diff --git a/proto/gows.proto b/proto/gows.proto index 6c5d0f1..0516007 100644 --- a/proto/gows.proto +++ b/proto/gows.proto @@ -14,11 +14,45 @@ message OptionalUInt32 { uint32 value = 1; } +message OptionalUInt64 { + uint64 value = 1; +} +message OptionalInt64 { + int64 value = 1; +} +message OptionalBool { + bool value = 1; +} +message OptionalDouble { + double value = 1; +} + +// +// Session + Jid shortcuts +// +message JidRequest { + Session session = 1; + string jid = 2; +} + +message JidStringRequest { + Session session = 1; + string jid = 2; + string value = 3; +} + +message JidBoolRequest { + Session session = 1; + string jid = 2; + bool value = 3; +} + + // // Events // service EventStream { - rpc StreamEvents(Session) returns (stream EventJson); + rpc StreamEvents(StreamEventsRequest) returns (stream EventJson); } message EventJson { @@ -27,6 +61,11 @@ message EventJson { string data = 3; } +message StreamEventsRequest { + Session session = 1; + repeated string exclude = 2; +} + service MessageService { // // Session management @@ -36,32 +75,153 @@ service MessageService { rpc GetSessionState(Session) returns (SessionStateResponse); rpc RequestCode(PairCodeRequest) returns (PairCodeResponse); rpc Logout(Session) returns (Empty); + // + // Profile + // + rpc SetProfileName(ProfileNameRequest) returns (Empty); + rpc SetProfileStatus(ProfileStatusRequest) returns (Empty); + rpc SetProfilePicture(SetProfilePictureRequest) returns (Empty); + + // + // Lids + // + rpc GetAllLids(GetLidsRequest) returns (JsonList); + rpc GetLidsCount(Session) returns (OptionalUInt64); + rpc FindPNByLid(EntityByIdRequest) returns (OptionalString); + rpc FindLIDByPhoneNumber(EntityByIdRequest) returns (OptionalString); + + // + // Groups + // + rpc FetchGroups(Session) returns (Empty); + rpc GetGroups(Session) returns (JsonList); + rpc GetGroupInfo(JidRequest) returns (Json); + rpc CreateGroup(CreateGroupRequest) returns (Json); + rpc LeaveGroup(JidRequest) returns (Empty); + rpc GetGroupInviteLink(JidRequest) returns (OptionalString); + rpc RevokeGroupInviteLink(JidRequest) returns (OptionalString); + rpc GetGroupInfoFromLink(GroupCodeRequest) returns (Json); + rpc JoinGroupWithLink(GroupCodeRequest) returns (Json); + rpc SetGroupName(JidStringRequest) returns (Empty); + rpc SetGroupDescription(JidStringRequest) returns (Empty); + rpc SetGroupPicture(SetPictureRequest) returns (Empty); + rpc SetGroupLocked(JidBoolRequest) returns (Empty); // change info only by admins + rpc SetGroupAnnounce(JidBoolRequest) returns (Empty); // send messages only by admins + rpc UpdateGroupParticipants(UpdateParticipantsRequest) returns (JsonList); + + // + // Communities + // + rpc ListCommunities(Session) returns (JsonList); + rpc GetCommunity(JidRequest) returns (Json); + rpc CreateCommunity(CreateCommunityRequest) returns (Json); + rpc LeaveCommunity(JidRequest) returns (Empty); + rpc GetCommunityLinkedGroups(JidRequest) returns (Json); + rpc LinkCommunityGroup(CommunityGroupLinkRequest) returns (Empty); + rpc UnlinkCommunityGroup(CommunityGroupLinkRequest) returns (Empty); + rpc CreateCommunityGroup(CreateCommunityGroupRequest) returns (Json); + rpc SetCommunitySubject(JidStringRequest) returns (Empty); + rpc SetCommunityDescription(JidStringRequest) returns (Empty); + rpc GetCommunityInviteLink(JidRequest) returns (OptionalString); + rpc JoinCommunity(GroupCodeRequest) returns (Json); + + // + // Business + // + rpc GetBusinessProfile(JidRequest) returns (Json); + // // Actions // - rpc SendMessage (MessageRequest) returns (MessageResponse); - rpc SendReaction (MessageReaction) returns (MessageResponse); rpc GetProfilePicture (ProfilePictureRequest) returns (ProfilePictureResponse); rpc SendPresence(PresenceRequest) returns (Empty); rpc SendChatPresence(ChatPresenceRequest) returns (Empty); rpc SubscribePresence(SubscribePresenceRequest) returns (Empty); - rpc MarkRead(MarkReadRequest) returns (Empty); rpc CheckPhones(CheckPhonesRequest) returns (CheckPhonesResponse); + rpc MarkChatUnread(ChatUnreadRequest) returns (Empty); + rpc MuteChat(ChatMuteRequest) returns (Empty); + rpc UnmuteChat(JidRequest) returns (Empty); + rpc UpdateBlockStatus(UpdateBlockStatusRequest) returns (Empty); + rpc GetBlocklist(Session) returns (BlocklistResponse); + rpc DeleteChat(JidRequest) returns (Empty); + rpc ClearChat(JidRequest) returns (Empty); + rpc StarMessage(StarMessageRequest) returns (Empty); + rpc PinMessage(PinMessageRequest) returns (Empty); + rpc UnpinMessage(UnpinMessageRequest) returns (Empty); + rpc GetPrivacySettings(Session) returns (PrivacySettingsResponse); + rpc UpdatePrivacySettings(UpdatePrivacySettingsRequest) returns (PrivacySettingsResponse); + rpc ReadStatus(ReadStatusRequest) returns (Empty); + + // + // Message + // + rpc GenerateNewMessageID (Session) returns (NewMessageIDResponse); + rpc SendMessage (MessageRequest) returns (MessageResponse); + rpc SendReaction (MessageReaction) returns (MessageResponse); + rpc MarkRead(MarkReadRequest) returns (Empty); + rpc EditMessage(EditMessageRequest) returns (MessageResponse); + rpc RevokeMessage(RevokeMessageRequest) returns (MessageResponse); + rpc SendButtonReply (ButtonReplyRequest) returns (MessageResponse); + // // Newsletters // rpc GetSubscribedNewsletters(NewsletterListRequest) returns (NewsletterList); rpc GetNewsletterInfo(NewsletterInfoRequest) returns (Newsletter); + rpc GetNewsletterMessagesByInvite(GetNewsletterMessagesByInviteRequest) returns (Json); + rpc SearchNewslettersByView(SearchNewslettersByViewRequest) returns (NewsletterSearchPageResult); + rpc SearchNewslettersByText(SearchNewslettersByTextRequest) returns (NewsletterSearchPageResult); rpc CreateNewsletter(CreateNewsletterRequest) returns (Newsletter); rpc NewsletterToggleMute(NewsletterToggleMuteRequest) returns (Empty); rpc NewsletterToggleFollow(NewsletterToggleFollowRequest) returns (Empty); + + // + // Labels + // + rpc GetLabels(GetLabelsRequest) returns (JsonList); + rpc UpsertLabel(UpsertLabelRequest) returns (Empty); + rpc DeleteLabel(DeleteLabelRequest) returns (Empty); + rpc AddChatLabel(ChatLabelRequest) returns (Empty); + rpc RemoveChatLabel(ChatLabelRequest) returns (Empty); + rpc GetLabelsByJid(EntityByIdRequest) returns (JsonList); + rpc GetChatsByLabelId(EntityByIdRequest) returns (JsonList); + + // + // Contacts + // + rpc UpdateContact(UpdateContactRequest) returns (Empty); + rpc GetContacts(GetContactsRequest) returns (JsonList); + rpc GetContactById(EntityByIdRequest) returns (Json); + + // + // Events + // + rpc CancelEventMessage(CancelEventMessageRequest) returns (MessageResponse); + // // Media // rpc DownloadMedia(DownloadMediaRequest) returns (DownloadMediaResponse); + + // + // Calls + // + rpc RejectCall(RejectCallRequest) returns (Empty); + + // + // Storage + // + rpc GetMessageById(EntityByIdRequest) returns (Json); + rpc GetMessages(GetMessagesRequest) returns (JsonList); + + + rpc GetChats(GetChatsRequest) returns (JsonList); + } + + // // Session management // @@ -93,14 +253,30 @@ message SessionStoreConfig { string address = 3; } +message SessionStorageConfig { + optional bool messages = 1; + optional bool groups = 2; + optional bool chats = 3; + optional bool labels = 4; +} + message SessionProxyConfig { string url = 1; } +message SessionIgnoreJidsConfig { + bool status = 1; + bool groups = 2; + bool newsletters = 3; + bool broadcast = 4; +} + message SessionConfig { SessionStoreConfig store = 1; SessionLogConfig log = 2; SessionProxyConfig proxy = 3; + optional SessionIgnoreJidsConfig ignore = 4; + optional SessionStorageConfig storage = 5; } message StartSessionRequest { @@ -116,6 +292,74 @@ message Session { string id = 1; } +// +// Profile +// +message ProfileNameRequest { + Session session = 1; + string name = 2; +} + +message ProfileStatusRequest { + Session session = 1; + string status = 2; +} + +message SetProfilePictureRequest { + Session session = 1; + bytes picture = 2; +} + +message CreateGroupRequest { + Session session = 1; + string name = 2; + repeated string participants = 3; +} + +message SetPictureRequest { + Session session = 1; + string jid = 2; + bytes picture = 3; +} + +enum ParticipantAction { + ADD = 0; + REMOVE = 1; + PROMOTE = 2; + DEMOTE = 3; +} + +message UpdateParticipantsRequest { + Session session = 1; + string jid = 2; + repeated string participants = 3; + ParticipantAction action = 4; +} +message GroupCodeRequest { + Session session = 1; + string code = 2; +} + +message CreateCommunityRequest { + Session session = 1; + string subject = 2; + string description = 3; +} + +message CreateCommunityGroupRequest { + Session session = 1; + string communityJid = 2; + string subject = 3; + repeated string participants = 4; +} + +message CommunityGroupLinkRequest { + Session session = 1; + string communityJid = 2; + string groupJid = 3; +} + + // // Actions // @@ -124,6 +368,8 @@ enum MediaType { AUDIO = 1; VIDEO = 2; DOCUMENT = 3; + PTV = 4; + STICKER = 5; } message AudioInfo { @@ -131,13 +377,54 @@ message AudioInfo { bytes waveform = 2; } +message VideoInfo { + float duration = 1; + bool gifPlayback = 2; + uint32 externalShareFullVideoDurationInSeconds = 3; +} + message Media { bytes content = 1; MediaType type = 2; string mimetype = 3; AudioInfo audio = 4; + string filename = 5; + string contentPath = 6; + VideoInfo video = 7; +} + +message LinkPreview { + string url = 1; + string title = 2; + string description = 3; + bytes image = 4; } +message vCardContact { + string displayName = 1; + string vcard = 3; +} + +message EventLocation { + string name = 1; + optional double degreesLongitude = 2; + optional double degreesLatitude = 3; +} + +message Location { + optional string name = 1; + double degreesLongitude = 2; + double degreesLatitude = 3; +} + +message EventMessage { + string name = 1; + optional string description = 2; + int64 startTime = 3; + optional int64 endTime = 4; + bool extraGuestsAllowed = 5; + EventLocation location = 6; +} message MessageRequest { Session session = 1; @@ -147,6 +434,56 @@ message MessageRequest { OptionalString backgroundColor = 5; OptionalUInt32 font = 6; + bool linkPreview = 7; + bool linkPreviewHighQuality = 8; + + string replyTo = 9; + // Pre-generated message ID + string id = 10; + // Status Message + repeated string participants = 11; + + // Custom link preview + LinkPreview preview = 12; + + // Contact message + repeated vCardContact contacts = 13; + + // Event message + EventMessage event = 14; + + // Poll message + PollMessage poll = 15; + + // List message + ListMessage list = 16; + + // Location message + Location location = 17; + + // Poll vote message + PollVoteMessage pollVote = 18; + + repeated string mentions = 19; +} + +message Row { + string title = 1; + optional string description = 2; + string rowId = 3; +} + +message Section { + string title = 1; + repeated Row rows = 2; +} + +message ListMessage { + string title = 1; + optional string description = 2; + optional string footer = 3; + string button = 4; + repeated Section sections = 5; } message MessageReaction { @@ -160,6 +497,11 @@ message MessageReaction { message MessageResponse { string id = 1; int64 timestamp = 2; + Json message = 3; +} + +message NewMessageIDResponse { + string id = 1; } message ProfilePictureRequest { @@ -170,6 +512,14 @@ message ProfilePictureResponse { string url = 2; } +message ButtonReplyRequest { + Session session = 1; + string jid = 2; + string selectedDisplayText = 3; + string selectedButtonID = 4; + string replyTo = 5; +} + enum Presence { AVAILABLE = 0; UNAVAILABLE = 1; @@ -208,6 +558,7 @@ message MarkReadRequest { string sender = 3; string messageId = 4; ReceiptType type = 5; + repeated string messageIds = 6; } message CheckPhonesRequest { @@ -215,6 +566,81 @@ message CheckPhonesRequest { repeated string phones = 2; } +message ChatUnreadRequest { + Session session = 1; + string jid = 2; + bool read = 3; +} + +message ChatMuteRequest { + Session session = 1; + string jid = 2; + OptionalInt64 duration_seconds = 3; + OptionalInt64 mute_end_ms = 4; +} + +enum BlockAction { + BLOCK = 0; + UNBLOCK = 1; +} + +message UpdateBlockStatusRequest { + Session session = 1; + string jid = 2; + BlockAction action = 3; +} + +message BlocklistResponse { + repeated string jids = 1; +} + +message PrivacySettingsResponse { + map categories = 1; + bool link_previews_disabled = 2; +} + +message UpdatePrivacySettingsRequest { + Session session = 1; + OptionalString lastSeen = 2; + OptionalString profilePicture = 3; + OptionalString status = 4; + OptionalString readReceipts = 5; + OptionalString groupsAdd = 6; + OptionalString online = 7; + OptionalString calls = 8; + OptionalString messages = 9; + OptionalBool linkPreviewsDisabled = 10; +} + +message ReadStatusRequest { + Session session = 1; + string contactId = 2; + string statusId = 3; +} + +message StarMessageRequest { + Session session = 1; + string jid = 2; + string sender = 3; + string messageId = 4; + bool star = 5; +} + +message PinMessageRequest { + Session session = 1; + string jid = 2; + string sender = 3; + string messageId = 4; + int64 duration_seconds = 5; +} + +message UnpinMessageRequest { + Session session = 1; + string jid = 2; + string sender = 3; + string messageId = 4; +} + message PhoneInfo { string phone = 1; string jid = 2; @@ -224,6 +650,24 @@ message PhoneInfo { message CheckPhonesResponse { repeated PhoneInfo infos = 1; } + +message RevokeMessageRequest { + Session session = 1; + string jid = 2; + string sender = 3; + string messageId = 4; + repeated string participants = 5; +} + +message EditMessageRequest { + Session session = 1; + string jid = 2; + string messageId = 4; + string text = 5; + bool linkPreview = 6; + bool linkPreviewHighQuality = 7; +} + // // Newsletters // @@ -240,6 +684,7 @@ message Newsletter { string picture = 5; bool verified = 7; string role = 8; + int64 subscriberCount = 9; } message NewsletterList { @@ -251,6 +696,43 @@ message NewsletterInfoRequest { string id = 2; } +message GetNewsletterMessagesByInviteRequest { + Session session = 1; + string invite = 2; + int64 limit = 3; +} + +message SearchPage { + uint64 limit = 2; + string startCursor = 3; +} + +message SearchNewslettersByViewRequest { + Session session = 1; + SearchPage page = 2; + string view = 3; + repeated string categories = 4; + repeated string countries = 5; +} + +message SearchNewslettersByTextRequest { + Session session = 1; + SearchPage page = 2; + string text = 3; + repeated string categories = 4; +} +message SearchPageResult { + string startCursor = 1; + string endCursor = 2; + bool hasNextPage = 3; + bool hasPreviousPage = 4; +} + +message NewsletterSearchPageResult { + SearchPageResult page = 1; + NewsletterList newsletters = 2; +} + message CreateNewsletterRequest { Session session = 1; string name = 2; @@ -277,9 +759,165 @@ message NewsletterToggleFollowRequest { message DownloadMediaRequest { Session session = 1; string message = 2; // JSON string + string jid = 3; + string messageId = 4; + string contentPath = 5; } message DownloadMediaResponse { bytes content = 1; + string contentPath = 5; } +// +// Storage +// + +message EntityByIdRequest { + Session session = 1; + string id = 2; +} + +message Json { + string data = 1; +} + +message JsonList { + repeated Json elements = 1; +} + +message Pagination { + uint64 limit = 1; + uint64 offset = 2; +} + +message SortBy { + string field = 1; + enum Order { + ASC = 0; + DESC = 1; + } + Order order = 2; +} + + +// +// Storage - Messages +// +message MessageFilters { + OptionalString jid = 1; + OptionalUInt64 timestampGte = 2; + OptionalUInt64 timestampLte = 3; + OptionalBool fromMe = 4; + OptionalUInt32 status = 5; +} + +message GetMessagesRequest { + Session session = 1; + MessageFilters filters = 2; + Pagination pagination = 3; + SortBy sortBy = 4; + OptionalBool merge = 5; +} + +// +// Contacts +// + +message UpdateContactRequest { + Session session = 1; + string jid = 2; + string firstName = 3; + string lastName = 4; +} + +message GetContactsRequest { + Session session = 1; + SortBy sortBy = 2; + Pagination pagination = 3; +} + +// +// Storage - Chats +// +message ChatFilter { + repeated string jids = 1; +} + +message GetChatsRequest { + Session session = 1; + SortBy sortBy = 2; + Pagination pagination = 3; + ChatFilter filter = 4; + OptionalBool merge = 5; +} + +// +// Labels +// +message GetLabelsRequest { + Session session = 1; +} + +message Label { + string id = 1; + string name = 2; + int32 color = 4; +} + +message UpsertLabelRequest { + Session session = 1; + Label label = 2; +} + +message DeleteLabelRequest{ + Session session = 1; + Label label = 2; +} + + +message ChatLabelRequest { + Session session = 1; + string chatId = 2; + string labelId = 3; +} + +// +// Events +// +message CancelEventMessageRequest { + Session session = 1; + string jid = 2; + string messageId = 4; +} + +// +// Lids +// +message GetLidsRequest { + Session session = 1; +} + +// +// Lids +// +message RejectCallRequest { + Session session = 1; + string from = 2; + string id = 3; +} + +// +// Poll +// +message PollMessage { + string name = 1; + repeated string options = 2; + bool multipleAnswers = 3; +} + +message PollVoteMessage { + string pollMessageId = 1; + optional int64 pollServerId = 2; // only for Channels + repeated string options = 3; +} diff --git a/src/go.mod b/src/go.mod index e65c430..4e406c3 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,40 +1,39 @@ module github.com/devlikeapro/gows -go 1.23.3 +go 1.25.0 require ( github.com/golang/protobuf v1.5.4 - github.com/mattn/go-sqlite3 v1.14.24 - github.com/mdp/qrterminal/v3 v3.2.0 - go.mau.fi/whatsmeow v0.0.0-20250104105216-918c879fcd19 + github.com/google/uuid v1.6.0 + github.com/h2non/bimg v1.1.9 + github.com/lib/pq v1.10.9 + github.com/mattn/go-sqlite3 v1.14.45 + github.com/u2takey/ffmpeg-go v0.5.0 + go.mau.fi/whatsmeow v0.0.0-20260622185415-5f04eac6dbbb google.golang.org/grpc v1.68.0 - google.golang.org/protobuf v1.36.1 + google.golang.org/protobuf v1.36.11 ) require ( - filippo.io/edwards25519 v1.1.0 // indirect + filippo.io/edwards25519 v1.2.0 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/cettoana/go-waveform v0.0.0-20210107122202-35aaec2de427 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/h2non/bimg v1.1.9 // indirect + github.com/beeper/argo-go v1.1.2 // indirect + github.com/coder/websocket v1.8.15 // indirect + github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/rs/zerolog v1.33.0 // indirect - github.com/u2takey/ffmpeg-go v0.5.0 // indirect + github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect + github.com/rs/zerolog v1.35.1 // indirect github.com/u2takey/go-utils v0.3.1 // indirect - github.com/xiph/ogg v1.3.5 // indirect - go.mau.fi/libsignal v0.1.1 // indirect - go.mau.fi/util v0.8.3 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect + github.com/vektah/gqlparser/v2 v2.5.27 // indirect + go.mau.fi/libsignal v0.2.2 // indirect + go.mau.fi/util v0.9.10 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 // indirect - mccoy.space/g/ogg v0.0.0-20221103053400-1ea94e6f3152 // indirect - rsc.io/qr v0.2.0 // indirect ) diff --git a/src/go.sum b/src/go.sum index 499b550..c78ef31 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,197 +1,127 @@ -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gioui.org v0.0.0-20200628203458-851255f7a67b/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/cettoana/go-waveform v0.0.0-20210107122202-35aaec2de427 h1:8DlrwsUv3km3BVS6a9pUBv4SvVl8AM4UcUm3hW2jjCY= -github.com/cettoana/go-waveform v0.0.0-20210107122202-35aaec2de427/go.mod h1:WhazezqBT3T5GMSQCWKNKycfevN/a/Na4GkstKwu37c= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs= +github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4= +github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= +github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= +github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-latex/latex v0.0.0-20200518072620-0806b477ea35/go.mod h1:PNI+CcWytn/2Z/9f1SGOOYn0eILruVyp0v2/iAs8asQ= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/h2non/bimg v1.1.9 h1:WH20Nxko9l/HFm4kZCA3Phbgu2cbHvYzxwxn9YROEGg= github.com/h2non/bimg v1.1.9/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk= -github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk= +github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk= +github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= -github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= +github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU= github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc= github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys= github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/xiph/ogg v1.3.5 h1:88XqZNhTGMYTNlj3q0QuuSex3zA1D5lEGDSuXXmBBGk= -github.com/xiph/ogg v1.3.5/go.mod h1:1ifPhXYnM5MxcYUnaQKrUI4SM2Qp8ueN/04cJaMGp28= -go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI= -go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw= -go.mau.fi/util v0.8.2 h1:zWbVHwdRKwI6U9AusmZ8bwgcLosikwbb4GGqLrNr1YE= -go.mau.fi/util v0.8.2/go.mod h1:BHHC9R2WLMJd1bwTZfTcFxUgRFmUgUmiWcT4RbzUgiA= -go.mau.fi/util v0.8.3 h1:sulhXtfquMrQjsOP67x9CzWVBYUwhYeoo8hNQIpCWZ4= -go.mau.fi/util v0.8.3/go.mod h1:c00Db8xog70JeIsEvhdHooylTkTkakgnAOsZ04hplQY= -go.mau.fi/whatsmeow v0.0.0-20241121132808-ae900cb6bee4 h1:fb3X+Us6dDmXWZJt7H9o9P+pSiEq9Fut1jLqurVPpVc= -go.mau.fi/whatsmeow v0.0.0-20241121132808-ae900cb6bee4/go.mod h1:iB+F/NVNOnyumU2p/TKTSSdBhH05GHFG36diYuFp9VQ= -go.mau.fi/whatsmeow v0.0.0-20250104105216-918c879fcd19 h1:uVS+Zct5fF8rSXV9lfs87zoXdge0JXTzVGNkjmZ61UU= -go.mau.fi/whatsmeow v0.0.0-20250104105216-918c879fcd19/go.mod h1:TLzm2XkwgufONEmiVAsFny+9uBqyEZnUoPrQAfMyuSU= +github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= +github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= +go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= +go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= +go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= +go.mau.fi/whatsmeow v0.0.0-20260622185415-5f04eac6dbbb h1:N2LheAJIBgdohEcpfSK0xAgf9PMzZOmWn0oVWY20zNM= +go.mau.fi/whatsmeow v0.0.0-20260622185415-5f04eac6dbbb/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.1/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.8.1/go.mod h1:3GH8dTfoceRTELDnv+4HNwbvM/eMfdDUGHFG2bo3NeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 h1:xeVptzkP8BuJhoIjNizd2bRHfq9KB9HfOLZu90T04XM= -gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -mccoy.space/g/ogg v0.0.0-20221103053400-1ea94e6f3152 h1:p3Sx79jj5P979gSyMOoSftOXFPuGquvCZMzbOeV+8oE= -mccoy.space/g/ogg v0.0.0-20221103053400-1ea94e6f3152/go.mod h1:Fa7PeLv7azi4nCJLxrQ2SK7pqOEvqgt9deL4K/XZCA8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= -rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/src/gows/gows.go b/src/gows/gows.go index 089344a..c774da8 100644 --- a/src/gows/gows.go +++ b/src/gows/gows.go @@ -104,11 +104,11 @@ type ConnectedEventData struct { func BuildSession(ctx context.Context, log waLog.Logger, dialect string, address string) (*GoWS, error) { // Prepare the database - container, err := sqlstore.New(dialect, address, log.Sub("Database")) + container, err := sqlstore.New(ctx, dialect, address, log.Sub("Database")) if err != nil { return nil, err } - deviceStore, err := container.GetFirstDevice() + deviceStore, err := container.GetFirstDevice(ctx) if err != nil { _ = container.Close() return nil, err diff --git a/src/server/actions.go b/src/server/actions.go index f890467..d3d2a55 100644 --- a/src/server/actions.go +++ b/src/server/actions.go @@ -18,7 +18,7 @@ func (s *Server) GetProfilePicture(ctx context.Context, req *__.ProfilePictureRe if err != nil { return nil, err } - info, err := cli.GetProfilePictureInfo(jid, &whatsmeow.GetProfilePictureParams{ + info, err := cli.GetProfilePictureInfo(ctx, jid, &whatsmeow.GetProfilePictureParams{ Preview: false, }) if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) { @@ -49,7 +49,7 @@ func (s *Server) CheckPhones(ctx context.Context, req *__.CheckPhonesRequest) (* phones[i] = p } - res, err := cli.IsOnWhatsApp(phones) + res, err := cli.IsOnWhatsApp(ctx, phones) if err != nil { return nil, err } diff --git a/src/server/business.go b/src/server/business.go new file mode 100644 index 0000000..b785e40 --- /dev/null +++ b/src/server/business.go @@ -0,0 +1,24 @@ +package server + +import ( + "context" + + __ "github.com/devlikeapro/gows/proto" + "go.mau.fi/whatsmeow/types" +) + +func (s *Server) GetBusinessProfile(ctx context.Context, req *__.JidRequest) (*__.Json, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + profile, err := cli.GetBusinessProfile(ctx, jid) + if err != nil { + return nil, err + } + return s.toJson(profile), nil +} diff --git a/src/server/chat_actions.go b/src/server/chat_actions.go new file mode 100644 index 0000000..c102aa0 --- /dev/null +++ b/src/server/chat_actions.go @@ -0,0 +1,275 @@ +package server + +import ( + "context" + "errors" + "time" + + __ "github.com/devlikeapro/gows/proto" + "github.com/golang/protobuf/proto" + "go.mau.fi/whatsmeow/appstate" + waCommon "go.mau.fi/whatsmeow/proto/waCommon" + "go.mau.fi/whatsmeow/proto/waE2E" + waSyncAction "go.mau.fi/whatsmeow/proto/waSyncAction" + "go.mau.fi/whatsmeow/types" + "go.mau.fi/whatsmeow/types/events" +) + +func (s *Server) MuteChat(ctx context.Context, req *__.ChatMuteRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + + var muteDuration time.Duration + switch { + case req.GetMuteEndMs() != nil: + muteEndMs := req.GetMuteEndMs().GetValue() + if muteEndMs > 0 && muteEndMs < 1_000_000_000_000 { + muteEndMs *= 1000 + } + remaining := time.Duration(muteEndMs-time.Now().UnixMilli()) * time.Millisecond + if remaining <= 0 { + patch := appstate.BuildMute(jid, false, 0) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil + } + muteDuration = remaining + case req.GetDurationSeconds() != nil: + sec := req.GetDurationSeconds().GetValue() + muteDuration = time.Duration(sec) * time.Second + default: + muteDuration = 8 * time.Hour + } + + patch := appstate.BuildMute(jid, true, muteDuration) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) UnmuteChat(ctx context.Context, req *__.JidRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + patch := appstate.BuildMute(jid, false, 0) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) UpdateBlockStatus(ctx context.Context, req *__.UpdateBlockStatusRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + var action events.BlocklistChangeAction + switch req.GetAction() { + case __.BlockAction_BLOCK: + action = events.BlocklistChangeActionBlock + case __.BlockAction_UNBLOCK: + action = events.BlocklistChangeActionUnblock + default: + return nil, errors.New("invalid block action") + } + _, err = cli.UpdateBlocklist(ctx, jid, action) + if err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) GetBlocklist(ctx context.Context, req *__.Session) (*__.BlocklistResponse, error) { + cli, err := s.Sm.Get(req.GetId()) + if err != nil { + return nil, err + } + list, err := cli.GetBlocklist(ctx) + if err != nil { + return nil, err + } + jids := make([]string, 0, len(list.JIDs)) + for _, j := range list.JIDs { + jids = append(jids, j.String()) + } + return &__.BlocklistResponse{Jids: jids}, nil +} + +func syncActionMessageRange(lastMessageTimestamp time.Time, lastMessageKey *waCommon.MessageKey) *waSyncAction.SyncActionMessageRange { + if lastMessageTimestamp.IsZero() { + lastMessageTimestamp = time.Now() + } + messageRange := &waSyncAction.SyncActionMessageRange{ + LastMessageTimestamp: proto.Int64(lastMessageTimestamp.Unix()), + } + if lastMessageKey != nil { + messageRange.Messages = []*waSyncAction.SyncActionMessage{{ + Key: lastMessageKey, + Timestamp: proto.Int64(lastMessageTimestamp.Unix()), + }} + } + return messageRange +} + +func buildClearChat(target types.JID, lastMessageTimestamp time.Time, lastMessageKey *waCommon.MessageKey) appstate.PatchInfo { + action := &waSyncAction.ClearChatAction{ + MessageRange: syncActionMessageRange(lastMessageTimestamp, lastMessageKey), + } + return appstate.PatchInfo{ + Type: appstate.WAPatchRegularHigh, + Mutations: []appstate.MutationInfo{{ + Index: []string{appstate.IndexClearChat, target.String()}, + Version: 6, + Value: &waSyncAction.SyncActionValue{ + ClearChatAction: action, + }, + }}, + } +} + +func (s *Server) DeleteChat(ctx context.Context, req *__.JidRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + patch := appstate.BuildDeleteChat(jid, time.Now(), nil, false) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) ClearChat(ctx context.Context, req *__.JidRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + patch := buildClearChat(jid, time.Now(), nil) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) StarMessage(ctx context.Context, req *__.StarMessageRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + target, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + sender, err := types.ParseJID(req.GetSender()) + if err != nil { + return nil, err + } + fromMe := false + if cli.Store.ID != nil && sender.User == cli.Store.ID.User { + fromMe = true + } else if sender.User == cli.Store.LID.User && cli.Store.LID.User != "" { + fromMe = true + } + patch := appstate.BuildStar(target, sender, req.GetMessageId(), fromMe, req.GetStar()) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func buildPinInChatMessage( + cli interface { + BuildMessageKey(chat, sender types.JID, id types.MessageID) *waCommon.MessageKey + }, + chat types.JID, + sender types.JID, + messageID types.MessageID, + durationSeconds int64, + pin bool, +) *waE2E.Message { + pinType := waE2E.PinInChatMessage_PIN_FOR_ALL + if !pin { + pinType = waE2E.PinInChatMessage_UNPIN_FOR_ALL + } + message := &waE2E.Message{ + PinInChatMessage: &waE2E.PinInChatMessage{ + Key: cli.BuildMessageKey(chat, sender, messageID), + Type: pinType.Enum(), + SenderTimestampMS: proto.Int64(time.Now().UnixMilli()), + }, + } + if pin && durationSeconds > 0 { + message.MessageContextInfo = &waE2E.MessageContextInfo{ + MessageAddOnDurationInSecs: proto.Uint32(uint32(durationSeconds)), + MessageAddOnExpiryType: waE2E.MessageContextInfo_STATIC.Enum(), + } + } + return message +} + +func (s *Server) PinMessage(ctx context.Context, req *__.PinMessageRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + chat, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + sender, err := types.ParseJID(req.GetSender()) + if err != nil { + return nil, err + } + message := buildPinInChatMessage(cli, chat, sender, req.GetMessageId(), req.GetDurationSeconds(), true) + _, err = cli.SendMessage(ctx, chat, message) + if err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) UnpinMessage(ctx context.Context, req *__.UnpinMessageRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + chat, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + sender, err := types.ParseJID(req.GetSender()) + if err != nil { + return nil, err + } + message := buildPinInChatMessage(cli, chat, sender, req.GetMessageId(), 0, false) + _, err = cli.SendMessage(ctx, chat, message) + if err != nil { + return nil, err + } + return &__.Empty{}, nil +} diff --git a/src/server/communities.go b/src/server/communities.go new file mode 100644 index 0000000..3641e8b --- /dev/null +++ b/src/server/communities.go @@ -0,0 +1,250 @@ +package server + +import ( + "context" + + __ "github.com/devlikeapro/gows/proto" + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types" +) + +func (s *Server) toJson(data interface{}) *__.Json { + return &__.Json{Data: s.safeMarshal(data)} +} + +func (s *Server) toJsonList(items []interface{}) *__.JsonList { + elements := make([]*__.Json, len(items)) + for i, item := range items { + elements[i] = s.toJson(item) + } + return &__.JsonList{Elements: elements} +} + +func (s *Server) ListCommunities(ctx context.Context, req *__.Session) (*__.JsonList, error) { + cli, err := s.Sm.Get(req.GetId()) + if err != nil { + return nil, err + } + groups, err := cli.GetJoinedGroups(ctx) + if err != nil { + return nil, err + } + var communities []interface{} + for _, group := range groups { + if group.IsParent { + communities = append(communities, group) + } + } + return s.toJsonList(communities), nil +} + +func (s *Server) GetCommunity(ctx context.Context, req *__.JidRequest) (*__.Json, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + info, err := cli.GetGroupInfo(ctx, jid) + if err != nil { + return nil, err + } + return s.toJson(info), nil +} + +func (s *Server) CreateCommunity(ctx context.Context, req *__.CreateCommunityRequest) (*__.Json, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + createReq := whatsmeow.ReqCreateGroup{ + Name: req.GetSubject(), + } + createReq.IsParent = true + info, err := cli.CreateGroup(ctx, createReq) + if err != nil { + return nil, err + } + if description := req.GetDescription(); description != "" { + if err := cli.SetGroupDescription(ctx, info.JID, description); err != nil { + return nil, err + } + info, err = cli.GetGroupInfo(ctx, info.JID) + if err != nil { + return nil, err + } + } + return s.toJson(info), nil +} + +func (s *Server) LeaveCommunity(ctx context.Context, req *__.JidRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + if err := cli.LeaveGroup(ctx, jid); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +type communityLinkedGroupsResponse struct { + CommunityJID string `json:"communityJid"` + IsCommunity bool `json:"isCommunity"` + LinkedGroups []*types.GroupLinkTarget `json:"linkedGroups"` +} + +func (s *Server) GetCommunityLinkedGroups(ctx context.Context, req *__.JidRequest) (*__.Json, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + info, err := cli.GetGroupInfo(ctx, jid) + if err != nil { + return nil, err + } + linked, err := cli.GetSubGroups(ctx, jid) + if err != nil { + return nil, err + } + return s.toJson(communityLinkedGroupsResponse{ + CommunityJID: jid.String(), + IsCommunity: info.IsParent, + LinkedGroups: linked, + }), nil +} + +func (s *Server) LinkCommunityGroup(ctx context.Context, req *__.CommunityGroupLinkRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + parent, err := types.ParseJID(req.GetCommunityJid()) + if err != nil { + return nil, err + } + child, err := types.ParseJID(req.GetGroupJid()) + if err != nil { + return nil, err + } + if err := cli.LinkGroup(ctx, parent, child); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) UnlinkCommunityGroup(ctx context.Context, req *__.CommunityGroupLinkRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + parent, err := types.ParseJID(req.GetCommunityJid()) + if err != nil { + return nil, err + } + child, err := types.ParseJID(req.GetGroupJid()) + if err != nil { + return nil, err + } + if err := cli.UnlinkGroup(ctx, parent, child); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) CreateCommunityGroup(ctx context.Context, req *__.CreateCommunityGroupRequest) (*__.Json, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + parent, err := types.ParseJID(req.GetCommunityJid()) + if err != nil { + return nil, err + } + participants := make([]types.JID, len(req.GetParticipants())) + for i, p := range req.GetParticipants() { + jid, parseErr := types.ParseJID(p) + if parseErr != nil { + return nil, parseErr + } + participants[i] = jid + } + createReq := whatsmeow.ReqCreateGroup{ + Name: req.GetSubject(), + Participants: participants, + } + createReq.LinkedParentJID = parent + info, err := cli.CreateGroup(ctx, createReq) + if err != nil { + return nil, err + } + return s.toJson(info), nil +} + +func (s *Server) SetCommunitySubject(ctx context.Context, req *__.JidStringRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + if err := cli.SetGroupName(ctx, jid, req.GetValue()); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) SetCommunityDescription(ctx context.Context, req *__.JidStringRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + if err := cli.SetGroupDescription(ctx, jid, req.GetValue()); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) GetCommunityInviteLink(ctx context.Context, req *__.JidRequest) (*__.OptionalString, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetJid()) + if err != nil { + return nil, err + } + link, err := cli.GetGroupInviteLink(ctx, jid, false) + if err != nil { + return nil, err + } + return &__.OptionalString{Value: link}, nil +} + +func (s *Server) JoinCommunity(ctx context.Context, req *__.GroupCodeRequest) (*__.Json, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := cli.JoinGroupWithLink(ctx, req.GetCode()) + if err != nil { + return nil, err + } + return s.toJson(map[string]string{"jid": jid.String()}), nil +} diff --git a/src/server/events.go b/src/server/events.go index 3d557c2..16d3be3 100644 --- a/src/server/events.go +++ b/src/server/events.go @@ -27,8 +27,25 @@ func (s *Server) safeMarshal(v interface{}) (result string) { return result } -func (s *Server) StreamEvents(req *__.Session, stream grpc.ServerStreamingServer[__.EventJson]) error { - name := req.GetId() +func isExcludedEvent(eventType string, exclude []string) bool { + if len(exclude) == 0 { + return false + } + shortName := eventType + if idx := strings.LastIndex(eventType, "."); idx >= 0 { + shortName = eventType[idx+1:] + } + for _, excluded := range exclude { + if eventType == excluded || shortName == excluded { + return true + } + } + return false +} + +func (s *Server) StreamEvents(req *__.StreamEventsRequest, stream grpc.ServerStreamingServer[__.EventJson]) error { + name := req.GetSession().GetId() + exclude := req.GetExclude() streamId := uuid.New() listener := s.addListener(name, streamId) defer s.removeListener(name, streamId) @@ -40,6 +57,9 @@ func (s *Server) StreamEvents(req *__.Session, stream grpc.ServerStreamingServer // Remove * at the start if it's * eventType := reflect.TypeOf(event).String() eventType = strings.TrimPrefix(eventType, "*") + if isExcludedEvent(eventType, exclude) { + continue + } jsonString := s.safeMarshal(event) if jsonString == "" { diff --git a/src/server/grpc.go b/src/server/grpc.go index 454c219..fc77f24 100644 --- a/src/server/grpc.go +++ b/src/server/grpc.go @@ -16,8 +16,8 @@ var _ pb.MessageServiceServer = (*Server)(nil) var _ pb.EventStreamServer = (*Server)(nil) type Server struct { - pb.UnsafeMessageServiceServer - pb.UnsafeEventStreamServer + pb.UnimplementedMessageServiceServer + pb.UnimplementedEventStreamServer Sm *gows.SessionManager log waLog.Logger diff --git a/src/server/labels.go b/src/server/labels.go new file mode 100644 index 0000000..1d9e352 --- /dev/null +++ b/src/server/labels.go @@ -0,0 +1,41 @@ +package server + +import ( + "context" + + __ "github.com/devlikeapro/gows/proto" + "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/types" +) + +func (s *Server) AddChatLabel(ctx context.Context, req *__.ChatLabelRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetChatId()) + if err != nil { + return nil, err + } + patch := appstate.BuildLabelChat(jid, req.GetLabelId(), true) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} + +func (s *Server) RemoveChatLabel(ctx context.Context, req *__.ChatLabelRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + jid, err := types.ParseJID(req.GetChatId()) + if err != nil { + return nil, err + } + patch := appstate.BuildLabelChat(jid, req.GetLabelId(), false) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + return &__.Empty{}, nil +} diff --git a/src/server/media.go b/src/server/media.go index a903dc0..53a66ff 100644 --- a/src/server/media.go +++ b/src/server/media.go @@ -16,7 +16,7 @@ func (s *Server) DownloadMedia(ctx context.Context, req *__.DownloadMediaRequest if err != nil { return nil, err } - resp, err := cli.DownloadAny(msg) + resp, err := cli.DownloadAny(ctx, msg) if err != nil { s.log.Errorf("Failed to download media: %v", err) return nil, nil diff --git a/src/server/messages.go b/src/server/messages.go index df47241..5e3ede2 100644 --- a/src/server/messages.go +++ b/src/server/messages.go @@ -6,6 +6,7 @@ import ( "github.com/devlikeapro/gows/media" "github.com/devlikeapro/gows/proto" "github.com/golang/protobuf/proto" + "github.com/h2non/bimg" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" @@ -175,6 +176,32 @@ func (s *Server) SendMessage(ctx context.Context, req *__.MessageRequest) (*__.M FileLength: &mediaResponse.FileLength, JPEGThumbnail: thumbnail, } + case __.MediaType_STICKER: + mediaType = whatsmeow.MediaImage + mediaResponse, err = cli.UploadMedia(ctx, jid, req.Media.Content, mediaType) + if err != nil { + return nil, err + } + mimetype := req.Media.Mimetype + if mimetype == "" { + mimetype = "image/webp" + } + var width, height uint32 + if size, sizeErr := bimg.NewImage(req.Media.Content).Size(); sizeErr == nil { + width = uint32(size.Width) + height = uint32(size.Height) + } + message.StickerMessage = &waE2E.StickerMessage{ + Mimetype: proto.String(mimetype), + URL: &mediaResponse.URL, + DirectPath: &mediaResponse.DirectPath, + MediaKey: mediaResponse.MediaKey, + FileEncSHA256: mediaResponse.FileEncSHA256, + FileSHA256: mediaResponse.FileSHA256, + FileLength: &mediaResponse.FileLength, + Width: &width, + Height: &height, + } } } @@ -237,7 +264,7 @@ func (s *Server) MarkRead(ctx context.Context, req *__.MarkReadRequest) (*__.Emp // id to ids array ids := []types.MessageID{req.MessageId} now := time.Now() - err = cli.MarkRead(ids, now, jid, sender, receiptType) + err = cli.MarkRead(ctx, ids, now, jid, sender, receiptType) if err != nil { return nil, err } diff --git a/src/server/newsletter.go b/src/server/newsletter.go index 638ca41..d6021e6 100644 --- a/src/server/newsletter.go +++ b/src/server/newsletter.go @@ -44,7 +44,7 @@ func (s *Server) GetSubscribedNewsletters(ctx context.Context, req *__.Newslette if err != nil { return nil, err } - resp, err := cli.GetSubscribedNewsletters() + resp, err := cli.GetSubscribedNewsletters(ctx) if err != nil { return nil, err } @@ -74,13 +74,13 @@ func (s *Server) GetNewsletterInfo(ctx context.Context, req *__.NewsletterInfoRe if err != nil { return nil, err } - resp, err := cli.GetNewsletterInfo(jid) + resp, err := cli.GetNewsletterInfo(ctx, jid) if err != nil { return nil, err } return toNewsletter(resp), nil } - resp, err := cli.GetNewsletterInfoWithInvite(id) + resp, err := cli.GetNewsletterInfoWithInvite(ctx, id) if err != nil { return nil, err } @@ -97,7 +97,7 @@ func (s *Server) CreateNewsletter(ctx context.Context, req *__.CreateNewsletterR Description: req.GetDescription(), Picture: req.GetPicture(), } - resp, err := cli.CreateNewsletter(params) + resp, err := cli.CreateNewsletter(ctx, params) if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (s *Server) NewsletterToggleMute(ctx context.Context, req *__.NewsletterTog if !gows.IsNewsletter(jid) { return nil, errors.New("invalid jid, not a newsletter") } - err = cli.NewsletterToggleMute(jid, req.GetMute()) + err = cli.NewsletterToggleMute(ctx, jid, req.GetMute()) if err != nil { return nil, err } @@ -138,9 +138,9 @@ func (s *Server) NewsletterToggleFollow(ctx context.Context, req *__.NewsletterT return nil, errors.New("invalid jid, not a newsletter") } if req.Follow { - err = cli.FollowNewsletter(jid) + err = cli.FollowNewsletter(ctx, jid) } else { - err = cli.UnfollowNewsletter(jid) + err = cli.UnfollowNewsletter(ctx, jid) } if err != nil { return nil, err diff --git a/src/server/presence.go b/src/server/presence.go index ddb1e51..01b52d7 100644 --- a/src/server/presence.go +++ b/src/server/presence.go @@ -23,7 +23,7 @@ func (s *Server) SendPresence(ctx context.Context, req *__.PresenceRequest) (*__ return nil, errors.New("invalid presence") } - err = cli.SendPresence(presence) + err = cli.SendPresence(ctx, presence) if err != nil { return nil, err } @@ -53,7 +53,7 @@ func (s *Server) SendChatPresence(ctx context.Context, req *__.ChatPresenceReque default: return nil, errors.New("invalid chat presence") } - err = cli.SendChatPresence(jid, presence, presenceMedia) + err = cli.SendChatPresence(ctx, jid, presence, presenceMedia) if err != nil { return nil, err } @@ -69,11 +69,11 @@ func (s *Server) SubscribePresence(ctx context.Context, req *__.SubscribePresenc if err != nil { return nil, err } - err = cli.SendPresence(types.PresenceAvailable) + err = cli.SendPresence(ctx, types.PresenceAvailable) if err != nil { return nil, err } - err = cli.SubscribePresence(jid) + err = cli.SubscribePresence(ctx, jid) if err != nil { return nil, err } diff --git a/src/server/privacy.go b/src/server/privacy.go new file mode 100644 index 0000000..5d4ae69 --- /dev/null +++ b/src/server/privacy.go @@ -0,0 +1,118 @@ +package server + +import ( + "context" + + __ "github.com/devlikeapro/gows/proto" + "github.com/golang/protobuf/proto" + "go.mau.fi/whatsmeow/appstate" + waSyncAction "go.mau.fi/whatsmeow/proto/waSyncAction" + "go.mau.fi/whatsmeow/types" +) + +func privacySettingsToResponse(settings *types.PrivacySettings, linkPreviewsDisabled bool) *__.PrivacySettingsResponse { + categories := map[string]string{} + if settings != nil { + if settings.LastSeen != "" { + categories["last"] = string(settings.LastSeen) + } + if settings.Profile != "" { + categories["profile"] = string(settings.Profile) + } + if settings.Status != "" { + categories["status"] = string(settings.Status) + } + if settings.ReadReceipts != "" { + categories["readreceipts"] = string(settings.ReadReceipts) + } + if settings.GroupAdd != "" { + categories["groupadd"] = string(settings.GroupAdd) + } + if settings.Online != "" { + categories["online"] = string(settings.Online) + } + if settings.CallAdd != "" { + categories["calladd"] = string(settings.CallAdd) + } + if settings.Messages != "" { + categories["messages"] = string(settings.Messages) + } + } + return &__.PrivacySettingsResponse{ + Categories: categories, + LinkPreviewsDisabled: linkPreviewsDisabled, + } +} + +func buildDisableLinkPreviews(disabled bool) appstate.PatchInfo { + return appstate.PatchInfo{ + Type: appstate.WAPatchCriticalBlock, + Mutations: []appstate.MutationInfo{{ + Index: []string{appstate.IndexSettingDisableLinkPreviews}, + Version: 7, + Value: &waSyncAction.SyncActionValue{ + PrivacySettingDisableLinkPreviewsAction: &waSyncAction.PrivacySettingDisableLinkPreviewsAction{ + IsPreviewsDisabled: proto.Bool(disabled), + }, + }, + }}, + } +} + +func (s *Server) GetPrivacySettings(ctx context.Context, req *__.Session) (*__.PrivacySettingsResponse, error) { + cli, err := s.Sm.Get(req.GetId()) + if err != nil { + return nil, err + } + settings, err := cli.TryFetchPrivacySettings(ctx, true) + if err != nil { + return nil, err + } + return privacySettingsToResponse(settings, false), nil +} + +func (s *Server) UpdatePrivacySettings(ctx context.Context, req *__.UpdatePrivacySettingsRequest) (*__.PrivacySettingsResponse, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + + linkPreviewsDisabled := false + if req.LinkPreviewsDisabled != nil { + linkPreviewsDisabled = req.LinkPreviewsDisabled.GetValue() + patch := buildDisableLinkPreviews(linkPreviewsDisabled) + if err := cli.SendAppState(ctx, patch); err != nil { + return nil, err + } + } + + updates := []struct { + field *__.OptionalString + name types.PrivacySettingType + }{ + {req.LastSeen, types.PrivacySettingTypeLastSeen}, + {req.ProfilePicture, types.PrivacySettingTypeProfile}, + {req.Status, types.PrivacySettingTypeStatus}, + {req.ReadReceipts, types.PrivacySettingTypeReadReceipts}, + {req.GroupsAdd, types.PrivacySettingTypeGroupAdd}, + {req.Online, types.PrivacySettingTypeOnline}, + {req.Calls, types.PrivacySettingTypeCallAdd}, + {req.Messages, types.PrivacySettingTypeMessages}, + } + + for _, update := range updates { + if update.field == nil { + continue + } + _, err = cli.SetPrivacySetting(ctx, update.name, types.PrivacySetting(update.field.GetValue())) + if err != nil { + return nil, err + } + } + + settings, err := cli.TryFetchPrivacySettings(ctx, true) + if err != nil { + return nil, err + } + return privacySettingsToResponse(settings, linkPreviewsDisabled), nil +} diff --git a/src/server/session.go b/src/server/session.go index b62fd85..039e77d 100644 --- a/src/server/session.go +++ b/src/server/session.go @@ -85,6 +85,7 @@ func (s *Server) RequestCode(ctx context.Context, req *__.PairCodeRequest) (*__. return nil, err } code, err := cli.PairPhone( + ctx, req.GetPhone(), true, whatsmeow.PairClientChrome, @@ -101,7 +102,7 @@ func (s *Server) Logout(ctx context.Context, req *__.Session) (*__.Empty, error) if err != nil { return nil, err } - err = cli.Logout() + err = cli.Logout(ctx) if err != nil { if errors.Is(err, whatsmeow.ErrNotLoggedIn) { // Ignore not logged in error diff --git a/src/server/status.go b/src/server/status.go new file mode 100644 index 0000000..e04ecd1 --- /dev/null +++ b/src/server/status.go @@ -0,0 +1,41 @@ +package server + +import ( + "context" + "strings" + "time" + + __ "github.com/devlikeapro/gows/proto" + "go.mau.fi/whatsmeow/types" +) + +func parseStatusMessageID(statusID string) string { + parts := strings.Split(statusID, "_") + if len(parts) >= 3 { + return parts[2] + } + return statusID +} + +func (s *Server) ReadStatus(ctx context.Context, req *__.ReadStatusRequest) (*__.Empty, error) { + cli, err := s.Sm.Get(req.GetSession().GetId()) + if err != nil { + return nil, err + } + contact, err := types.ParseJID(req.GetContactId()) + if err != nil { + return nil, err + } + messageID := parseStatusMessageID(req.GetStatusId()) + err = cli.MarkRead( + ctx, + []types.MessageID{messageID}, + time.Now(), + types.StatusBroadcastJID, + contact, + ) + if err != nil { + return nil, err + } + return &__.Empty{}, nil +}