Update dependencies (#1929)
This commit is contained in:
5286
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
vendored
5286
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
vendored
File diff suppressed because it is too large
Load Diff
BIN
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
vendored
BIN
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
vendored
Binary file not shown.
203
vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
vendored
203
vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
vendored
@@ -48,6 +48,12 @@ message DeviceProps {
|
||||
CATALINA = 12;
|
||||
TCL_TV = 13;
|
||||
}
|
||||
message HistorySyncConfig {
|
||||
optional uint32 fullSyncDaysLimit = 1;
|
||||
optional uint32 fullSyncSizeMbLimit = 2;
|
||||
optional uint32 storageQuotaMb = 3;
|
||||
}
|
||||
|
||||
message AppVersion {
|
||||
optional uint32 primary = 1;
|
||||
optional uint32 secondary = 2;
|
||||
@@ -60,37 +66,48 @@ message DeviceProps {
|
||||
optional AppVersion version = 2;
|
||||
optional PlatformType platformType = 3;
|
||||
optional bool requireFullSync = 4;
|
||||
optional HistorySyncConfig historySyncConfig = 5;
|
||||
}
|
||||
|
||||
message PollVoteMessage {
|
||||
repeated bytes selectedOptions = 1;
|
||||
enum PeerDataOperationRequestType {
|
||||
UPLOAD_STICKER = 0;
|
||||
SEND_RECENT_STICKER_BOOTSTRAP = 1;
|
||||
GENERATE_LINK_PREVIEW = 2;
|
||||
}
|
||||
|
||||
message PollUpdateMessage {
|
||||
optional MessageKey pollCreationMessageKey = 1;
|
||||
optional PollEncValue vote = 2;
|
||||
optional PollUpdateMessageMetadata metadata = 3;
|
||||
optional int64 senderTimestampMs = 4;
|
||||
}
|
||||
|
||||
message PollUpdateMessageMetadata {
|
||||
}
|
||||
|
||||
message PollEncValue {
|
||||
optional bytes encPayload = 1;
|
||||
optional bytes encIv = 2;
|
||||
}
|
||||
|
||||
message PollCreationMessage {
|
||||
message Option {
|
||||
optional string optionName = 1;
|
||||
message PeerDataOperationRequestResponseMessage {
|
||||
message PeerDataOperationResult {
|
||||
message LinkPreviewResponse {
|
||||
optional string url = 1;
|
||||
optional string title = 2;
|
||||
optional string description = 3;
|
||||
optional bytes thumbData = 4;
|
||||
optional string canonicalUrl = 5;
|
||||
optional string matchText = 6;
|
||||
optional string previewType = 7;
|
||||
}
|
||||
|
||||
optional MediaRetryNotification.ResultType mediaUploadResult = 1;
|
||||
optional StickerMessage stickerMessage = 2;
|
||||
optional LinkPreviewResponse linkPreviewResponse = 3;
|
||||
}
|
||||
|
||||
optional bytes encKey = 1;
|
||||
optional string name = 2;
|
||||
repeated Option options = 3;
|
||||
optional uint32 selectableOptionsCount = 4;
|
||||
optional ContextInfo contextInfo = 5;
|
||||
optional PeerDataOperationRequestType peerDataOperationRequestType = 1;
|
||||
optional string stanzaId = 2;
|
||||
repeated PeerDataOperationResult peerDataOperationResult = 3;
|
||||
}
|
||||
|
||||
message PeerDataOperationRequestMessage {
|
||||
message RequestUrlPreview {
|
||||
optional string url = 1;
|
||||
}
|
||||
|
||||
message RequestStickerReupload {
|
||||
optional string fileSha256 = 1;
|
||||
}
|
||||
|
||||
optional PeerDataOperationRequestType peerDataOperationRequestType = 1;
|
||||
repeated RequestStickerReupload requestStickerReupload = 2;
|
||||
repeated RequestUrlPreview requestUrlPreview = 3;
|
||||
}
|
||||
|
||||
message PaymentInviteMessage {
|
||||
@@ -369,6 +386,7 @@ message HistorySyncNotification {
|
||||
optional uint32 chunkOrder = 7;
|
||||
optional string originalMessageId = 8;
|
||||
optional uint32 progress = 9;
|
||||
optional int64 oldestMsgInChunkTimestampSec = 10;
|
||||
}
|
||||
|
||||
message HighlyStructuredMessage {
|
||||
@@ -491,6 +509,13 @@ message ExtendedTextMessage {
|
||||
optional string inviteLinkParentGroupSubjectV2 = 27;
|
||||
optional bytes inviteLinkParentGroupThumbnailV2 = 28;
|
||||
optional InviteLinkGroupType inviteLinkGroupTypeV2 = 29;
|
||||
optional bool viewOnce = 30;
|
||||
}
|
||||
|
||||
message EncReactionMessage {
|
||||
optional MessageKey targetMessageKey = 1;
|
||||
optional bytes encPayload = 2;
|
||||
optional bytes encIv = 3;
|
||||
}
|
||||
|
||||
message DocumentMessage {
|
||||
@@ -626,6 +651,7 @@ message AudioMessage {
|
||||
optional ContextInfo contextInfo = 17;
|
||||
optional bytes streamingSidecar = 18;
|
||||
optional bytes waveform = 19;
|
||||
optional fixed32 backgroundArgb = 20;
|
||||
}
|
||||
|
||||
message AppStateSyncKey {
|
||||
@@ -741,6 +767,7 @@ message ContextInfo {
|
||||
optional bool containsAutoReply = 10;
|
||||
optional bool renderLargerThumbnail = 11;
|
||||
optional bool showAdAttribution = 12;
|
||||
optional string ctwaClid = 13;
|
||||
}
|
||||
|
||||
message AdReplyInfo {
|
||||
@@ -778,6 +805,8 @@ message ContextInfo {
|
||||
optional ActionLink actionLink = 33;
|
||||
optional string groupSubject = 34;
|
||||
optional string parentGroupJid = 35;
|
||||
optional string trustBannerType = 37;
|
||||
optional uint32 trustBannerAction = 38;
|
||||
}
|
||||
|
||||
message ActionLink {
|
||||
@@ -894,6 +923,9 @@ message Message {
|
||||
optional FutureProofMessage documentWithCaptionMessage = 53;
|
||||
optional RequestPhoneNumberMessage requestPhoneNumberMessage = 54;
|
||||
optional FutureProofMessage viewOnceMessageV2 = 55;
|
||||
optional EncReactionMessage encReactionMessage = 56;
|
||||
optional FutureProofMessage editedMessage = 58;
|
||||
optional FutureProofMessage viewOnceMessageV2Extension = 59;
|
||||
}
|
||||
|
||||
message MessageContextInfo {
|
||||
@@ -964,9 +996,11 @@ message TemplateMessage {
|
||||
|
||||
optional ContextInfo contextInfo = 3;
|
||||
optional HydratedFourRowTemplate hydratedTemplate = 4;
|
||||
optional string templateId = 9;
|
||||
oneof format {
|
||||
FourRowTemplate fourRowTemplate = 1;
|
||||
HydratedFourRowTemplate hydratedFourRowTemplate = 2;
|
||||
InteractiveMessage interactiveMessageTemplate = 5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,6 +1033,8 @@ message StickerMessage {
|
||||
optional bool isAnimated = 13;
|
||||
optional bytes pngThumbnail = 16;
|
||||
optional ContextInfo contextInfo = 17;
|
||||
optional int64 stickerSentTs = 18;
|
||||
optional bool isAvatar = 19;
|
||||
}
|
||||
|
||||
message SenderKeyDistributionMessage {
|
||||
@@ -1012,11 +1048,6 @@ message SendPaymentMessage {
|
||||
optional PaymentBackground background = 4;
|
||||
}
|
||||
|
||||
enum RmrSource {
|
||||
FAVORITE_STICKER = 0;
|
||||
RECENT_STICKER = 1;
|
||||
RECENT_STICKER_INIT = 2;
|
||||
}
|
||||
message RequestPhoneNumberMessage {
|
||||
optional ContextInfo contextInfo = 1;
|
||||
}
|
||||
@@ -1031,23 +1062,6 @@ message RequestPaymentMessage {
|
||||
optional PaymentBackground background = 7;
|
||||
}
|
||||
|
||||
message RequestMediaUploadResponseMessage {
|
||||
message RequestMediaUploadResult {
|
||||
optional string fileSha256 = 1;
|
||||
optional MediaRetryNotification.ResultType mediaUploadResult = 2;
|
||||
optional StickerMessage stickerMessage = 3;
|
||||
}
|
||||
|
||||
optional RmrSource rmrSource = 1;
|
||||
optional string stanzaId = 2;
|
||||
repeated RequestMediaUploadResult reuploadResult = 3;
|
||||
}
|
||||
|
||||
message RequestMediaUploadMessage {
|
||||
repeated string fileSha256 = 1;
|
||||
optional RmrSource rmrSource = 2;
|
||||
}
|
||||
|
||||
message ReactionMessage {
|
||||
optional MessageKey key = 1;
|
||||
optional string text = 2;
|
||||
@@ -1067,8 +1081,9 @@ message ProtocolMessage {
|
||||
INITIAL_SECURITY_NOTIFICATION_SETTING_SYNC = 9;
|
||||
APP_STATE_FATAL_EXCEPTION_NOTIFICATION = 10;
|
||||
SHARE_PHONE_NUMBER = 11;
|
||||
REQUEST_MEDIA_UPLOAD_MESSAGE = 12;
|
||||
REQUEST_MEDIA_UPLOAD_RESPONSE_MESSAGE = 13;
|
||||
MESSAGE_EDIT = 14;
|
||||
PEER_DATA_OPERATION_REQUEST_MESSAGE = 16;
|
||||
PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE = 17;
|
||||
}
|
||||
optional MessageKey key = 1;
|
||||
optional Type type = 2;
|
||||
@@ -1080,8 +1095,10 @@ message ProtocolMessage {
|
||||
optional InitialSecurityNotificationSettingSync initialSecurityNotificationSettingSync = 9;
|
||||
optional AppStateFatalExceptionNotification appStateFatalExceptionNotification = 10;
|
||||
optional DisappearingMode disappearingMode = 11;
|
||||
optional RequestMediaUploadMessage requestMediaUploadMessage = 12;
|
||||
optional RequestMediaUploadResponseMessage requestMediaUploadResponseMessage = 13;
|
||||
optional Message editedMessage = 14;
|
||||
optional int64 timestampMs = 15;
|
||||
optional PeerDataOperationRequestMessage peerDataOperationRequestMessage = 16;
|
||||
optional PeerDataOperationRequestResponseMessage peerDataOperationRequestResponseMessage = 17;
|
||||
}
|
||||
|
||||
message ProductMessage {
|
||||
@@ -1113,6 +1130,37 @@ message ProductMessage {
|
||||
optional ContextInfo contextInfo = 17;
|
||||
}
|
||||
|
||||
message PollVoteMessage {
|
||||
repeated bytes selectedOptions = 1;
|
||||
}
|
||||
|
||||
message PollUpdateMessage {
|
||||
optional MessageKey pollCreationMessageKey = 1;
|
||||
optional PollEncValue vote = 2;
|
||||
optional PollUpdateMessageMetadata metadata = 3;
|
||||
optional int64 senderTimestampMs = 4;
|
||||
}
|
||||
|
||||
message PollUpdateMessageMetadata {
|
||||
}
|
||||
|
||||
message PollEncValue {
|
||||
optional bytes encPayload = 1;
|
||||
optional bytes encIv = 2;
|
||||
}
|
||||
|
||||
message PollCreationMessage {
|
||||
message Option {
|
||||
optional string optionName = 1;
|
||||
}
|
||||
|
||||
optional bytes encKey = 1;
|
||||
optional string name = 2;
|
||||
repeated Option options = 3;
|
||||
optional uint32 selectableOptionsCount = 4;
|
||||
optional ContextInfo contextInfo = 5;
|
||||
}
|
||||
|
||||
message EphemeralSetting {
|
||||
optional sfixed32 duration = 1;
|
||||
optional sfixed64 timestamp = 2;
|
||||
@@ -1210,6 +1258,7 @@ message GlobalSettings {
|
||||
optional bool showGroupNotificationsPreview = 8;
|
||||
optional int32 disappearingModeDuration = 9;
|
||||
optional int64 disappearingModeTimestamp = 10;
|
||||
optional AvatarUserSettings avatarUserSettings = 11;
|
||||
}
|
||||
|
||||
message Conversation {
|
||||
@@ -1256,7 +1305,14 @@ message Conversation {
|
||||
optional string parentGroupId = 37;
|
||||
optional string displayName = 38;
|
||||
optional string pnJid = 39;
|
||||
optional bool selfPnExposed = 40;
|
||||
optional bool shareOwnPn = 40;
|
||||
optional bool pnhDuplicateLidThread = 41;
|
||||
optional string lidJid = 42;
|
||||
}
|
||||
|
||||
message AvatarUserSettings {
|
||||
optional string fbid = 1;
|
||||
optional string password = 2;
|
||||
}
|
||||
|
||||
message AutoDownloadSettings {
|
||||
@@ -1300,10 +1356,13 @@ message MsgOpaqueData {
|
||||
repeated PollOption pollOptions = 18;
|
||||
optional uint32 pollSelectableOptionsCount = 20;
|
||||
optional bytes messageSecret = 21;
|
||||
optional string originalSelfAuthorJid = 51;
|
||||
optional string originalSelfAuthor = 51;
|
||||
optional int64 senderTimestampMs = 22;
|
||||
optional string pollUpdateParentKey = 23;
|
||||
optional PollEncValue encPollVote = 24;
|
||||
optional string encReactionTargetMessageKey = 25;
|
||||
optional bytes encReactionEncPayload = 26;
|
||||
optional bytes encReactionEncIv = 27;
|
||||
}
|
||||
|
||||
message ServerErrorReceipt {
|
||||
@@ -1433,6 +1492,9 @@ message SyncActionValue {
|
||||
optional NuxAction nuxAction = 31;
|
||||
optional PrimaryVersionAction primaryVersionAction = 32;
|
||||
optional StickerAction stickerAction = 33;
|
||||
optional RemoveRecentStickerAction removeRecentStickerAction = 34;
|
||||
optional ChatAssignmentAction chatAssignment = 35;
|
||||
optional ChatAssignmentOpenedStatusAction chatAssignmentOpenedStatus = 36;
|
||||
}
|
||||
|
||||
message UserStatusMuteAction {
|
||||
@@ -1524,6 +1586,7 @@ message NuxAction {
|
||||
message MuteAction {
|
||||
optional bool muted = 1;
|
||||
optional int64 muteEndTimestamp = 2;
|
||||
optional bool autoMuted = 3;
|
||||
}
|
||||
|
||||
message MarkChatAsReadAction {
|
||||
@@ -1562,12 +1625,21 @@ message DeleteChatAction {
|
||||
message ContactAction {
|
||||
optional string fullName = 1;
|
||||
optional string firstName = 2;
|
||||
optional string lidJid = 3;
|
||||
}
|
||||
|
||||
message ClearChatAction {
|
||||
optional SyncActionMessageRange messageRange = 1;
|
||||
}
|
||||
|
||||
message ChatAssignmentOpenedStatusAction {
|
||||
optional bool chatOpened = 1;
|
||||
}
|
||||
|
||||
message ChatAssignmentAction {
|
||||
optional string deviceAgentID = 1;
|
||||
}
|
||||
|
||||
message ArchiveChatAction {
|
||||
optional bool archived = 1;
|
||||
optional SyncActionMessageRange messageRange = 2;
|
||||
@@ -1748,6 +1820,9 @@ message ClientPayload {
|
||||
OCULUS_CALL = 26;
|
||||
MILAN = 27;
|
||||
CAPI = 28;
|
||||
WEAROS = 29;
|
||||
ARDEVICE = 30;
|
||||
VRDEVICE = 31;
|
||||
}
|
||||
message AppVersion {
|
||||
optional uint32 primary = 1;
|
||||
@@ -1829,6 +1904,11 @@ message ClientPayload {
|
||||
NETWORK_SWITCH = 4;
|
||||
PING_RECONNECT = 5;
|
||||
}
|
||||
enum BizMarketSegment {
|
||||
DEFAULT = 0;
|
||||
DEVX = 1;
|
||||
INBOX = 2;
|
||||
}
|
||||
optional uint64 username = 1;
|
||||
optional bool passive = 3;
|
||||
optional UserAgent userAgent = 5;
|
||||
@@ -1853,6 +1933,9 @@ message ClientPayload {
|
||||
optional bytes fbDeviceId = 32;
|
||||
optional bool pull = 33;
|
||||
optional bytes paddingBytes = 34;
|
||||
optional BizMarketSegment bizMarketSegment = 35;
|
||||
optional int32 yearClass = 36;
|
||||
optional int32 memClass = 37;
|
||||
}
|
||||
|
||||
message WebNotificationsInfo {
|
||||
@@ -2016,6 +2099,16 @@ message WebMessageInfo {
|
||||
COMMUNITY_PARENT_GROUP_DELETED = 149;
|
||||
COMMUNITY_LINK_PARENT_GROUP_MEMBERSHIP_APPROVAL = 150;
|
||||
GROUP_PARTICIPANT_JOINED_GROUP_AND_PARENT_GROUP = 151;
|
||||
MASKED_THREAD_CREATED = 152;
|
||||
MASKED_THREAD_UNMASKED = 153;
|
||||
BIZ_CHAT_ASSIGNMENT = 154;
|
||||
CHAT_PSA = 155;
|
||||
CHAT_POLL_CREATION_MESSAGE = 156;
|
||||
CAG_MASKED_THREAD_CREATED = 157;
|
||||
COMMUNITY_PARENT_GROUP_SUBJECT_CHANGED = 158;
|
||||
CAG_INVITE_AUTO_ADD = 159;
|
||||
BIZ_CHAT_ASSIGNMENT_UNASSIGN = 160;
|
||||
CAG_INVITE_AUTO_JOINED = 161;
|
||||
}
|
||||
enum Status {
|
||||
ERROR = 0;
|
||||
@@ -2252,6 +2345,8 @@ message KeepInChat {
|
||||
optional int64 serverTimestamp = 2;
|
||||
optional MessageKey key = 3;
|
||||
optional string deviceJid = 4;
|
||||
optional int64 clientTimestampMs = 5;
|
||||
optional int64 serverTimestampMs = 6;
|
||||
}
|
||||
|
||||
message NoiseCertificate {
|
||||
|
||||
99
vendor/go.mau.fi/whatsmeow/client.go
vendored
99
vendor/go.mau.fi/whatsmeow/client.go
vendored
@@ -135,16 +135,17 @@ const handlerQueueSize = 2048
|
||||
// The logger can be nil, it will default to a no-op logger.
|
||||
//
|
||||
// The device store must be set. A default SQL-backed implementation is available in the store/sqlstore package.
|
||||
// container, err := sqlstore.New("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// // If you want multiple sessions, remember their JIDs and use .GetDevice(jid) or .GetAllDevices() instead.
|
||||
// deviceStore, err := container.GetFirstDevice()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// client := whatsmeow.NewClient(deviceStore, nil)
|
||||
//
|
||||
// container, err := sqlstore.New("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// // If you want multiple sessions, remember their JIDs and use .GetDevice(jid) or .GetAllDevices() instead.
|
||||
// deviceStore, err := container.GetFirstDevice()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// client := whatsmeow.NewClient(deviceStore, nil)
|
||||
func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
if log == nil {
|
||||
log = waLog.Noop
|
||||
@@ -218,16 +219,18 @@ func (cli *Client) SetProxyAddress(addr string) error {
|
||||
// By default, the client will find the proxy from the https_proxy environment variable like Go's net/http does.
|
||||
//
|
||||
// To disable reading proxy info from environment variables, explicitly set the proxy to nil:
|
||||
// cli.SetProxy(nil)
|
||||
//
|
||||
// cli.SetProxy(nil)
|
||||
//
|
||||
// To use a different proxy for the websocket and media, pass a function that checks the request path or headers:
|
||||
// cli.SetProxy(func(r *http.Request) (*url.URL, error) {
|
||||
// if r.URL.Host == "web.whatsapp.com" && r.URL.Path == "/ws/chat" {
|
||||
// return websocketProxyURL, nil
|
||||
// } else {
|
||||
// return mediaProxyURL, nil
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// cli.SetProxy(func(r *http.Request) (*url.URL, error) {
|
||||
// if r.URL.Host == "web.whatsapp.com" && r.URL.Path == "/ws/chat" {
|
||||
// return websocketProxyURL, nil
|
||||
// } else {
|
||||
// return mediaProxyURL, nil
|
||||
// }
|
||||
// })
|
||||
func (cli *Client) SetProxy(proxy socket.Proxy) {
|
||||
cli.proxy = proxy
|
||||
cli.http.Transport.(*http.Transport).Proxy = proxy
|
||||
@@ -421,29 +424,31 @@ func (cli *Client) Logout() error {
|
||||
//
|
||||
// All registered event handlers will receive all events. You should use a type switch statement to
|
||||
// filter the events you want:
|
||||
// func myEventHandler(evt interface{}) {
|
||||
// switch v := evt.(type) {
|
||||
// case *events.Message:
|
||||
// fmt.Println("Received a message!")
|
||||
// case *events.Receipt:
|
||||
// fmt.Println("Received a receipt!")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func myEventHandler(evt interface{}) {
|
||||
// switch v := evt.(type) {
|
||||
// case *events.Message:
|
||||
// fmt.Println("Received a message!")
|
||||
// case *events.Receipt:
|
||||
// fmt.Println("Received a receipt!")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If you want to access the Client instance inside the event handler, the recommended way is to
|
||||
// wrap the whole handler in another struct:
|
||||
// type MyClient struct {
|
||||
// WAClient *whatsmeow.Client
|
||||
// eventHandlerID uint32
|
||||
// }
|
||||
//
|
||||
// func (mycli *MyClient) register() {
|
||||
// mycli.eventHandlerID = mycli.WAClient.AddEventHandler(mycli.myEventHandler)
|
||||
// }
|
||||
// type MyClient struct {
|
||||
// WAClient *whatsmeow.Client
|
||||
// eventHandlerID uint32
|
||||
// }
|
||||
//
|
||||
// func (mycli *MyClient) myEventHandler(evt interface{}) {
|
||||
// // Handle event and access mycli.WAClient
|
||||
// }
|
||||
// func (mycli *MyClient) register() {
|
||||
// mycli.eventHandlerID = mycli.WAClient.AddEventHandler(mycli.myEventHandler)
|
||||
// }
|
||||
//
|
||||
// func (mycli *MyClient) myEventHandler(evt interface{}) {
|
||||
// // Handle event and access mycli.WAClient
|
||||
// }
|
||||
func (cli *Client) AddEventHandler(handler EventHandler) uint32 {
|
||||
nextID := atomic.AddUint32(&nextHandlerID, 1)
|
||||
cli.eventHandlersLock.Lock()
|
||||
@@ -458,11 +463,12 @@ func (cli *Client) AddEventHandler(handler EventHandler) uint32 {
|
||||
// N.B. Do not run this directly from an event handler. That would cause a deadlock because the
|
||||
// event dispatcher holds a read lock on the event handler list, and this method wants a write lock
|
||||
// on the same list. Instead run it in a goroutine:
|
||||
// func (mycli *MyClient) myEventHandler(evt interface{}) {
|
||||
// if noLongerWantEvents {
|
||||
// go mycli.WAClient.RemoveEventHandler(mycli.eventHandlerID)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (mycli *MyClient) myEventHandler(evt interface{}) {
|
||||
// if noLongerWantEvents {
|
||||
// go mycli.WAClient.RemoveEventHandler(mycli.eventHandlerID)
|
||||
// }
|
||||
// }
|
||||
func (cli *Client) RemoveEventHandler(id uint32) bool {
|
||||
cli.eventHandlersLock.Lock()
|
||||
defer cli.eventHandlersLock.Unlock()
|
||||
@@ -575,11 +581,12 @@ func (cli *Client) dispatchEvent(evt interface{}) {
|
||||
// ParseWebMessage parses a WebMessageInfo object into *events.Message to match what real-time messages have.
|
||||
//
|
||||
// The chat JID can be found in the Conversation data:
|
||||
// chatJID, err := types.ParseJID(conv.GetId())
|
||||
// for _, historyMsg := range conv.GetMessages() {
|
||||
// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
|
||||
// yourNormalEventHandler(evt)
|
||||
// }
|
||||
//
|
||||
// chatJID, err := types.ParseJID(conv.GetId())
|
||||
// for _, historyMsg := range conv.GetMessages() {
|
||||
// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
|
||||
// yourNormalEventHandler(evt)
|
||||
// }
|
||||
func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessageInfo) (*events.Message, error) {
|
||||
info := types.MessageInfo{
|
||||
MessageSource: types.MessageSource{
|
||||
|
||||
14
vendor/go.mau.fi/whatsmeow/download.go
vendored
14
vendor/go.mau.fi/whatsmeow/download.go
vendored
@@ -151,9 +151,10 @@ func getSize(msg DownloadableMessage) int {
|
||||
// DownloadThumbnail downloads a thumbnail from a message.
|
||||
//
|
||||
// This is primarily intended for downloading link preview thumbnails, which are in ExtendedTextMessage:
|
||||
// var msg *waProto.Message
|
||||
// ...
|
||||
// thumbnailImageBytes, err := cli.DownloadThumbnail(msg.GetExtendedTextMessage())
|
||||
//
|
||||
// var msg *waProto.Message
|
||||
// ...
|
||||
// thumbnailImageBytes, err := cli.DownloadThumbnail(msg.GetExtendedTextMessage())
|
||||
func (cli *Client) DownloadThumbnail(msg DownloadableThumbnail) ([]byte, error) {
|
||||
mediaType, ok := classToThumbnailMediaType[msg.ProtoReflect().Descriptor().Name()]
|
||||
if !ok {
|
||||
@@ -173,9 +174,10 @@ func GetMediaType(msg DownloadableMessage) MediaType {
|
||||
// Download downloads the attachment from the given protobuf message.
|
||||
//
|
||||
// The attachment is a specific part of a Message protobuf struct, not the message itself, e.g.
|
||||
// var msg *waProto.Message
|
||||
// ...
|
||||
// imageData, err := cli.Download(msg.GetImageMessage())
|
||||
//
|
||||
// var msg *waProto.Message
|
||||
// ...
|
||||
// imageData, err := cli.Download(msg.GetImageMessage())
|
||||
//
|
||||
// You can also use DownloadAny to download the first non-nil sub-message.
|
||||
func (cli *Client) Download(msg DownloadableMessage) ([]byte, error) {
|
||||
|
||||
8
vendor/go.mau.fi/whatsmeow/errors.go
vendored
8
vendor/go.mau.fi/whatsmeow/errors.go
vendored
@@ -48,6 +48,8 @@ var (
|
||||
ErrInviteLinkRevoked = errors.New("that group invite link has been revoked")
|
||||
// ErrBusinessMessageLinkNotFound is returned by ResolveBusinessMessageLink if the link doesn't exist or has been revoked.
|
||||
ErrBusinessMessageLinkNotFound = errors.New("that business message link does not exist or has been revoked")
|
||||
// ErrContactQRLinkNotFound is returned by ResolveContactQRLink if the link doesn't exist or has been revoked.
|
||||
ErrContactQRLinkNotFound = errors.New("that contact QR link does not exist or has been revoked")
|
||||
// ErrInvalidImageFormat is returned by SetGroupPhoto if the given photo is not in the correct format.
|
||||
ErrInvalidImageFormat = errors.New("the given data is not a valid image")
|
||||
// ErrMediaNotAvailableOnPhone is returned by DecryptMediaRetryNotification if the given event contains error code 2.
|
||||
@@ -79,6 +81,12 @@ var (
|
||||
ErrNothingDownloadableFound = errors.New("didn't find any attachments in message")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrOriginalMessageSecretNotFound = errors.New("original message secret key not found")
|
||||
ErrNotEncryptedReactionMessage = errors.New("given message isn't an encrypted reaction message")
|
||||
ErrNotPollUpdateMessage = errors.New("given message isn't a poll update message")
|
||||
)
|
||||
|
||||
type wrappedIQError struct {
|
||||
HumanError error
|
||||
IQError error
|
||||
|
||||
48
vendor/go.mau.fi/whatsmeow/group.go
vendored
48
vendor/go.mau.fi/whatsmeow/group.go
vendored
@@ -175,16 +175,24 @@ func (cli *Client) SetGroupTopic(jid types.JID, previousID, newID, topic string)
|
||||
if newID == "" {
|
||||
newID = GenerateMessageID()
|
||||
}
|
||||
attrs := waBinary.Attrs{
|
||||
"id": newID,
|
||||
}
|
||||
if previousID != "" {
|
||||
attrs["prev"] = previousID
|
||||
}
|
||||
content := []waBinary.Node{{
|
||||
Tag: "body",
|
||||
Content: []byte(topic),
|
||||
}}
|
||||
if len(topic) == 0 {
|
||||
attrs["delete"] = "true"
|
||||
content = nil
|
||||
}
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
Tag: "description",
|
||||
Attrs: waBinary.Attrs{
|
||||
"prev": previousID,
|
||||
"id": newID,
|
||||
},
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "body",
|
||||
Content: []byte(topic),
|
||||
}},
|
||||
Tag: "description",
|
||||
Attrs: attrs,
|
||||
Content: content,
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -525,20 +533,26 @@ func (cli *Client) parseGroupChange(node *waBinary.Node) (*events.GroupInfo, err
|
||||
NameSetBy: cag.OptionalJIDOrEmpty("s_o"),
|
||||
}
|
||||
case "description":
|
||||
topicChild := child.GetChildByTag("body")
|
||||
topicBytes, ok := topicChild.Content.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("group change description has unexpected body: %s", topicChild.XMLString())
|
||||
var topicStr string
|
||||
_, isDelete := child.GetOptionalChildByTag("delete")
|
||||
if !isDelete {
|
||||
topicChild := child.GetChildByTag("body")
|
||||
topicBytes, ok := topicChild.Content.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("group change description has unexpected body: %s", topicChild.XMLString())
|
||||
}
|
||||
topicStr = string(topicBytes)
|
||||
}
|
||||
var setBy types.JID
|
||||
if evt.Sender != nil {
|
||||
setBy = *evt.Sender
|
||||
}
|
||||
evt.Topic = &types.GroupTopic{
|
||||
Topic: string(topicBytes),
|
||||
TopicID: cag.String("id"),
|
||||
TopicSetAt: evt.Timestamp,
|
||||
TopicSetBy: setBy,
|
||||
Topic: topicStr,
|
||||
TopicID: cag.String("id"),
|
||||
TopicSetAt: evt.Timestamp,
|
||||
TopicSetBy: setBy,
|
||||
TopicDeleted: isDelete,
|
||||
}
|
||||
case "announcement":
|
||||
evt.Announce = &types.GroupAnnounce{
|
||||
|
||||
73
vendor/go.mau.fi/whatsmeow/mediaretry.go
vendored
73
vendor/go.mau.fi/whatsmeow/mediaretry.go
vendored
@@ -7,8 +7,6 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
@@ -18,6 +16,7 @@ import (
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
)
|
||||
|
||||
@@ -25,18 +24,6 @@ func getMediaRetryKey(mediaKey []byte) (cipherKey []byte) {
|
||||
return hkdfutil.SHA256(mediaKey, nil, []byte("WhatsApp Media Retry Notification"), 32)
|
||||
}
|
||||
|
||||
func prepareMediaRetryGCM(mediaKey []byte) (cipher.AEAD, error) {
|
||||
block, err := aes.NewCipher(getMediaRetryKey(mediaKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize AES cipher: %w", err)
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize GCM: %w", err)
|
||||
}
|
||||
return gcm, nil
|
||||
}
|
||||
|
||||
func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphertext, iv []byte, err error) {
|
||||
receipt := &waProto.ServerErrorReceipt{
|
||||
StanzaId: proto.String(messageID),
|
||||
@@ -47,17 +34,12 @@ func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphe
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
var gcm cipher.AEAD
|
||||
gcm, err = prepareMediaRetryGCM(mediaKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
iv = make([]byte, 12)
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ciphertext = gcm.Seal(plaintext[:0], iv, plaintext, []byte(messageID))
|
||||
ciphertext, err = gcmutil.Encrypt(getMediaRetryKey(mediaKey), iv, plaintext, []byte(messageID))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -66,35 +48,35 @@ func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphe
|
||||
// This is mostly relevant when handling history syncs and getting a 404 or 410 error downloading media.
|
||||
// Rough example on how to use it (will not work out of the box, you must adjust it depending on what you need exactly):
|
||||
//
|
||||
// var mediaRetryCache map[types.MessageID]*waProto.ImageMessage
|
||||
// var mediaRetryCache map[types.MessageID]*waProto.ImageMessage
|
||||
//
|
||||
// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
|
||||
// imageMsg := evt.Message.GetImageMessage() // replace this with the part of the message you want to download
|
||||
// data, err := cli.Download(imageMsg)
|
||||
// if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
|
||||
// err = cli.SendMediaRetryReceipt(&evt.Info, imageMsg.GetMediaKey())
|
||||
// // You need to store the event data somewhere as it's necessary for handling the retry response.
|
||||
// mediaRetryCache[evt.Info.ID] = imageMsg
|
||||
// }
|
||||
// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
|
||||
// imageMsg := evt.Message.GetImageMessage() // replace this with the part of the message you want to download
|
||||
// data, err := cli.Download(imageMsg)
|
||||
// if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
|
||||
// err = cli.SendMediaRetryReceipt(&evt.Info, imageMsg.GetMediaKey())
|
||||
// // You need to store the event data somewhere as it's necessary for handling the retry response.
|
||||
// mediaRetryCache[evt.Info.ID] = imageMsg
|
||||
// }
|
||||
//
|
||||
// The response will come as an *events.MediaRetry. The response will then have to be decrypted
|
||||
// using DecryptMediaRetryNotification and the same media key passed here. If the media retry was successful,
|
||||
// the decrypted notification should contain an updated DirectPath, which can be used to download the file.
|
||||
//
|
||||
// func eventHandler(rawEvt interface{}) {
|
||||
// switch evt := rawEvt.(type) {
|
||||
// case *events.MediaRetry:
|
||||
// imageMsg := mediaRetryCache[evt.MessageID]
|
||||
// retryData, err := whatsmeow.DecryptMediaRetryNotification(evt, imageMsg.GetMediaKey())
|
||||
// if err != nil || retryData.GetResult != waProto.MediaRetryNotification_SUCCESS {
|
||||
// return
|
||||
// }
|
||||
// // Use the new path to download the attachment
|
||||
// imageMsg.DirectPath = retryData.DirectPath
|
||||
// data, err := cli.Download(imageMsg)
|
||||
// // Alternatively, you can use cli.DownloadMediaWithPath and provide the individual fields manually.
|
||||
// }
|
||||
// }
|
||||
// func eventHandler(rawEvt interface{}) {
|
||||
// switch evt := rawEvt.(type) {
|
||||
// case *events.MediaRetry:
|
||||
// imageMsg := mediaRetryCache[evt.MessageID]
|
||||
// retryData, err := whatsmeow.DecryptMediaRetryNotification(evt, imageMsg.GetMediaKey())
|
||||
// if err != nil || retryData.GetResult != waProto.MediaRetryNotification_SUCCESS {
|
||||
// return
|
||||
// }
|
||||
// // Use the new path to download the attachment
|
||||
// imageMsg.DirectPath = retryData.DirectPath
|
||||
// data, err := cli.Download(imageMsg)
|
||||
// // Alternatively, you can use cli.DownloadMediaWithPath and provide the individual fields manually.
|
||||
// }
|
||||
// }
|
||||
func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey []byte) error {
|
||||
ciphertext, iv, err := encryptMediaRetryReceipt(message.ID, mediaKey)
|
||||
if err != nil {
|
||||
@@ -136,15 +118,12 @@ func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey []
|
||||
// See Client.SendMediaRetryReceipt for more info on how to use this.
|
||||
func DecryptMediaRetryNotification(evt *events.MediaRetry, mediaKey []byte) (*waProto.MediaRetryNotification, error) {
|
||||
var notif waProto.MediaRetryNotification
|
||||
var plaintext []byte
|
||||
if evt.Error != nil && evt.Ciphertext == nil {
|
||||
if evt.Error.Code == 2 {
|
||||
return nil, ErrMediaNotAvailableOnPhone
|
||||
}
|
||||
return nil, fmt.Errorf("%w (code: %d)", ErrUnknownMediaRetryError, evt.Error.Code)
|
||||
} else if gcm, err := prepareMediaRetryGCM(mediaKey); err != nil {
|
||||
return nil, err
|
||||
} else if plaintext, err = gcm.Open(nil, evt.IV, evt.Ciphertext, []byte(evt.MessageID)); err != nil {
|
||||
} else if plaintext, err := gcmutil.Decrypt(getMediaRetryKey(mediaKey), evt.IV, evt.Ciphertext, []byte(evt.MessageID)); err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt notification: %w", err)
|
||||
} else if err = proto.Unmarshal(plaintext, ¬if); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal notification (invalid encryption key?): %w", err)
|
||||
|
||||
55
vendor/go.mau.fi/whatsmeow/message.go
vendored
55
vendor/go.mau.fi/whatsmeow/message.go
vendored
@@ -312,6 +312,8 @@ func (cli *Client) handleHistorySyncNotification(notif *waProto.HistorySyncNotif
|
||||
cli.Log.Debugf("Received history sync (type %s, chunk %d)", historySync.GetSyncType(), historySync.GetChunkOrder())
|
||||
if historySync.GetSyncType() == waProto.HistorySync_PUSH_NAME {
|
||||
go cli.handleHistoricalPushNames(historySync.GetPushnames())
|
||||
} else if len(historySync.GetConversations()) > 0 {
|
||||
go cli.storeHistoricalMessageSecrets(historySync.GetConversations())
|
||||
}
|
||||
cli.dispatchEvent(&events.HistorySync{
|
||||
Data: &historySync,
|
||||
@@ -387,10 +389,63 @@ func (cli *Client) processProtocolParts(info *types.MessageInfo, msg *waProto.Me
|
||||
cli.handleSenderKeyDistributionMessage(info.Chat, info.Sender, msg.SenderKeyDistributionMessage)
|
||||
}
|
||||
}
|
||||
// N.B. Edits are protocol messages, but they're also wrapped inside EditedMessage,
|
||||
// which is only unwrapped after processProtocolParts, so this won't trigger for edits.
|
||||
if msg.GetProtocolMessage() != nil {
|
||||
cli.handleProtocolMessage(info, msg)
|
||||
}
|
||||
if msgSecret := msg.GetMessageContextInfo().GetMessageSecret(); len(msgSecret) > 0 {
|
||||
err := cli.Store.MsgSecrets.PutMessageSecret(info.Chat, info.Sender, info.ID, msgSecret)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to store message secret key for %s: %v", info.ID, err)
|
||||
} else {
|
||||
cli.Log.Debugf("Stored message secret key for %s", info.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) storeHistoricalMessageSecrets(conversations []*waProto.Conversation) {
|
||||
var secrets []store.MessageSecretInsert
|
||||
me := cli.Store.ID.ToNonAD()
|
||||
for _, conv := range conversations {
|
||||
chatJID, _ := types.ParseJID(conv.GetId())
|
||||
if chatJID.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
for _, msg := range conv.GetMessages() {
|
||||
if secret := msg.GetMessage().GetMessageSecret(); secret != nil {
|
||||
var senderJID types.JID
|
||||
msgKey := msg.GetMessage().GetKey()
|
||||
if msgKey.GetFromMe() {
|
||||
senderJID = me
|
||||
} else if chatJID.Server == types.DefaultUserServer {
|
||||
senderJID = chatJID
|
||||
} else if msgKey.GetParticipant() != "" {
|
||||
senderJID, _ = types.ParseJID(msgKey.GetParticipant())
|
||||
} else if msg.GetMessage().GetParticipant() != "" {
|
||||
senderJID, _ = types.ParseJID(msg.GetMessage().GetParticipant())
|
||||
}
|
||||
if senderJID.IsEmpty() || msgKey.GetId() == "" {
|
||||
continue
|
||||
}
|
||||
secrets = append(secrets, store.MessageSecretInsert{
|
||||
Chat: chatJID,
|
||||
Sender: senderJID,
|
||||
ID: msgKey.GetId(),
|
||||
Secret: secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(secrets) > 0 {
|
||||
cli.Log.Debugf("Storing %d message secret keys in history sync", len(secrets))
|
||||
err := cli.Store.MsgSecrets.PutMessageSecrets(secrets)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to store message secret keys in history sync: %v", err)
|
||||
} else {
|
||||
cli.Log.Infof("Stored %d message secret keys from history sync", len(secrets))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.Message) {
|
||||
|
||||
267
vendor/go.mau.fi/whatsmeow/msgsecret.go
vendored
Normal file
267
vendor/go.mau.fi/whatsmeow/msgsecret.go
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2022 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
)
|
||||
|
||||
type MsgSecretType string
|
||||
|
||||
const (
|
||||
EncSecretPollVote MsgSecretType = "Poll Vote"
|
||||
EncSecretReaction MsgSecretType = "Enc Reaction"
|
||||
)
|
||||
|
||||
func generateMsgSecretKey(
|
||||
modificationType MsgSecretType, modificationSender types.JID,
|
||||
origMsgID types.MessageID, origMsgSender types.JID, origMsgSecret []byte,
|
||||
) ([]byte, []byte) {
|
||||
origMsgSenderStr := origMsgSender.ToNonAD().String()
|
||||
modificationSenderStr := modificationSender.ToNonAD().String()
|
||||
|
||||
useCaseSecret := make([]byte, 0, len(origMsgID)+len(origMsgSenderStr)+len(modificationSenderStr)+len(modificationType))
|
||||
useCaseSecret = append(useCaseSecret, origMsgID...)
|
||||
useCaseSecret = append(useCaseSecret, origMsgSenderStr...)
|
||||
useCaseSecret = append(useCaseSecret, modificationSenderStr...)
|
||||
useCaseSecret = append(useCaseSecret, modificationType...)
|
||||
|
||||
secretKey := hkdfutil.SHA256(origMsgSecret, nil, useCaseSecret, 32)
|
||||
additionalData := []byte(fmt.Sprintf("%s\x00%s", origMsgID, modificationSenderStr))
|
||||
|
||||
return secretKey, additionalData
|
||||
}
|
||||
|
||||
func getOrigSenderFromKey(msg *events.Message, key *waProto.MessageKey) (types.JID, error) {
|
||||
if key.GetFromMe() {
|
||||
// fromMe always means the poll and vote were sent by the same user
|
||||
return msg.Info.Sender, nil
|
||||
} else if msg.Info.Chat.Server == types.DefaultUserServer {
|
||||
sender, err := types.ParseJID(key.GetRemoteJid())
|
||||
if err != nil {
|
||||
return types.EmptyJID, fmt.Errorf("failed to parse JID %q of original message sender: %w", key.GetRemoteJid(), err)
|
||||
}
|
||||
return sender, nil
|
||||
} else {
|
||||
sender, err := types.ParseJID(key.GetParticipant())
|
||||
if sender.Server != types.DefaultUserServer {
|
||||
err = fmt.Errorf("unexpected server")
|
||||
}
|
||||
if err != nil {
|
||||
return types.EmptyJID, fmt.Errorf("failed to parse JID %q of original message sender: %w", key.GetParticipant(), err)
|
||||
}
|
||||
return sender, nil
|
||||
}
|
||||
}
|
||||
|
||||
type messageEncryptedSecret interface {
|
||||
GetEncIv() []byte
|
||||
GetEncPayload() []byte
|
||||
}
|
||||
|
||||
func (cli *Client) decryptMsgSecret(msg *events.Message, useCase MsgSecretType, encrypted messageEncryptedSecret, origMsgKey *waProto.MessageKey) ([]byte, error) {
|
||||
pollSender, err := getOrigSenderFromKey(msg, origMsgKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baseEncKey, err := cli.Store.MsgSecrets.GetMessageSecret(msg.Info.Chat, pollSender, origMsgKey.GetId())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get original message secret key: %w", err)
|
||||
} else if baseEncKey == nil {
|
||||
return nil, ErrOriginalMessageSecretNotFound
|
||||
}
|
||||
secretKey, additionalData := generateMsgSecretKey(useCase, msg.Info.Sender, origMsgKey.GetId(), pollSender, baseEncKey)
|
||||
plaintext, err := gcmutil.Decrypt(secretKey, encrypted.GetEncIv(), encrypted.GetEncPayload(), additionalData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt secret message: %w", err)
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMsgSecret(chat, origSender types.JID, origMsgID types.MessageID, useCase MsgSecretType, plaintext []byte) (ciphertext, iv []byte, err error) {
|
||||
ownID := *cli.Store.ID
|
||||
|
||||
baseEncKey, err := cli.Store.MsgSecrets.GetMessageSecret(chat, origSender, origMsgID)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get original message secret key: %w", err)
|
||||
} else if baseEncKey == nil {
|
||||
return nil, nil, ErrOriginalMessageSecretNotFound
|
||||
}
|
||||
secretKey, additionalData := generateMsgSecretKey(useCase, ownID, origMsgID, origSender, baseEncKey)
|
||||
|
||||
iv = make([]byte, 12)
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate iv: %w", err)
|
||||
}
|
||||
ciphertext, err = gcmutil.Encrypt(secretKey, iv, plaintext, additionalData)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encrypt secret message: %w", err)
|
||||
}
|
||||
return ciphertext, iv, nil
|
||||
}
|
||||
|
||||
// DecryptReaction decrypts a reaction update message. This form of reactions hasn't been rolled out yet,
|
||||
// so this function is likely not of much use.
|
||||
//
|
||||
// if evt.Message.GetEncReactionMessage() != nil {
|
||||
// reaction, err := cli.DecryptReaction(evt)
|
||||
// if err != nil {
|
||||
// fmt.Println(":(", err)
|
||||
// return
|
||||
// }
|
||||
// fmt.Printf("Reaction message: %+v\n", reaction)
|
||||
// }
|
||||
func (cli *Client) DecryptReaction(reaction *events.Message) (*waProto.ReactionMessage, error) {
|
||||
encReaction := reaction.Message.GetEncReactionMessage()
|
||||
if encReaction != nil {
|
||||
return nil, ErrNotEncryptedReactionMessage
|
||||
}
|
||||
plaintext, err := cli.decryptMsgSecret(reaction, EncSecretReaction, encReaction, encReaction.GetTargetMessageKey())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt reaction: %w", err)
|
||||
}
|
||||
var msg waProto.ReactionMessage
|
||||
err = proto.Unmarshal(plaintext, &msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode reaction protobuf: %w", err)
|
||||
}
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
// DecryptPollVote decrypts a poll update message. The vote itself includes SHA-256 hashes of the selected options.
|
||||
//
|
||||
// if evt.Message.GetPollUpdateMessage() != nil {
|
||||
// pollVote, err := cli.DecryptPollVote(evt)
|
||||
// if err != nil {
|
||||
// fmt.Println(":(", err)
|
||||
// return
|
||||
// }
|
||||
// fmt.Println("Selected hashes:")
|
||||
// for _, hash := range pollVote.GetSelectedOptions() {
|
||||
// fmt.Printf("- %X\n", hash)
|
||||
// }
|
||||
// }
|
||||
func (cli *Client) DecryptPollVote(vote *events.Message) (*waProto.PollVoteMessage, error) {
|
||||
pollUpdate := vote.Message.GetPollUpdateMessage()
|
||||
if pollUpdate == nil {
|
||||
return nil, ErrNotPollUpdateMessage
|
||||
}
|
||||
plaintext, err := cli.decryptMsgSecret(vote, EncSecretPollVote, pollUpdate.GetVote(), pollUpdate.GetPollCreationMessageKey())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt poll vote: %w", err)
|
||||
}
|
||||
var msg waProto.PollVoteMessage
|
||||
err = proto.Unmarshal(plaintext, &msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode poll vote protobuf: %w", err)
|
||||
}
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func getKeyFromInfo(msgInfo *types.MessageInfo) *waProto.MessageKey {
|
||||
creationKey := &waProto.MessageKey{
|
||||
RemoteJid: proto.String(msgInfo.Chat.String()),
|
||||
FromMe: proto.Bool(msgInfo.IsFromMe),
|
||||
Id: proto.String(msgInfo.ID),
|
||||
}
|
||||
if msgInfo.IsGroup {
|
||||
creationKey.Participant = proto.String(msgInfo.Sender.String())
|
||||
}
|
||||
return creationKey
|
||||
}
|
||||
|
||||
// HashPollOptions hashes poll option names using SHA-256 for voting.
|
||||
// This is used by BuildPollVote to convert selected option names to hashes.
|
||||
func HashPollOptions(optionNames []string) [][]byte {
|
||||
optionHashes := make([][]byte, len(optionNames))
|
||||
for i, option := range optionNames {
|
||||
optionHash := sha256.Sum256([]byte(option))
|
||||
optionHashes[i] = optionHash[:]
|
||||
}
|
||||
return optionHashes
|
||||
}
|
||||
|
||||
// BuildPollVote builds a poll vote message using the given poll message info and option names.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// For example, to vote for the first option after receiving a message event (*events.Message):
|
||||
//
|
||||
// if evt.Message.GetPollCreationMessage() != nil {
|
||||
// pollVoteMsg, err := cli.BuildPollVote(&evt.Info, []string{evt.Message.GetPollCreationMessage().GetOptions()[0].GetOptionName()})
|
||||
// if err != nil {
|
||||
// fmt.Println(":(", err)
|
||||
// return
|
||||
// }
|
||||
// resp, err := cli.SendMessage(context.Background(), evt.Info.Chat, "", pollVoteMsg)
|
||||
// }
|
||||
func (cli *Client) BuildPollVote(pollInfo *types.MessageInfo, optionNames []string) (*waProto.Message, error) {
|
||||
pollUpdate, err := cli.EncryptPollVote(pollInfo, &waProto.PollVoteMessage{
|
||||
SelectedOptions: HashPollOptions(optionNames),
|
||||
})
|
||||
return &waProto.Message{PollUpdateMessage: pollUpdate}, err
|
||||
}
|
||||
|
||||
// BuildPollCreation builds a poll creation message with the given poll name, options and maximum number of selections.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildPollCreation("meow?", []string{"yes", "no"}, 1))
|
||||
func (cli *Client) BuildPollCreation(name string, optionNames []string, selectableOptionCount int) *waProto.Message {
|
||||
msgSecret := make([]byte, 32)
|
||||
_, err := rand.Read(msgSecret)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if selectableOptionCount < 0 || selectableOptionCount > len(optionNames) {
|
||||
selectableOptionCount = 0
|
||||
}
|
||||
options := make([]*waProto.PollCreationMessage_Option, len(optionNames))
|
||||
for i, option := range optionNames {
|
||||
options[i] = &waProto.PollCreationMessage_Option{OptionName: proto.String(option)}
|
||||
}
|
||||
return &waProto.Message{
|
||||
PollCreationMessage: &waProto.PollCreationMessage{
|
||||
Name: proto.String(name),
|
||||
Options: options,
|
||||
SelectableOptionsCount: proto.Uint32(uint32(selectableOptionCount)),
|
||||
},
|
||||
MessageContextInfo: &waProto.MessageContextInfo{
|
||||
MessageSecret: msgSecret,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptPollVote encrypts a poll vote message. This is a slightly lower-level function, using BuildPollVote is recommended.
|
||||
func (cli *Client) EncryptPollVote(pollInfo *types.MessageInfo, vote *waProto.PollVoteMessage) (*waProto.PollUpdateMessage, error) {
|
||||
plaintext, err := proto.Marshal(vote)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal poll vote protobuf: %w", err)
|
||||
}
|
||||
ciphertext, iv, err := cli.encryptMsgSecret(pollInfo.Chat, pollInfo.Sender, pollInfo.ID, EncSecretPollVote, plaintext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt poll vote: %w", err)
|
||||
}
|
||||
return &waProto.PollUpdateMessage{
|
||||
PollCreationMessageKey: getKeyFromInfo(pollInfo),
|
||||
Vote: &waProto.PollEncValue{
|
||||
EncPayload: ciphertext,
|
||||
EncIv: iv,
|
||||
},
|
||||
SenderTimestampMs: proto.Int64(time.Now().UnixMilli()),
|
||||
}, nil
|
||||
}
|
||||
3
vendor/go.mau.fi/whatsmeow/presence.go
vendored
3
vendor/go.mau.fi/whatsmeow/presence.go
vendored
@@ -84,7 +84,8 @@ func (cli *Client) SendPresence(state types.Presence) error {
|
||||
//
|
||||
// Also, it seems that the WhatsApp servers require you to be online to receive presence status from other users,
|
||||
// so you should mark yourself as online before trying to use this function:
|
||||
// cli.SendPresence(types.PresenceAvailable)
|
||||
//
|
||||
// cli.SendPresence(types.PresenceAvailable)
|
||||
func (cli *Client) SubscribePresence(jid types.JID) error {
|
||||
return cli.sendNode(waBinary.Node{
|
||||
Tag: "presence",
|
||||
|
||||
3
vendor/go.mau.fi/whatsmeow/receipt.go
vendored
3
vendor/go.mau.fi/whatsmeow/receipt.go
vendored
@@ -164,7 +164,8 @@ func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, se
|
||||
// web works when it's not in the foreground.
|
||||
//
|
||||
// To mark the client as online, use
|
||||
// cli.SendPresence(types.PresenceAvailable)
|
||||
//
|
||||
// cli.SendPresence(types.PresenceAvailable)
|
||||
//
|
||||
// Note that if you turn this off (i.e. call SetForceActiveDeliveryReceipts(false)),
|
||||
// receipts will act like the client is offline until SendPresence is called again.
|
||||
|
||||
133
vendor/go.mau.fi/whatsmeow/send.go
vendored
133
vendor/go.mau.fi/whatsmeow/send.go
vendored
@@ -34,8 +34,8 @@ import (
|
||||
|
||||
// GenerateMessageID generates a random string that can be used as a message ID on WhatsApp.
|
||||
//
|
||||
// msgID := whatsmeow.GenerateMessageID()
|
||||
// cli.SendMessage(context.Background(), targetJID, msgID, &waProto.Message{...})
|
||||
// msgID := whatsmeow.GenerateMessageID()
|
||||
// cli.SendMessage(context.Background(), targetJID, msgID, &waProto.Message{...})
|
||||
func GenerateMessageID() types.MessageID {
|
||||
id := make([]byte, 8)
|
||||
_, err := rand.Read(id)
|
||||
@@ -80,9 +80,10 @@ type SendResponse struct {
|
||||
//
|
||||
// The message itself can contain anything you want (within the protobuf schema).
|
||||
// e.g. for a simple text message, use the Conversation field:
|
||||
// cli.SendMessage(context.Background(), targetJID, "", &waProto.Message{
|
||||
// Conversation: proto.String("Hello, World!"),
|
||||
// })
|
||||
//
|
||||
// cli.SendMessage(context.Background(), targetJID, "", &waProto.Message{
|
||||
// Conversation: proto.String("Hello, World!"),
|
||||
// })
|
||||
//
|
||||
// Things like replies, mentioning users and the "forwarded" flag are stored in ContextInfo,
|
||||
// which can be put in ExtendedTextMessage and any of the media message types.
|
||||
@@ -114,6 +115,14 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.Messa
|
||||
if !isPeerMessage {
|
||||
cli.addRecentMessage(to, id, message)
|
||||
}
|
||||
if message.GetMessageContextInfo().GetMessageSecret() != nil {
|
||||
err = cli.Store.MsgSecrets.PutMessageSecret(to, *cli.Store.ID, id, message.GetMessageContextInfo().GetMessageSecret())
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to store message secret key for outgoing message %s: %v", id, err)
|
||||
} else {
|
||||
cli.Log.Debugf("Stored message secret key for outgoing message %s", id)
|
||||
}
|
||||
}
|
||||
var phash string
|
||||
var data []byte
|
||||
switch to.Server {
|
||||
@@ -163,21 +172,68 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.Messa
|
||||
}
|
||||
|
||||
// RevokeMessage deletes the given message from everyone in the chat.
|
||||
// You can only revoke your own messages, and if the message is too old, then other users will ignore the deletion.
|
||||
//
|
||||
// This method will wait for the server to acknowledge the revocation message before returning.
|
||||
// The return value is the timestamp of the message from the server.
|
||||
//
|
||||
// Deprecated: This method is deprecated in favor of BuildRevoke
|
||||
func (cli *Client) RevokeMessage(chat types.JID, id types.MessageID) (SendResponse, error) {
|
||||
return cli.SendMessage(context.TODO(), chat, "", &waProto.Message{
|
||||
return cli.SendMessage(context.TODO(), chat, "", cli.BuildRevoke(chat, types.EmptyJID, id))
|
||||
}
|
||||
|
||||
// BuildRevoke builds a message revocation message using the given variables.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// To revoke your own messages, pass your JID or an empty JID as the second parameter (sender).
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildRevoke(chat, types.EmptyJID, originalMessageID)
|
||||
//
|
||||
// To revoke someone else's messages when you are group admin, pass the message sender's JID as the second parameter.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildRevoke(chat, senderJID, originalMessageID)
|
||||
func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waProto.Message {
|
||||
key := &waProto.MessageKey{
|
||||
FromMe: proto.Bool(true),
|
||||
Id: proto.String(id),
|
||||
RemoteJid: proto.String(chat.String()),
|
||||
}
|
||||
if !sender.IsEmpty() && sender.User != cli.Store.ID.User {
|
||||
key.FromMe = proto.Bool(false)
|
||||
if chat.Server != types.DefaultUserServer {
|
||||
key.Participant = proto.String(sender.ToNonAD().String())
|
||||
}
|
||||
}
|
||||
return &waProto.Message{
|
||||
ProtocolMessage: &waProto.ProtocolMessage{
|
||||
Type: waProto.ProtocolMessage_REVOKE.Enum(),
|
||||
Key: &waProto.MessageKey{
|
||||
FromMe: proto.Bool(true),
|
||||
Id: proto.String(id),
|
||||
RemoteJid: proto.String(chat.String()),
|
||||
Key: key,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildEdit builds a message edit message using the given variables.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildEdit(chat, originalMessageID, &waProto.Message{
|
||||
// Conversation: proto.String("edited message"),
|
||||
// })
|
||||
func (cli *Client) BuildEdit(chat types.JID, id types.MessageID, newContent *waProto.Message) *waProto.Message {
|
||||
return &waProto.Message{
|
||||
EditedMessage: &waProto.FutureProofMessage{
|
||||
Message: &waProto.Message{
|
||||
ProtocolMessage: &waProto.ProtocolMessage{
|
||||
Key: &waProto.MessageKey{
|
||||
FromMe: proto.Bool(true),
|
||||
Id: proto.String(id),
|
||||
RemoteJid: proto.String(chat.String()),
|
||||
},
|
||||
Type: waProto.ProtocolMessage_MESSAGE_EDIT.Enum(),
|
||||
EditedMessage: newContent,
|
||||
TimestampMs: proto.Int64(time.Now().UnixMilli()),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -362,10 +418,16 @@ func getTypeFromMessage(msg *waProto.Message) string {
|
||||
switch {
|
||||
case msg.ViewOnceMessage != nil:
|
||||
return getTypeFromMessage(msg.ViewOnceMessage.Message)
|
||||
case msg.ViewOnceMessageV2 != nil:
|
||||
return getTypeFromMessage(msg.ViewOnceMessageV2.Message)
|
||||
case msg.EphemeralMessage != nil:
|
||||
return getTypeFromMessage(msg.EphemeralMessage.Message)
|
||||
case msg.DocumentWithCaptionMessage != nil:
|
||||
return getTypeFromMessage(msg.DocumentWithCaptionMessage.Message)
|
||||
case msg.ReactionMessage != nil:
|
||||
return "reaction"
|
||||
case msg.PollCreationMessage != nil, msg.PollUpdateMessage != nil:
|
||||
return "poll"
|
||||
case msg.Conversation != nil, msg.ExtendedTextMessage != nil, msg.ProtocolMessage != nil:
|
||||
return "text"
|
||||
//TODO this requires setting mediatype in the enc nodes
|
||||
@@ -376,17 +438,36 @@ func getTypeFromMessage(msg *waProto.Message) string {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
EditAttributeEmpty = ""
|
||||
EditAttributeMessageEdit = "1"
|
||||
EditAttributeSenderRevoke = "7"
|
||||
EditAttributeAdminRevoke = "8"
|
||||
)
|
||||
|
||||
const RemoveReactionText = ""
|
||||
|
||||
func getEditAttribute(msg *waProto.Message) string {
|
||||
if msg.ProtocolMessage != nil && msg.GetProtocolMessage().GetType() == waProto.ProtocolMessage_REVOKE && msg.GetProtocolMessage().GetKey() != nil {
|
||||
if msg.GetProtocolMessage().GetKey().GetFromMe() {
|
||||
return "7"
|
||||
} else {
|
||||
return "8"
|
||||
switch {
|
||||
case msg.ProtocolMessage != nil && msg.ProtocolMessage.GetKey() != nil:
|
||||
switch msg.ProtocolMessage.GetType() {
|
||||
case waProto.ProtocolMessage_REVOKE:
|
||||
if msg.ProtocolMessage.GetKey().GetFromMe() {
|
||||
return EditAttributeSenderRevoke
|
||||
} else {
|
||||
return EditAttributeAdminRevoke
|
||||
}
|
||||
case waProto.ProtocolMessage_MESSAGE_EDIT:
|
||||
if msg.EditedMessage != nil {
|
||||
return EditAttributeMessageEdit
|
||||
}
|
||||
}
|
||||
} else if msg.ReactionMessage != nil && msg.ReactionMessage.GetText() == "" {
|
||||
return "7"
|
||||
case msg.ReactionMessage != nil && msg.ReactionMessage.GetText() == RemoveReactionText:
|
||||
return EditAttributeSenderRevoke
|
||||
case msg.KeepInChatMessage != nil && msg.KeepInChatMessage.GetKey().GetFromMe() && msg.KeepInChatMessage.GetKeepType() == waProto.KeepType_UNDO_KEEP_FOR_ALL:
|
||||
return EditAttributeSenderRevoke
|
||||
}
|
||||
return ""
|
||||
return EditAttributeEmpty
|
||||
}
|
||||
|
||||
func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, message *waProto.Message, timings *MessageDebugTimings) (*waBinary.Node, error) {
|
||||
@@ -450,6 +531,18 @@ func (cli *Client) prepareMessageNode(ctx context.Context, to types.JID, id type
|
||||
if includeIdentity {
|
||||
content = append(content, cli.makeDeviceIdentityNode())
|
||||
}
|
||||
if attrs["type"] == "poll" {
|
||||
pollType := "creation"
|
||||
if message.PollUpdateMessage != nil {
|
||||
pollType = "vote"
|
||||
}
|
||||
content = append(content, waBinary.Node{
|
||||
Tag: "meta",
|
||||
Attrs: waBinary.Attrs{
|
||||
"polltype": pollType,
|
||||
},
|
||||
})
|
||||
}
|
||||
return &waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
package socket
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
@@ -16,6 +15,8 @@ import (
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
)
|
||||
|
||||
type NoiseHandshake struct {
|
||||
@@ -29,18 +30,6 @@ func NewNoiseHandshake() *NoiseHandshake {
|
||||
return &NoiseHandshake{}
|
||||
}
|
||||
|
||||
func newCipher(key []byte) (cipher.AEAD, error) {
|
||||
aesCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aesGCM, err := cipher.NewGCM(aesCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return aesGCM, nil
|
||||
}
|
||||
|
||||
func sha256Slice(data []byte) []byte {
|
||||
hash := sha256.Sum256(data)
|
||||
return hash[:]
|
||||
@@ -55,7 +44,7 @@ func (nh *NoiseHandshake) Start(pattern string, header []byte) {
|
||||
}
|
||||
nh.salt = nh.hash
|
||||
var err error
|
||||
nh.key, err = newCipher(nh.hash)
|
||||
nh.key, err = gcmutil.Prepare(nh.hash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -88,9 +77,9 @@ func (nh *NoiseHandshake) Decrypt(ciphertext []byte) (plaintext []byte, err erro
|
||||
func (nh *NoiseHandshake) Finish(fs *FrameSocket, frameHandler FrameHandler, disconnectHandler DisconnectHandler) (*NoiseSocket, error) {
|
||||
if write, read, err := nh.extractAndExpand(nh.salt, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract final keys: %w", err)
|
||||
} else if writeKey, err := newCipher(write); err != nil {
|
||||
} else if writeKey, err := gcmutil.Prepare(write); err != nil {
|
||||
return nil, fmt.Errorf("failed to create final write cipher: %w", err)
|
||||
} else if readKey, err := newCipher(read); err != nil {
|
||||
} else if readKey, err := gcmutil.Prepare(read); err != nil {
|
||||
return nil, fmt.Errorf("failed to create final read cipher: %w", err)
|
||||
} else if ns, err := newNoiseSocket(fs, writeKey, readKey, frameHandler, disconnectHandler); err != nil {
|
||||
return nil, fmt.Errorf("failed to create noise socket: %w", err)
|
||||
@@ -114,7 +103,7 @@ func (nh *NoiseHandshake) MixIntoKey(data []byte) error {
|
||||
return fmt.Errorf("failed to extract keys for mixing: %w", err)
|
||||
}
|
||||
nh.salt = write
|
||||
nh.key, err = newCipher(read)
|
||||
nh.key, err = gcmutil.Prepare(read)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create new cipher while mixing keys: %w", err)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ func newNoiseSocket(fs *FrameSocket, writeKey, readKey cipher.AEAD, frameHandler
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) consumeFrames(ctx context.Context, frames <-chan []byte) {
|
||||
if ctx == nil {
|
||||
// ctx being nil implies the connection already closed somehow
|
||||
return
|
||||
}
|
||||
ctxDone := ctx.Done()
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -74,7 +74,7 @@ func (vc WAVersionContainer) ProtoAppVersion() *waProto.ClientPayload_UserAgent_
|
||||
}
|
||||
|
||||
// waVersion is the WhatsApp web client version
|
||||
var waVersion = WAVersionContainer{2, 2230, 10}
|
||||
var waVersion = WAVersionContainer{2, 2245, 9}
|
||||
|
||||
// waVersionHash is the md5 hash of a dot-separated waVersion
|
||||
var waVersionHash [16]byte
|
||||
|
||||
@@ -38,7 +38,8 @@ var _ store.DeviceContainer = (*Container)(nil)
|
||||
// The logger can be nil and will default to a no-op logger.
|
||||
//
|
||||
// When using SQLite, it's strongly recommended to enable foreign keys by adding `?_foreign_keys=true`:
|
||||
// container, err := sqlstore.New("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
|
||||
//
|
||||
// container, err := sqlstore.New("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on", nil)
|
||||
func New(dialect, address string, log waLog.Logger) (*Container, error) {
|
||||
db, err := sql.Open(dialect, address)
|
||||
if err != nil {
|
||||
@@ -59,11 +60,12 @@ func New(dialect, address string, log waLog.Logger) (*Container, error) {
|
||||
// The logger can be nil and will default to a no-op logger.
|
||||
//
|
||||
// When using SQLite, it's strongly recommended to enable foreign keys by adding `?_foreign_keys=true`:
|
||||
// db, err := sql.Open("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// container, err := sqlstore.NewWithDB(db, "sqlite3", nil)
|
||||
//
|
||||
// db, err := sql.Open("sqlite3", "file:yoursqlitefile.db?_foreign_keys=on")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// container, err := sqlstore.NewWithDB(db, "sqlite3", nil)
|
||||
func NewWithDB(db *sql.DB, dialect string, log waLog.Logger) *Container {
|
||||
if log == nil {
|
||||
log = waLog.Noop
|
||||
@@ -123,6 +125,7 @@ func (c *Container) scanDevice(row scannable) (*store.Device, error) {
|
||||
device.AppState = innerStore
|
||||
device.Contacts = innerStore
|
||||
device.ChatSettings = innerStore
|
||||
device.MsgSecrets = innerStore
|
||||
device.Container = c
|
||||
device.Initialized = true
|
||||
|
||||
@@ -236,6 +239,7 @@ func (c *Container) PutDevice(device *store.Device) error {
|
||||
device.AppState = innerStore
|
||||
device.Contacts = innerStore
|
||||
device.ChatSettings = innerStore
|
||||
device.MsgSecrets = innerStore
|
||||
device.Initialized = true
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -28,7 +28,8 @@ var ErrInvalidLength = errors.New("database returned byte array with illegal len
|
||||
// PostgresArrayWrapper is a function to wrap array values before passing them to the sql package.
|
||||
//
|
||||
// When using github.com/lib/pq, you should set
|
||||
// whatsmeow.PostgresArrayWrapper = pq.Array
|
||||
//
|
||||
// whatsmeow.PostgresArrayWrapper = pq.Array
|
||||
var PostgresArrayWrapper func(interface{}) interface {
|
||||
driver.Valuer
|
||||
sql.Scanner
|
||||
@@ -687,3 +688,42 @@ func (s *SQLStore) GetChatSettings(chat types.JID) (settings types.LocalChatSett
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
putMsgSecret = `
|
||||
INSERT INTO whatsmeow_message_secrets (our_jid, chat_jid, sender_jid, message_id, key)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (our_jid, chat_jid, sender_jid, message_id) DO NOTHING
|
||||
`
|
||||
getMsgSecret = `
|
||||
SELECT key FROM whatsmeow_message_secrets WHERE our_jid=$1 AND chat_jid=$2 AND sender_jid=$3 AND message_id=$4
|
||||
`
|
||||
)
|
||||
|
||||
func (s *SQLStore) PutMessageSecrets(inserts []store.MessageSecretInsert) (err error) {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
for _, insert := range inserts {
|
||||
_, err = s.db.Exec(putMsgSecret, s.JID, insert.Chat.ToNonAD(), insert.Sender.ToNonAD(), insert.ID, insert.Secret)
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SQLStore) PutMessageSecret(chat, sender types.JID, id types.MessageID, secret []byte) (err error) {
|
||||
_, err = s.db.Exec(putMsgSecret, s.JID, chat.ToNonAD(), sender.ToNonAD(), id, secret)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetMessageSecret(chat, sender types.JID, id types.MessageID) (secret []byte, err error) {
|
||||
err = s.db.QueryRow(getMsgSecret, s.JID, chat.ToNonAD(), sender.ToNonAD(), id).Scan(&secret)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ type upgradeFunc func(*sql.Tx, *Container) error
|
||||
//
|
||||
// This may be of use if you want to manage the database fully manually, but in most cases you
|
||||
// should just call Container.Upgrade to let the library handle everything.
|
||||
var Upgrades = [...]upgradeFunc{upgradeV1, upgradeV2}
|
||||
var Upgrades = [...]upgradeFunc{upgradeV1, upgradeV2, upgradeV3}
|
||||
|
||||
func (c *Container) getVersion() (int, error) {
|
||||
_, err := c.db.Exec("CREATE TABLE IF NOT EXISTS whatsmeow_version (version INTEGER)")
|
||||
@@ -246,3 +246,17 @@ func upgradeV2(tx *sql.Tx, container *Container) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func upgradeV3(tx *sql.Tx, container *Container) error {
|
||||
_, err := tx.Exec(`CREATE TABLE whatsmeow_message_secrets (
|
||||
our_jid TEXT,
|
||||
chat_jid TEXT,
|
||||
sender_jid TEXT,
|
||||
message_id TEXT,
|
||||
key bytea NOT NULL,
|
||||
|
||||
PRIMARY KEY (our_jid, chat_jid, sender_jid, message_id),
|
||||
FOREIGN KEY (our_jid) REFERENCES whatsmeow_device(jid) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
)`)
|
||||
return err
|
||||
}
|
||||
|
||||
14
vendor/go.mau.fi/whatsmeow/store/store.go
vendored
14
vendor/go.mau.fi/whatsmeow/store/store.go
vendored
@@ -99,6 +99,19 @@ type DeviceContainer interface {
|
||||
DeleteDevice(store *Device) error
|
||||
}
|
||||
|
||||
type MessageSecretInsert struct {
|
||||
Chat types.JID
|
||||
Sender types.JID
|
||||
ID types.MessageID
|
||||
Secret []byte
|
||||
}
|
||||
|
||||
type MsgSecretStore interface {
|
||||
PutMessageSecrets([]MessageSecretInsert) error
|
||||
PutMessageSecret(chat, sender types.JID, id types.MessageID, secret []byte) error
|
||||
GetMessageSecret(chat, sender types.JID, id types.MessageID) ([]byte, error)
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
Log waLog.Logger
|
||||
|
||||
@@ -123,6 +136,7 @@ type Device struct {
|
||||
AppState AppStateStore
|
||||
Contacts ContactStore
|
||||
ChatSettings ChatSettingsStore
|
||||
MsgSecrets MsgSecretStore
|
||||
Container DeviceContainer
|
||||
|
||||
DatabaseErrorHandler func(device *Device, action string, attemptIndex int, err error) (retry bool)
|
||||
|
||||
@@ -217,6 +217,7 @@ type Message struct {
|
||||
IsViewOnce bool // True if the message was unwrapped from a ViewOnceMessage or ViewOnceMessageV2
|
||||
IsViewOnceV2 bool // True if the message was unwrapped from a ViewOnceMessage
|
||||
IsDocumentWithCaption bool // True if the message was unwrapped from a DocumentWithCaptionMessage
|
||||
IsEdit bool // True if the message was unwrapped from an EditedMessage
|
||||
|
||||
// The raw message struct. This is the raw unmodified data, which means the actual message might
|
||||
// be wrapped in DeviceSentMessage, EphemeralMessage or ViewOnceMessage.
|
||||
@@ -250,6 +251,10 @@ func (evt *Message) UnwrapRaw() *Message {
|
||||
evt.Message = evt.Message.GetDocumentWithCaptionMessage().GetMessage()
|
||||
evt.IsDocumentWithCaption = true
|
||||
}
|
||||
if evt.Message.GetEditedMessage().GetMessage() != nil {
|
||||
evt.Message = evt.Message.GetEditedMessage().GetMessage()
|
||||
evt.IsEdit = true
|
||||
}
|
||||
return evt
|
||||
}
|
||||
|
||||
@@ -259,12 +264,20 @@ type ReceiptType string
|
||||
const (
|
||||
// ReceiptTypeDelivered means the message was delivered to the device (but the user might not have noticed).
|
||||
ReceiptTypeDelivered ReceiptType = ""
|
||||
// ReceiptTypeSender is sent by your other devices when a message you sent is delivered to them.
|
||||
ReceiptTypeSender ReceiptType = "sender"
|
||||
// ReceiptTypeRetry means the message was delivered to the device, but decrypting the message failed.
|
||||
ReceiptTypeRetry ReceiptType = "retry"
|
||||
// ReceiptTypeRead means the user opened the chat and saw the message.
|
||||
ReceiptTypeRead ReceiptType = "read"
|
||||
// ReceiptTypeReadSelf means the current user read a message from a different device, and has read receipts disabled in privacy settings.
|
||||
ReceiptTypeReadSelf ReceiptType = "read-self"
|
||||
// ReceiptTypePlayed means the user opened a view-once media message.
|
||||
//
|
||||
// This is dispatched for both incoming and outgoing messages when played. If the current user opened the media,
|
||||
// it means the media should be removed from all devices. If a recipient opened the media, it's just a notification
|
||||
// for the sender that the media was viewed.
|
||||
ReceiptTypePlayed ReceiptType = "played"
|
||||
)
|
||||
|
||||
// GoString returns the name of the Go constant for the ReceiptType value.
|
||||
@@ -276,6 +289,8 @@ func (rt ReceiptType) GoString() string {
|
||||
return "events.ReceiptTypeReadSelf"
|
||||
case ReceiptTypeDelivered:
|
||||
return "events.ReceiptTypeDelivered"
|
||||
case ReceiptTypePlayed:
|
||||
return "events.ReceiptTypePlayed"
|
||||
default:
|
||||
return fmt.Sprintf("events.ReceiptType(%#v)", string(rt))
|
||||
}
|
||||
@@ -294,7 +309,8 @@ type Receipt struct {
|
||||
// ChatPresence is emitted when a chat state update (also known as typing notification) is received.
|
||||
//
|
||||
// Note that WhatsApp won't send you these updates unless you mark yourself as online:
|
||||
// client.SendPresence(types.PresenceAvailable)
|
||||
//
|
||||
// client.SendPresence(types.PresenceAvailable)
|
||||
type ChatPresence struct {
|
||||
types.MessageSource
|
||||
State types.ChatPresence // The current state, either composing or paused
|
||||
@@ -304,7 +320,8 @@ type ChatPresence struct {
|
||||
// Presence is emitted when a presence update is received.
|
||||
//
|
||||
// Note that WhatsApp only sends you presence updates for individual users after you subscribe to them:
|
||||
// client.SubscribePresence(user JID)
|
||||
//
|
||||
// client.SubscribePresence(user JID)
|
||||
type Presence struct {
|
||||
// The user whose presence event this is
|
||||
From types.JID
|
||||
|
||||
9
vendor/go.mau.fi/whatsmeow/types/group.go
vendored
9
vendor/go.mau.fi/whatsmeow/types/group.go
vendored
@@ -44,10 +44,11 @@ type GroupName struct {
|
||||
|
||||
// GroupTopic contains the topic (description) of a group along with metadata of who set it and when.
|
||||
type GroupTopic struct {
|
||||
Topic string
|
||||
TopicID string
|
||||
TopicSetAt time.Time
|
||||
TopicSetBy JID
|
||||
Topic string
|
||||
TopicID string
|
||||
TopicSetAt time.Time
|
||||
TopicSetBy JID
|
||||
TopicDeleted bool
|
||||
}
|
||||
|
||||
// GroupLocked specifies whether the group info can only be edited by admins.
|
||||
|
||||
7
vendor/go.mau.fi/whatsmeow/types/user.go
vendored
7
vendor/go.mau.fi/whatsmeow/types/user.go
vendored
@@ -75,6 +75,13 @@ type BusinessMessageLinkTarget struct {
|
||||
Message string // The message that WhatsApp clients will pre-fill in the input box when clicking the link.
|
||||
}
|
||||
|
||||
// ContactQRLinkTarget contains the info that is found using a contact QR link (see Client.ResolveContactQRLink)
|
||||
type ContactQRLinkTarget struct {
|
||||
JID JID // The JID of the user.
|
||||
Type string // Might always be "contact".
|
||||
PushName string // The notify / push name of the user.
|
||||
}
|
||||
|
||||
// PrivacySetting is an individual setting value in the user's privacy settings.
|
||||
type PrivacySetting string
|
||||
|
||||
|
||||
35
vendor/go.mau.fi/whatsmeow/upload.go
vendored
35
vendor/go.mau.fi/whatsmeow/upload.go
vendored
@@ -38,25 +38,26 @@ type UploadResponse struct {
|
||||
// You should copy the fields in the response to the corresponding fields in a protobuf message.
|
||||
//
|
||||
// For example, to send an image:
|
||||
// resp, err := cli.Upload(context.Background(), yourImageBytes, whatsmeow.MediaImage)
|
||||
// // handle error
|
||||
//
|
||||
// imageMsg := &waProto.ImageMessage{
|
||||
// Caption: proto.String("Hello, world!"),
|
||||
// Mimetype: proto.String("image/png"), // replace this with the actual mime type
|
||||
// // you can also optionally add other fields like ContextInfo and JpegThumbnail here
|
||||
// resp, err := cli.Upload(context.Background(), yourImageBytes, whatsmeow.MediaImage)
|
||||
// // handle error
|
||||
//
|
||||
// Url: &resp.URL,
|
||||
// DirectPath: &resp.DirectPath,
|
||||
// MediaKey: resp.MediaKey,
|
||||
// FileEncSha256: resp.FileEncSHA256,
|
||||
// FileSha256: resp.FileSha256,
|
||||
// FileLength: &resp.FileLength,
|
||||
// }
|
||||
// _, err = cli.SendMessage(context.Background(), targetJID, "", &waProto.Message{
|
||||
// ImageMessage: imageMsg,
|
||||
// })
|
||||
// // handle error again
|
||||
// imageMsg := &waProto.ImageMessage{
|
||||
// Caption: proto.String("Hello, world!"),
|
||||
// Mimetype: proto.String("image/png"), // replace this with the actual mime type
|
||||
// // you can also optionally add other fields like ContextInfo and JpegThumbnail here
|
||||
//
|
||||
// Url: &resp.URL,
|
||||
// DirectPath: &resp.DirectPath,
|
||||
// MediaKey: resp.MediaKey,
|
||||
// FileEncSha256: resp.FileEncSHA256,
|
||||
// FileSha256: resp.FileSha256,
|
||||
// FileLength: &resp.FileLength,
|
||||
// }
|
||||
// _, err = cli.SendMessage(context.Background(), targetJID, "", &waProto.Message{
|
||||
// ImageMessage: imageMsg,
|
||||
// })
|
||||
// // handle error again
|
||||
//
|
||||
// The same applies to the other message types like DocumentMessage, just replace the struct type and Message field name.
|
||||
func (cli *Client) Upload(ctx context.Context, plaintext []byte, appInfo MediaType) (resp UploadResponse, err error) {
|
||||
|
||||
70
vendor/go.mau.fi/whatsmeow/user.go
vendored
70
vendor/go.mau.fi/whatsmeow/user.go
vendored
@@ -21,7 +21,9 @@ import (
|
||||
)
|
||||
|
||||
const BusinessMessageLinkPrefix = "https://wa.me/message/"
|
||||
const ContactQRLinkPrefix = "https://wa.me/qr/"
|
||||
const BusinessMessageLinkDirectPrefix = "https://api.whatsapp.com/message/"
|
||||
const ContactQRLinkDirectPrefix = "https://api.whatsapp.com/qr/"
|
||||
|
||||
// ResolveBusinessMessageLink resolves a business message short link and returns the target JID, business name and
|
||||
// text to prefill in the input field (if any).
|
||||
@@ -34,7 +36,7 @@ func (cli *Client) ResolveBusinessMessageLink(code string) (*types.BusinessMessa
|
||||
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "w:qr",
|
||||
Type: "get",
|
||||
Type: iqGet,
|
||||
// WhatsApp android doesn't seem to have a "to" field for this one at all, not sure why but it works
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "qr",
|
||||
@@ -71,6 +73,72 @@ func (cli *Client) ResolveBusinessMessageLink(code string) (*types.BusinessMessa
|
||||
return &target, ag.Error()
|
||||
}
|
||||
|
||||
// ResolveContactQRLink resolves a link from a contact share QR code and returns the target JID and push name.
|
||||
//
|
||||
// The links look like https://wa.me/qr/<code> or https://api.whatsapp.com/qr/<code>. You can either provide
|
||||
// the full link, or just the <code> part.
|
||||
func (cli *Client) ResolveContactQRLink(code string) (*types.ContactQRLinkTarget, error) {
|
||||
code = strings.TrimPrefix(code, ContactQRLinkPrefix)
|
||||
code = strings.TrimPrefix(code, ContactQRLinkDirectPrefix)
|
||||
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "w:qr",
|
||||
Type: iqGet,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "qr",
|
||||
Attrs: waBinary.Attrs{
|
||||
"code": code,
|
||||
},
|
||||
}},
|
||||
})
|
||||
if errors.Is(err, ErrIQNotFound) {
|
||||
return nil, wrapIQError(ErrContactQRLinkNotFound, err)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qrChild, ok := resp.GetOptionalChildByTag("qr")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "qr", In: "response to contact link query"}
|
||||
}
|
||||
var target types.ContactQRLinkTarget
|
||||
ag := qrChild.AttrGetter()
|
||||
target.JID = ag.JID("jid")
|
||||
target.PushName = ag.OptionalString("notify")
|
||||
target.Type = ag.String("type")
|
||||
return &target, ag.Error()
|
||||
}
|
||||
|
||||
// GetContactQRLink gets your own contact share QR link that can be resolved using ResolveContactQRLink
|
||||
// (or scanned with the official apps when encoded as a QR code).
|
||||
//
|
||||
// If the revoke parameter is set to true, it will ask the server to revoke the previous link and generate a new one.
|
||||
func (cli *Client) GetContactQRLink(revoke bool) (string, error) {
|
||||
action := "get"
|
||||
if revoke {
|
||||
action = "revoke"
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "w:qr",
|
||||
Type: iqSet,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "qr",
|
||||
Attrs: waBinary.Attrs{
|
||||
"type": "contact",
|
||||
"action": action,
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
qrChild, ok := resp.GetOptionalChildByTag("qr")
|
||||
if !ok {
|
||||
return "", &ElementMissingError{Tag: "qr", In: "response to own contact link fetch"}
|
||||
}
|
||||
ag := qrChild.AttrGetter()
|
||||
return ag.String("code"), ag.Error()
|
||||
}
|
||||
|
||||
// SetStatusMessage updates the current user's status text, which is shown in the "About" section in the user profile.
|
||||
//
|
||||
// This is different from the ephemeral status broadcast messages. Use SendMessage to types.StatusBroadcastJID to send
|
||||
|
||||
41
vendor/go.mau.fi/whatsmeow/util/gcmutil/gcm.go
vendored
Normal file
41
vendor/go.mau.fi/whatsmeow/util/gcmutil/gcm.go
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2022 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package gcmutil
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Prepare(secretKey []byte) (gcm cipher.AEAD, err error) {
|
||||
var block cipher.Block
|
||||
if block, err = aes.NewCipher(secretKey); err != nil {
|
||||
err = fmt.Errorf("failed to initialize AES cipher: %w", err)
|
||||
} else if gcm, err = cipher.NewGCM(block); err != nil {
|
||||
err = fmt.Errorf("failed to initialize GCM: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Decrypt(secretKey, iv, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if gcm, err := Prepare(secretKey); err != nil {
|
||||
return nil, err
|
||||
} else if plaintext, decryptErr := gcm.Open(nil, iv, ciphertext, additionalData); decryptErr != nil {
|
||||
return nil, decryptErr
|
||||
} else {
|
||||
return plaintext, nil
|
||||
}
|
||||
}
|
||||
|
||||
func Encrypt(secretKey, iv, plaintext, additionalData []byte) ([]byte, error) {
|
||||
if gcm, err := Prepare(secretKey); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return gcm.Seal(nil, iv, plaintext, additionalData), nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user