├── .editorconfig ├── .gitattributes ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── appstate.go ├── appstate ├── decode.go ├── encode.go ├── errors.go ├── hash.go ├── keys.go └── lthash │ └── lthash.go ├── armadillomessage.go ├── binary ├── attrs.go ├── decoder.go ├── encoder.go ├── errors.go ├── node.go ├── proto │ ├── doc.go │ └── legacy.go ├── token │ └── token.go ├── unpack.go └── xml.go ├── broadcast.go ├── call.go ├── client.go ├── client_test.go ├── connectionevents.go ├── download-to-file.go ├── download.go ├── errors.go ├── go.mod ├── go.sum ├── group.go ├── handshake.go ├── internals.go ├── internals_generate.go ├── keepalive.go ├── mediaconn.go ├── mediaretry.go ├── message.go ├── msgsecret.go ├── newsletter.go ├── notification.go ├── pair-code.go ├── pair.go ├── prekeys.go ├── presence.go ├── privacysettings.go ├── proto ├── .gitignore ├── armadilloutil │ └── decode.go ├── extra.go ├── waAdv │ ├── WAAdv.pb.go │ ├── WAAdv.pb.raw │ └── WAAdv.proto ├── waArmadilloApplication │ ├── WAArmadilloApplication.pb.go │ ├── WAArmadilloApplication.pb.raw │ ├── WAArmadilloApplication.proto │ └── extra.go ├── waArmadilloBackupCommon │ ├── WAArmadilloBackupCommon.pb.go │ ├── WAArmadilloBackupCommon.pb.raw │ └── WAArmadilloBackupCommon.proto ├── waArmadilloBackupMessage │ ├── WAArmadilloBackupMessage.pb.go │ ├── WAArmadilloBackupMessage.pb.raw │ └── WAArmadilloBackupMessage.proto ├── waArmadilloICDC │ ├── WAArmadilloICDC.pb.go │ ├── WAArmadilloICDC.pb.raw │ └── WAArmadilloICDC.proto ├── waArmadilloTransportEvent │ ├── WAArmadilloTransportEvent.pb.go │ ├── WAArmadilloTransportEvent.pb.raw │ └── WAArmadilloTransportEvent.proto ├── waArmadilloXMA │ ├── WAArmadilloXMA.pb.go │ ├── WAArmadilloXMA.pb.raw │ └── WAArmadilloXMA.proto ├── waCert │ ├── WACert.pb.go │ ├── WACert.pb.raw │ └── WACert.proto ├── waChatLockSettings │ ├── WAProtobufsChatLockSettings.pb.go │ ├── WAProtobufsChatLockSettings.pb.raw │ └── WAProtobufsChatLockSettings.proto ├── waCommon │ ├── WACommon.pb.go │ ├── WACommon.pb.raw │ ├── WACommon.proto │ └── legacy.go ├── waCompanionReg │ ├── WACompanionReg.pb.go │ ├── WACompanionReg.pb.raw │ └── WACompanionReg.proto ├── waConsumerApplication │ ├── WAConsumerApplication.pb.go │ ├── WAConsumerApplication.pb.raw │ ├── WAConsumerApplication.proto │ └── extra.go ├── waDeviceCapabilities │ ├── WAProtobufsDeviceCapabilities.pb.go │ ├── WAProtobufsDeviceCapabilities.pb.raw │ └── WAProtobufsDeviceCapabilities.proto ├── waE2E │ ├── WAWebProtobufsE2E.pb.go │ ├── WAWebProtobufsE2E.pb.raw │ ├── WAWebProtobufsE2E.proto │ └── legacy.go ├── waEphemeral │ ├── WAWebProtobufsEphemeral.pb.go │ ├── WAWebProtobufsEphemeral.pb.raw │ └── WAWebProtobufsEphemeral.proto ├── waFingerprint │ ├── WAFingerprint.pb.go │ ├── WAFingerprint.pb.raw │ └── WAFingerprint.proto ├── waHistorySync │ ├── WAWebProtobufsHistorySync.pb.go │ ├── WAWebProtobufsHistorySync.pb.raw │ ├── WAWebProtobufsHistorySync.proto │ └── legacy.go ├── waLidMigrationSyncPayload │ ├── WAWebProtobufLidMigrationSyncPayload.pb.go │ ├── WAWebProtobufLidMigrationSyncPayload.pb.raw │ └── WAWebProtobufLidMigrationSyncPayload.proto ├── waMediaEntryData │ ├── WAMediaEntryData.pb.go │ ├── WAMediaEntryData.pb.raw │ └── WAMediaEntryData.proto ├── waMediaTransport │ ├── WAMediaTransport.pb.go │ ├── WAMediaTransport.pb.raw │ └── WAMediaTransport.proto ├── waMmsRetry │ ├── WAMmsRetry.pb.go │ ├── WAMmsRetry.pb.raw │ └── WAMmsRetry.proto ├── waMsgApplication │ ├── WAMsgApplication.pb.go │ ├── WAMsgApplication.pb.raw │ ├── WAMsgApplication.proto │ └── extra.go ├── waMsgTransport │ ├── WAMsgTransport.pb.go │ ├── WAMsgTransport.pb.raw │ ├── WAMsgTransport.proto │ └── extra.go ├── waMultiDevice │ ├── WAMultiDevice.pb.go │ ├── WAMultiDevice.pb.raw │ ├── WAMultiDevice.proto │ └── extra.go ├── waQuickPromotionSurfaces │ ├── WAWebProtobufsQuickPromotionSurfaces.pb.go │ ├── WAWebProtobufsQuickPromotionSurfaces.pb.raw │ └── WAWebProtobufsQuickPromotionSurfaces.proto ├── waReporting │ ├── WAWebProtobufsReporting.pb.go │ ├── WAWebProtobufsReporting.pb.raw │ └── WAWebProtobufsReporting.proto ├── waRoutingInfo │ ├── WAWebProtobufsRoutingInfo.pb.go │ ├── WAWebProtobufsRoutingInfo.pb.raw │ └── WAWebProtobufsRoutingInfo.proto ├── waServerSync │ ├── WAServerSync.pb.go │ ├── WAServerSync.pb.raw │ ├── WAServerSync.proto │ └── legacy.go ├── waSyncAction │ ├── WASyncAction.pb.go │ ├── WASyncAction.pb.raw │ └── WASyncAction.proto ├── waUserPassword │ ├── WAProtobufsUserPassword.pb.go │ ├── WAProtobufsUserPassword.pb.raw │ └── WAProtobufsUserPassword.proto ├── waVnameCert │ ├── WAWebProtobufsVnameCert.pb.go │ ├── WAWebProtobufsVnameCert.pb.raw │ └── WAWebProtobufsVnameCert.proto ├── waWa6 │ ├── WAWebProtobufsWa6.pb.go │ ├── WAWebProtobufsWa6.pb.raw │ └── WAWebProtobufsWa6.proto ├── waWeb │ ├── WAWebProtobufsWeb.pb.go │ ├── WAWebProtobufsWeb.pb.raw │ ├── WAWebProtobufsWeb.proto │ └── legacy.go └── waWinUIApi │ ├── WAWinUIApi.pb.go │ ├── WAWinUIApi.pb.raw │ └── WAWinUIApi.proto ├── push.go ├── qrchan.go ├── receipt.go ├── request.go ├── retry.go ├── send.go ├── sendfb.go ├── socket ├── constants.go ├── framesocket.go ├── noisehandshake.go └── noisesocket.go ├── store ├── clientpayload.go ├── noop.go ├── signal.go ├── sqlstore │ ├── container.go │ ├── lidmap.go │ ├── store.go │ └── upgrades │ │ ├── 00-latest-schema.sql │ │ ├── 03-message-secrets.sql │ │ ├── 04-privacy-tokens.sql │ │ ├── 05-account-jid-format.sql │ │ ├── 06-facebook-uuid.sql │ │ ├── 07-account-lid.sql │ │ ├── 08-lid-mapping.sql │ │ └── upgrades.go └── store.go ├── types ├── botmap.go ├── call.go ├── events │ ├── appstate.go │ ├── call.go │ └── events.go ├── group.go ├── jid.go ├── message.go ├── newsletter.go ├── presence.go └── user.go ├── update.go ├── upload.go ├── user.go └── util ├── cbcutil └── cbc.go ├── gcmutil └── gcm.go ├── hkdfutil └── hkdf.go ├── keys └── keypair.go └── log ├── log.go └── zerolog.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{yaml,yml}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pb.go linguist-generated=true 2 | *.pb.raw binary linguist-generated=true 3 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: trailing-whitespace 6 | exclude_types: [markdown] 7 | exclude: LICENSE|def.proto 8 | - id: end-of-file-fixer 9 | exclude: LICENSE|def.proto 10 | - id: check-yaml 11 | - id: check-added-large-files 12 | 13 | - repo: https://github.com/tekwizely/pre-commit-golang 14 | rev: v1.0.0-rc.1 15 | hooks: 16 | - id: go-imports-repo 17 | args: ["-w"] 18 | - id: go-vet-repo-mod 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waSocket 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/idlethorn/waSocket.svg)](https://pkg.go.dev/github.com/idlethorn/waSocket) 3 | 4 | waSocket is a Go library for the WhatsApp web multidevice API. 5 | This is a fork that use a workaround for slow groupm essage, and some unmerged PR 6 | ## Discussion 7 | 8 | Whatsapp Group: https://chat.whatsapp.com/BDI3NMjO7vW7RlOqgdxtmw 9 | 10 | For questions about the WhatsApp protocol (like how to send a specific type of 11 | message), you can also use the [WhatsApp protocol Q&A] section on GitHub 12 | discussions. 13 | 14 | 15 | 16 | ## Usage 17 | The [godoc](https://pkg.go.dev/github.com/idlethorn/waSocket) includes docs for all methods and event types. 18 | There's also a [simple example](https://pkg.go.dev/github.com/idlethorn/waSocket#example-package) at the top. 19 | 20 | ## Features 21 | Most core features are already present: 22 | 23 | * Sending messages to private chats and groups (both text and media) 24 | * Receiving all messages 25 | * Managing groups and receiving group change events 26 | * Joining via invite messages, using and creating invite links 27 | * Sending and receiving typing notifications 28 | * Sending and receiving delivery and read receipts 29 | * Reading and writing app state (contact list, chat pin/mute status, etc) 30 | * Sending and handling retry receipts if message decryption fails 31 | * Sending status messages (experimental, may not work for large contact lists) 32 | 33 | Things that are not yet implemented: 34 | 35 | * Sending broadcast list messages (this is not supported on WhatsApp web either) 36 | * Calls 37 | -------------------------------------------------------------------------------- /appstate/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package appstate 8 | 9 | import "errors" 10 | 11 | // Errors that this package can return. 12 | var ( 13 | ErrMissingPreviousSetValueOperation = errors.New("missing value MAC of previous SET operation") 14 | ErrMismatchingLTHash = errors.New("mismatching LTHash") 15 | ErrMismatchingPatchMAC = errors.New("mismatching patch MAC") 16 | ErrMismatchingContentMAC = errors.New("mismatching content MAC") 17 | ErrMismatchingIndexMAC = errors.New("mismatching index MAC") 18 | ErrKeyNotFound = errors.New("didn't find app state key") 19 | ) 20 | -------------------------------------------------------------------------------- /appstate/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package appstate 8 | 9 | import ( 10 | "crypto/hmac" 11 | "crypto/sha256" 12 | "crypto/sha512" 13 | "encoding/binary" 14 | "fmt" 15 | "hash" 16 | 17 | "github.com/idlethorn/waSocket/appstate/lthash" 18 | "github.com/idlethorn/waSocket/proto/waServerSync" 19 | "github.com/idlethorn/waSocket/proto/waSyncAction" 20 | ) 21 | 22 | type Mutation struct { 23 | Operation waServerSync.SyncdMutation_SyncdOperation 24 | Action *waSyncAction.SyncActionValue 25 | Index []string 26 | IndexMAC []byte 27 | ValueMAC []byte 28 | } 29 | 30 | type HashState struct { 31 | Version uint64 32 | Hash [128]byte 33 | } 34 | 35 | func (hs *HashState) updateHash(mutations []*waServerSync.SyncdMutation, getPrevSetValueMAC func(indexMAC []byte, maxIndex int) ([]byte, error)) ([]error, error) { 36 | var added, removed [][]byte 37 | var warnings []error 38 | 39 | for i, mutation := range mutations { 40 | if mutation.GetOperation() == waServerSync.SyncdMutation_SET { 41 | value := mutation.GetRecord().GetValue().GetBlob() 42 | added = append(added, value[len(value)-32:]) 43 | } 44 | indexMAC := mutation.GetRecord().GetIndex().GetBlob() 45 | removal, err := getPrevSetValueMAC(indexMAC, i) 46 | if err != nil { 47 | return warnings, fmt.Errorf("failed to get value MAC of previous SET operation: %w", err) 48 | } else if removal != nil { 49 | removed = append(removed, removal) 50 | } else if mutation.GetOperation() == waServerSync.SyncdMutation_REMOVE { 51 | // TODO figure out if there are certain cases that are safe to ignore and others that aren't 52 | // At least removing contact access from WhatsApp seems to create a REMOVE op for your own JID 53 | // that points to a non-existent index and is safe to ignore here. Other keys might not be safe to ignore. 54 | warnings = append(warnings, fmt.Errorf("%w for %X", ErrMissingPreviousSetValueOperation, indexMAC)) 55 | //return ErrMissingPreviousSetValueOperation 56 | } 57 | } 58 | 59 | lthash.WAPatchIntegrity.SubtractThenAddInPlace(hs.Hash[:], removed, added) 60 | return warnings, nil 61 | } 62 | 63 | func uint64ToBytes(val uint64) []byte { 64 | data := make([]byte, 8) 65 | binary.BigEndian.PutUint64(data, val) 66 | return data 67 | } 68 | 69 | func concatAndHMAC(alg func() hash.Hash, key []byte, data ...[]byte) []byte { 70 | h := hmac.New(alg, key) 71 | for _, item := range data { 72 | h.Write(item) 73 | } 74 | return h.Sum(nil) 75 | } 76 | 77 | func (hs *HashState) generateSnapshotMAC(name WAPatchName, key []byte) []byte { 78 | return concatAndHMAC(sha256.New, key, hs.Hash[:], uint64ToBytes(hs.Version), []byte(name)) 79 | } 80 | 81 | func generatePatchMAC(patch *waServerSync.SyncdPatch, name WAPatchName, key []byte, version uint64) []byte { 82 | dataToHash := make([][]byte, len(patch.GetMutations())+3) 83 | dataToHash[0] = patch.GetSnapshotMac() 84 | for i, mutation := range patch.Mutations { 85 | val := mutation.GetRecord().GetValue().GetBlob() 86 | dataToHash[i+1] = val[len(val)-32:] 87 | } 88 | dataToHash[len(dataToHash)-2] = uint64ToBytes(version) 89 | dataToHash[len(dataToHash)-1] = []byte(name) 90 | return concatAndHMAC(sha256.New, key, dataToHash...) 91 | } 92 | 93 | func generateContentMAC(operation waServerSync.SyncdMutation_SyncdOperation, data, keyID, key []byte) []byte { 94 | operationBytes := []byte{byte(operation) + 1} 95 | keyDataLength := uint64ToBytes(uint64(len(keyID) + 1)) 96 | return concatAndHMAC(sha512.New, key, operationBytes, keyID, data, keyDataLength)[:32] 97 | } 98 | -------------------------------------------------------------------------------- /appstate/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package appstate implements encoding and decoding WhatsApp's app state patches. 8 | package appstate 9 | 10 | import ( 11 | "encoding/base64" 12 | "sync" 13 | 14 | "github.com/idlethorn/waSocket/store" 15 | "github.com/idlethorn/waSocket/util/hkdfutil" 16 | waLog "github.com/idlethorn/waSocket/util/log" 17 | ) 18 | 19 | // WAPatchName represents a type of app state patch. 20 | type WAPatchName string 21 | 22 | const ( 23 | // WAPatchCriticalBlock contains the user's settings like push name and locale. 24 | WAPatchCriticalBlock WAPatchName = "critical_block" 25 | // WAPatchCriticalUnblockLow contains the user's contact list. 26 | WAPatchCriticalUnblockLow WAPatchName = "critical_unblock_low" 27 | // WAPatchRegularLow contains some local chat settings like pin, archive status, and the setting of whether to unarchive chats when messages come in. 28 | WAPatchRegularLow WAPatchName = "regular_low" 29 | // WAPatchRegularHigh contains more local chat settings like mute status and starred messages. 30 | WAPatchRegularHigh WAPatchName = "regular_high" 31 | // WAPatchRegular contains protocol info about app state patches like key expiration. 32 | WAPatchRegular WAPatchName = "regular" 33 | ) 34 | 35 | // AllPatchNames contains all currently known patch state names. 36 | var AllPatchNames = [...]WAPatchName{WAPatchCriticalBlock, WAPatchCriticalUnblockLow, WAPatchRegularHigh, WAPatchRegular, WAPatchRegularLow} 37 | 38 | // Constants for the first part of app state indexes. 39 | const ( 40 | IndexMute = "mute" 41 | IndexPin = "pin_v1" 42 | IndexArchive = "archive" 43 | IndexContact = "contact" 44 | IndexClearChat = "clearChat" 45 | IndexDeleteChat = "deleteChat" 46 | IndexStar = "star" 47 | IndexDeleteMessageForMe = "deleteMessageForMe" 48 | IndexMarkChatAsRead = "markChatAsRead" 49 | IndexSettingPushName = "setting_pushName" 50 | IndexSettingUnarchiveChats = "setting_unarchiveChats" 51 | IndexUserStatusMute = "userStatusMute" 52 | IndexLabelEdit = "label_edit" 53 | IndexLabelAssociationChat = "label_jid" 54 | IndexLabelAssociationMessage = "label_message" 55 | ) 56 | 57 | type Processor struct { 58 | keyCache map[string]ExpandedAppStateKeys 59 | keyCacheLock sync.Mutex 60 | Store *store.Device 61 | Log waLog.Logger 62 | } 63 | 64 | func NewProcessor(store *store.Device, log waLog.Logger) *Processor { 65 | return &Processor{ 66 | keyCache: make(map[string]ExpandedAppStateKeys), 67 | Store: store, 68 | Log: log, 69 | } 70 | } 71 | 72 | type ExpandedAppStateKeys struct { 73 | Index []byte 74 | ValueEncryption []byte 75 | ValueMAC []byte 76 | SnapshotMAC []byte 77 | PatchMAC []byte 78 | } 79 | 80 | func expandAppStateKeys(keyData []byte) (keys ExpandedAppStateKeys) { 81 | appStateKeyExpanded := hkdfutil.SHA256(keyData, nil, []byte("WhatsApp Mutation Keys"), 160) 82 | return ExpandedAppStateKeys{appStateKeyExpanded[0:32], appStateKeyExpanded[32:64], appStateKeyExpanded[64:96], appStateKeyExpanded[96:128], appStateKeyExpanded[128:160]} 83 | } 84 | 85 | func (proc *Processor) getAppStateKey(keyID []byte) (keys ExpandedAppStateKeys, err error) { 86 | keyCacheID := base64.RawStdEncoding.EncodeToString(keyID) 87 | var ok bool 88 | 89 | proc.keyCacheLock.Lock() 90 | defer proc.keyCacheLock.Unlock() 91 | 92 | keys, ok = proc.keyCache[keyCacheID] 93 | if !ok { 94 | var keyData *store.AppStateSyncKey 95 | keyData, err = proc.Store.AppStateKeys.GetAppStateSyncKey(keyID) 96 | if keyData != nil { 97 | keys = expandAppStateKeys(keyData.Data) 98 | proc.keyCache[keyCacheID] = keys 99 | } else if err == nil { 100 | err = ErrKeyNotFound 101 | } 102 | } 103 | return 104 | } 105 | 106 | func (proc *Processor) GetMissingKeyIDs(pl *PatchList) [][]byte { 107 | cache := make(map[string]bool) 108 | var missingKeys [][]byte 109 | checkMissing := func(keyID []byte) { 110 | if keyID == nil { 111 | return 112 | } 113 | stringKeyID := base64.RawStdEncoding.EncodeToString(keyID) 114 | _, alreadyAdded := cache[stringKeyID] 115 | if !alreadyAdded { 116 | keyData, err := proc.Store.AppStateKeys.GetAppStateSyncKey(keyID) 117 | if err != nil { 118 | proc.Log.Warnf("Error fetching key %X while checking if it's missing: %v", keyID, err) 119 | } 120 | missing := keyData == nil && err == nil 121 | cache[stringKeyID] = missing 122 | if missing { 123 | missingKeys = append(missingKeys, keyID) 124 | } 125 | } 126 | } 127 | if pl.Snapshot != nil { 128 | checkMissing(pl.Snapshot.GetKeyId().GetId()) 129 | for _, record := range pl.Snapshot.GetRecords() { 130 | checkMissing(record.GetKeyId().GetId()) 131 | } 132 | } 133 | for _, patch := range pl.Patches { 134 | checkMissing(patch.GetKeyId().GetId()) 135 | } 136 | return missingKeys 137 | } 138 | -------------------------------------------------------------------------------- /appstate/lthash/lthash.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package lthash implements a summation based hash algorithm that maintains the 8 | // integrity of a piece of data over a series of mutations. You can add/remove 9 | // mutations, and it'll return a hash equal to if the same series of mutations 10 | // was made sequentially. 11 | package lthash 12 | 13 | import ( 14 | "encoding/binary" 15 | 16 | "github.com/idlethorn/waSocket/util/hkdfutil" 17 | ) 18 | 19 | type LTHash struct { 20 | HKDFInfo []byte 21 | HKDFSize uint8 22 | } 23 | 24 | // WAPatchIntegrity is a LTHash instance initialized with the details used for verifying integrity of WhatsApp app state sync patches. 25 | var WAPatchIntegrity = LTHash{[]byte("WhatsApp Patch Integrity"), 128} 26 | 27 | func (lth LTHash) SubtractThenAdd(base []byte, subtract, add [][]byte) []byte { 28 | output := make([]byte, len(base)) 29 | copy(output, base) 30 | lth.SubtractThenAddInPlace(output, subtract, add) 31 | return output 32 | } 33 | 34 | func (lth LTHash) SubtractThenAddInPlace(base []byte, subtract, add [][]byte) { 35 | lth.multipleOp(base, subtract, true) 36 | lth.multipleOp(base, add, false) 37 | } 38 | 39 | func (lth LTHash) multipleOp(base []byte, input [][]byte, subtract bool) { 40 | for _, item := range input { 41 | performPointwiseWithOverflow(base, hkdfutil.SHA256(item, nil, lth.HKDFInfo, lth.HKDFSize), subtract) 42 | } 43 | } 44 | 45 | func performPointwiseWithOverflow(base, input []byte, subtract bool) []byte { 46 | for i := 0; i < len(base); i += 2 { 47 | x := binary.LittleEndian.Uint16(base[i : i+2]) 48 | y := binary.LittleEndian.Uint16(input[i : i+2]) 49 | var result uint16 50 | if subtract { 51 | result = x - y 52 | } else { 53 | result = x + y 54 | } 55 | binary.LittleEndian.PutUint16(base[i:i+2], result) 56 | } 57 | return base 58 | } 59 | -------------------------------------------------------------------------------- /armadillomessage.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "fmt" 11 | 12 | "google.golang.org/protobuf/proto" 13 | 14 | armadillo "github.com/idlethorn/waSocket/proto" 15 | "github.com/idlethorn/waSocket/proto/waCommon" 16 | "github.com/idlethorn/waSocket/proto/waMsgApplication" 17 | "github.com/idlethorn/waSocket/proto/waMsgTransport" 18 | "github.com/idlethorn/waSocket/types" 19 | "github.com/idlethorn/waSocket/types/events" 20 | ) 21 | 22 | func (cli *Client) handleDecryptedArmadillo(info *types.MessageInfo, decrypted []byte, retryCount int) bool { 23 | dec, err := decodeArmadillo(decrypted) 24 | if err != nil { 25 | cli.Log.Warnf("Failed to decode armadillo message from %s: %v", info.SourceString(), err) 26 | return false 27 | } 28 | dec.Info = *info 29 | dec.RetryCount = retryCount 30 | if dec.Transport.GetProtocol().GetAncillary().GetSkdm() != nil { 31 | if !info.IsGroup { 32 | cli.Log.Warnf("Got sender key distribution message in non-group chat from %s", info.Sender) 33 | } else { 34 | skdm := dec.Transport.GetProtocol().GetAncillary().GetSkdm() 35 | cli.handleSenderKeyDistributionMessage(info.Chat, info.Sender, skdm.AxolotlSenderKeyDistributionMessage) 36 | } 37 | } 38 | if dec.Message != nil { 39 | cli.dispatchEvent(&dec) 40 | } 41 | return true 42 | } 43 | 44 | func decodeArmadillo(data []byte) (dec events.FBMessage, err error) { 45 | var transport waMsgTransport.MessageTransport 46 | err = proto.Unmarshal(data, &transport) 47 | if err != nil { 48 | return dec, fmt.Errorf("failed to unmarshal transport: %w", err) 49 | } 50 | dec.Transport = &transport 51 | if transport.GetPayload() == nil { 52 | return 53 | } 54 | application, err := transport.GetPayload().Decode() 55 | if err != nil { 56 | return dec, fmt.Errorf("failed to unmarshal application: %w", err) 57 | } 58 | dec.Application = application 59 | if application.GetPayload() == nil { 60 | return 61 | } 62 | 63 | switch typedContent := application.GetPayload().GetContent().(type) { 64 | case *waMsgApplication.MessageApplication_Payload_CoreContent: 65 | err = fmt.Errorf("unsupported core content payload") 66 | case *waMsgApplication.MessageApplication_Payload_Signal: 67 | err = fmt.Errorf("unsupported signal payload") 68 | case *waMsgApplication.MessageApplication_Payload_ApplicationData: 69 | err = fmt.Errorf("unsupported application data payload") 70 | case *waMsgApplication.MessageApplication_Payload_SubProtocol: 71 | var protoMsg proto.Message 72 | var subData *waCommon.SubProtocol 73 | switch subProtocol := typedContent.SubProtocol.GetSubProtocol().(type) { 74 | case *waMsgApplication.MessageApplication_SubProtocolPayload_ConsumerMessage: 75 | dec.Message, err = subProtocol.Decode() 76 | case *waMsgApplication.MessageApplication_SubProtocolPayload_BusinessMessage: 77 | dec.Message = (*armadillo.Unsupported_BusinessApplication)(subProtocol.BusinessMessage) 78 | case *waMsgApplication.MessageApplication_SubProtocolPayload_PaymentMessage: 79 | dec.Message = (*armadillo.Unsupported_PaymentApplication)(subProtocol.PaymentMessage) 80 | case *waMsgApplication.MessageApplication_SubProtocolPayload_MultiDevice: 81 | dec.Message, err = subProtocol.Decode() 82 | case *waMsgApplication.MessageApplication_SubProtocolPayload_Voip: 83 | dec.Message = (*armadillo.Unsupported_Voip)(subProtocol.Voip) 84 | case *waMsgApplication.MessageApplication_SubProtocolPayload_Armadillo: 85 | dec.Message, err = subProtocol.Decode() 86 | default: 87 | return dec, fmt.Errorf("unsupported subprotocol type: %T", subProtocol) 88 | } 89 | if protoMsg != nil { 90 | err = proto.Unmarshal(subData.GetPayload(), protoMsg) 91 | if err != nil { 92 | return dec, fmt.Errorf("failed to unmarshal application subprotocol payload (%T v%d): %w", protoMsg, subData.GetVersion(), err) 93 | } 94 | } 95 | default: 96 | err = fmt.Errorf("unsupported application payload content type: %T", typedContent) 97 | } 98 | return 99 | } 100 | -------------------------------------------------------------------------------- /binary/errors.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import "errors" 4 | 5 | // Errors returned by the binary XML decoder. 6 | var ( 7 | ErrInvalidType = errors.New("unsupported payload type") 8 | ErrInvalidJIDType = errors.New("invalid JID type") 9 | ErrInvalidNode = errors.New("invalid node") 10 | ErrInvalidToken = errors.New("invalid token with tag") 11 | ErrNonStringKey = errors.New("non-string key") 12 | ) 13 | -------------------------------------------------------------------------------- /binary/node.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package binary implements encoding and decoding documents in WhatsApp's binary XML format. 8 | package binary 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | 14 | "github.com/idlethorn/waSocket/types" 15 | ) 16 | 17 | // Attrs is a type alias for the attributes of an XML element (Node). 18 | type Attrs = map[string]any 19 | 20 | // Node represents an XML element. 21 | type Node struct { 22 | Tag string // The tag of the element. 23 | Attrs Attrs // The attributes of the element. 24 | Content interface{} // The content inside the element. Can be nil, a list of Nodes, or a byte array. 25 | } 26 | 27 | type marshalableNode struct { 28 | Tag string 29 | Attrs Attrs 30 | Content json.RawMessage 31 | } 32 | 33 | func (n *Node) UnmarshalJSON(data []byte) error { 34 | var mn marshalableNode 35 | err := json.Unmarshal(data, &mn) 36 | if err != nil { 37 | return err 38 | } 39 | for key, val := range mn.Attrs { 40 | switch typedVal := val.(type) { 41 | case string: 42 | parsed, err := types.ParseJID(typedVal) 43 | if err == nil && parsed.Server == types.DefaultUserServer || parsed.Server == types.NewsletterServer || parsed.Server == types.GroupServer || parsed.Server == types.BroadcastServer { 44 | mn.Attrs[key] = parsed 45 | } 46 | case float64: 47 | mn.Attrs[key] = int64(typedVal) 48 | } 49 | } 50 | n.Tag = mn.Tag 51 | n.Attrs = mn.Attrs 52 | if len(mn.Content) > 0 { 53 | if mn.Content[0] == '[' { 54 | var nodes []Node 55 | err = json.Unmarshal(mn.Content, &nodes) 56 | if err != nil { 57 | return err 58 | } 59 | n.Content = nodes 60 | } else if mn.Content[0] == '"' { 61 | var binaryContent []byte 62 | err = json.Unmarshal(mn.Content, &binaryContent) 63 | if err != nil { 64 | return err 65 | } 66 | n.Content = binaryContent 67 | } else { 68 | return fmt.Errorf("node content must be an array of nodes or a base64 string") 69 | } 70 | } 71 | return nil 72 | } 73 | 74 | // GetChildren returns the Content of the node as a list of nodes. If the content is not a list of nodes, this returns nil. 75 | func (n *Node) GetChildren() []Node { 76 | if n.Content == nil { 77 | return nil 78 | } 79 | children, ok := n.Content.([]Node) 80 | if !ok { 81 | return nil 82 | } 83 | return children 84 | } 85 | 86 | // GetChildrenByTag returns the same list as GetChildren, but filters it by tag first. 87 | func (n *Node) GetChildrenByTag(tag string) (children []Node) { 88 | for _, node := range n.GetChildren() { 89 | if node.Tag == tag { 90 | children = append(children, node) 91 | } 92 | } 93 | return 94 | } 95 | 96 | // GetOptionalChildByTag finds the first child with the given tag and returns it. 97 | // Each provided tag will recurse in, so this is useful for getting a specific nested element. 98 | func (n *Node) GetOptionalChildByTag(tags ...string) (val Node, ok bool) { 99 | val = *n 100 | Outer: 101 | for _, tag := range tags { 102 | for _, child := range val.GetChildren() { 103 | if child.Tag == tag { 104 | val = child 105 | continue Outer 106 | } 107 | } 108 | // If no matching children are found, return false 109 | return 110 | } 111 | // All iterations of loop found a matching child, return it 112 | ok = true 113 | return 114 | } 115 | 116 | // GetChildByTag does the same thing as GetOptionalChildByTag, but returns the Node directly without the ok boolean. 117 | func (n *Node) GetChildByTag(tags ...string) Node { 118 | node, _ := n.GetOptionalChildByTag(tags...) 119 | return node 120 | } 121 | 122 | // Marshal encodes an XML element (Node) into WhatsApp's binary XML representation. 123 | func Marshal(n Node) ([]byte, error) { 124 | w := newEncoder() 125 | w.writeNode(n) 126 | return w.getData(), nil 127 | } 128 | 129 | // Unmarshal decodes WhatsApp's binary XML representation into a Node. 130 | func Unmarshal(data []byte) (*Node, error) { 131 | r := newDecoder(data) 132 | n, err := r.readNode() 133 | if err != nil { 134 | return nil, err 135 | } else if r.index != len(r.data) { 136 | return n, fmt.Errorf("%d leftover bytes after decoding", len(r.data)-r.index) 137 | } 138 | return n, nil 139 | } 140 | -------------------------------------------------------------------------------- /binary/proto/doc.go: -------------------------------------------------------------------------------- 1 | // Package proto contains type aliases for backwards compatibility. 2 | // 3 | // Deprecated: New code should reference the protobuf types in the github.com/idlethorn/waSocket/proto/wa* packages directly. 4 | package proto 5 | -------------------------------------------------------------------------------- /binary/unpack.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package binary 8 | 9 | import ( 10 | "bytes" 11 | "compress/zlib" 12 | "fmt" 13 | "io" 14 | ) 15 | 16 | // Unpack unpacks the given decrypted data from the WhatsApp web API. 17 | // 18 | // It checks the first byte to decide whether to uncompress the data with zlib or just return as-is 19 | // (without the first byte). There's currently no corresponding Pack function because Marshal 20 | // already returns the data with a leading zero (i.e. not compressed). 21 | func Unpack(data []byte) ([]byte, error) { 22 | dataType, data := data[0], data[1:] 23 | if 2&dataType > 0 { 24 | if decompressor, err := zlib.NewReader(bytes.NewReader(data)); err != nil { 25 | return nil, fmt.Errorf("failed to create zlib reader: %w", err) 26 | } else if data, err = io.ReadAll(decompressor); err != nil { 27 | return nil, err 28 | } 29 | } 30 | return data, nil 31 | } 32 | -------------------------------------------------------------------------------- /binary/xml.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package binary 8 | 9 | import ( 10 | "encoding/hex" 11 | "fmt" 12 | "sort" 13 | "strings" 14 | "unicode" 15 | "unicode/utf8" 16 | ) 17 | 18 | // Options to control how Node.XMLString behaves. 19 | var ( 20 | IndentXML = false 21 | MaxBytesToPrintAsHex = 128 22 | ) 23 | 24 | // XMLString converts the Node to its XML representation 25 | func (n *Node) XMLString() string { 26 | content := n.contentString() 27 | if len(content) == 0 { 28 | return fmt.Sprintf("<%[1]s%[2]s/>", n.Tag, n.attributeString()) 29 | } 30 | newline := "\n" 31 | if len(content) == 1 || !IndentXML { 32 | newline = "" 33 | } 34 | return fmt.Sprintf("<%[1]s%[2]s>%[4]s%[3]s%[4]s", n.Tag, n.attributeString(), strings.Join(content, newline), newline) 35 | } 36 | 37 | func (n *Node) attributeString() string { 38 | if len(n.Attrs) == 0 { 39 | return "" 40 | } 41 | stringAttrs := make([]string, len(n.Attrs)+1) 42 | i := 1 43 | for key, value := range n.Attrs { 44 | stringAttrs[i] = fmt.Sprintf(`%s="%v"`, key, value) 45 | i++ 46 | } 47 | sort.Strings(stringAttrs) 48 | return strings.Join(stringAttrs, " ") 49 | } 50 | 51 | func printable(data []byte) string { 52 | if !utf8.Valid(data) { 53 | return "" 54 | } 55 | str := string(data) 56 | for _, c := range str { 57 | if !unicode.IsPrint(c) { 58 | return "" 59 | } 60 | } 61 | return str 62 | } 63 | 64 | func (n *Node) contentString() []string { 65 | split := make([]string, 0) 66 | switch content := n.Content.(type) { 67 | case []Node: 68 | for _, item := range content { 69 | split = append(split, strings.Split(item.XMLString(), "\n")...) 70 | } 71 | case []byte: 72 | if strContent := printable(content); len(strContent) > 0 { 73 | if IndentXML { 74 | split = append(split, strings.Split(string(content), "\n")...) 75 | } else { 76 | split = append(split, strings.ReplaceAll(string(content), "\n", "\\n")) 77 | } 78 | } else if len(content) > MaxBytesToPrintAsHex { 79 | split = append(split, fmt.Sprintf("", len(content))) 80 | } else if !IndentXML { 81 | split = append(split, hex.EncodeToString(content)) 82 | } else { 83 | hexData := hex.EncodeToString(content) 84 | for i := 0; i < len(hexData); i += 80 { 85 | if len(hexData) < i+80 { 86 | split = append(split, hexData[i:]) 87 | } else { 88 | split = append(split, hexData[i:i+80]) 89 | } 90 | } 91 | } 92 | case nil: 93 | // don't append anything 94 | default: 95 | strContent := fmt.Sprintf("%s", content) 96 | if IndentXML { 97 | split = append(split, strings.Split(strContent, "\n")...) 98 | } else { 99 | split = append(split, strings.ReplaceAll(strContent, "\n", "\\n")) 100 | } 101 | } 102 | if len(split) > 1 && IndentXML { 103 | for i, line := range split { 104 | split[i] = " " + line 105 | } 106 | } 107 | return split 108 | } 109 | -------------------------------------------------------------------------------- /broadcast.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | 13 | waBinary "github.com/idlethorn/waSocket/binary" 14 | "github.com/idlethorn/waSocket/types" 15 | ) 16 | 17 | func (cli *Client) getBroadcastListParticipants(jid types.JID) ([]types.JID, error) { 18 | var list []types.JID 19 | var err error 20 | if jid == types.StatusBroadcastJID { 21 | list, err = cli.getStatusBroadcastRecipients() 22 | } else { 23 | return nil, ErrBroadcastListUnsupported 24 | } 25 | if err != nil { 26 | return nil, err 27 | } 28 | ownID := cli.getOwnID().ToNonAD() 29 | if ownID.IsEmpty() { 30 | return nil, ErrNotLoggedIn 31 | } 32 | 33 | selfIndex := -1 34 | for i, participant := range list { 35 | if participant.User == ownID.User { 36 | selfIndex = i 37 | break 38 | } 39 | } 40 | if selfIndex < 0 { 41 | list = append(list, ownID) 42 | } 43 | return list, nil 44 | } 45 | 46 | func (cli *Client) getStatusBroadcastRecipients() ([]types.JID, error) { 47 | statusPrivacyOptions, err := cli.GetStatusPrivacy() 48 | if err != nil { 49 | return nil, fmt.Errorf("failed to get status privacy: %w", err) 50 | } 51 | statusPrivacy := statusPrivacyOptions[0] 52 | if statusPrivacy.Type == types.StatusPrivacyTypeWhitelist { 53 | // Whitelist mode, just return the list 54 | return statusPrivacy.List, nil 55 | } 56 | 57 | // Blacklist or all contacts mode. Find all contacts from database, then filter them appropriately. 58 | contacts, err := cli.Store.Contacts.GetAllContacts() 59 | if err != nil { 60 | return nil, fmt.Errorf("failed to get contact list from db: %w", err) 61 | } 62 | 63 | blacklist := make(map[types.JID]struct{}) 64 | if statusPrivacy.Type == types.StatusPrivacyTypeBlacklist { 65 | for _, jid := range statusPrivacy.List { 66 | blacklist[jid] = struct{}{} 67 | } 68 | } 69 | 70 | var contactsArray []types.JID 71 | for jid, contact := range contacts { 72 | _, isBlacklisted := blacklist[jid] 73 | if isBlacklisted { 74 | continue 75 | } 76 | // TODO should there be a better way to separate contacts and found push names in the db? 77 | if len(contact.FullName) > 0 { 78 | contactsArray = append(contactsArray, jid) 79 | } 80 | } 81 | return contactsArray, nil 82 | } 83 | 84 | var DefaultStatusPrivacy = []types.StatusPrivacy{{ 85 | Type: types.StatusPrivacyTypeContacts, 86 | IsDefault: true, 87 | }} 88 | 89 | // GetStatusPrivacy gets the user's status privacy settings (who to send status broadcasts to). 90 | // 91 | // There can be multiple different stored settings, the first one is always the default. 92 | func (cli *Client) GetStatusPrivacy() ([]types.StatusPrivacy, error) { 93 | resp, err := cli.sendIQ(infoQuery{ 94 | Namespace: "status", 95 | Type: iqGet, 96 | To: types.ServerJID, 97 | Content: []waBinary.Node{{ 98 | Tag: "privacy", 99 | }}, 100 | }) 101 | if err != nil { 102 | if errors.Is(err, ErrIQNotFound) { 103 | return DefaultStatusPrivacy, nil 104 | } 105 | return nil, err 106 | } 107 | privacyLists := resp.GetChildByTag("privacy") 108 | var outputs []types.StatusPrivacy 109 | for _, list := range privacyLists.GetChildren() { 110 | if list.Tag != "list" { 111 | continue 112 | } 113 | 114 | ag := list.AttrGetter() 115 | var out types.StatusPrivacy 116 | out.IsDefault = ag.OptionalBool("default") 117 | out.Type = types.StatusPrivacyType(ag.String("type")) 118 | children := list.GetChildren() 119 | if len(children) > 0 { 120 | out.List = make([]types.JID, 0, len(children)) 121 | for _, child := range children { 122 | jid, ok := child.Attrs["jid"].(types.JID) 123 | if child.Tag == "user" && ok { 124 | out.List = append(out.List, jid) 125 | } 126 | } 127 | } 128 | outputs = append(outputs, out) 129 | if out.IsDefault { 130 | // Move default to always be first in the list 131 | outputs[len(outputs)-1] = outputs[0] 132 | outputs[0] = out 133 | } 134 | if len(ag.Errors) > 0 { 135 | return nil, ag.Error() 136 | } 137 | } 138 | if len(outputs) == 0 { 139 | return DefaultStatusPrivacy, nil 140 | } 141 | return outputs, nil 142 | } 143 | -------------------------------------------------------------------------------- /call.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | waBinary "github.com/idlethorn/waSocket/binary" 11 | "github.com/idlethorn/waSocket/types" 12 | "github.com/idlethorn/waSocket/types/events" 13 | ) 14 | 15 | func (cli *Client) handleCallEvent(node *waBinary.Node) { 16 | defer cli.maybeDeferredAck(node)() 17 | 18 | if len(node.GetChildren()) != 1 { 19 | cli.dispatchEvent(&events.UnknownCallEvent{Node: node}) 20 | return 21 | } 22 | ag := node.AttrGetter() 23 | child := node.GetChildren()[0] 24 | cag := child.AttrGetter() 25 | basicMeta := types.BasicCallMeta{ 26 | From: ag.JID("from"), 27 | Timestamp: ag.UnixTime("t"), 28 | CallCreator: cag.JID("call-creator"), 29 | CallID: cag.String("call-id"), 30 | } 31 | switch child.Tag { 32 | case "offer": 33 | cli.dispatchEvent(&events.CallOffer{ 34 | BasicCallMeta: basicMeta, 35 | CallRemoteMeta: types.CallRemoteMeta{ 36 | RemotePlatform: ag.String("platform"), 37 | RemoteVersion: ag.String("version"), 38 | }, 39 | Data: &child, 40 | }) 41 | case "offer_notice": 42 | cli.dispatchEvent(&events.CallOfferNotice{ 43 | BasicCallMeta: basicMeta, 44 | Media: cag.String("media"), 45 | Type: cag.String("type"), 46 | Data: &child, 47 | }) 48 | case "relaylatency": 49 | cli.dispatchEvent(&events.CallRelayLatency{ 50 | BasicCallMeta: basicMeta, 51 | Data: &child, 52 | }) 53 | case "accept": 54 | cli.dispatchEvent(&events.CallAccept{ 55 | BasicCallMeta: basicMeta, 56 | CallRemoteMeta: types.CallRemoteMeta{ 57 | RemotePlatform: ag.String("platform"), 58 | RemoteVersion: ag.String("version"), 59 | }, 60 | Data: &child, 61 | }) 62 | case "preaccept": 63 | cli.dispatchEvent(&events.CallPreAccept{ 64 | BasicCallMeta: basicMeta, 65 | CallRemoteMeta: types.CallRemoteMeta{ 66 | RemotePlatform: ag.String("platform"), 67 | RemoteVersion: ag.String("version"), 68 | }, 69 | Data: &child, 70 | }) 71 | case "transport": 72 | cli.dispatchEvent(&events.CallTransport{ 73 | BasicCallMeta: basicMeta, 74 | CallRemoteMeta: types.CallRemoteMeta{ 75 | RemotePlatform: ag.String("platform"), 76 | RemoteVersion: ag.String("version"), 77 | }, 78 | Data: &child, 79 | }) 80 | case "terminate": 81 | cli.dispatchEvent(&events.CallTerminate{ 82 | BasicCallMeta: basicMeta, 83 | Reason: cag.String("reason"), 84 | Data: &child, 85 | }) 86 | case "reject": 87 | cli.dispatchEvent(&events.CallReject{ 88 | BasicCallMeta: basicMeta, 89 | Data: &child, 90 | }) 91 | default: 92 | cli.dispatchEvent(&events.UnknownCallEvent{Node: node}) 93 | } 94 | } 95 | 96 | // RejectCall reject an incoming call. 97 | func (cli *Client) RejectCall(callFrom types.JID, callID string) error { 98 | ownID := cli.getOwnID() 99 | if ownID.IsEmpty() { 100 | return ErrNotLoggedIn 101 | } 102 | ownID, callFrom = ownID.ToNonAD(), callFrom.ToNonAD() 103 | return cli.sendNode(waBinary.Node{ 104 | Tag: "call", 105 | Attrs: waBinary.Attrs{"id": cli.GenerateMessageID(), "from": ownID, "to": callFrom}, 106 | Content: []waBinary.Node{{ 107 | Tag: "reject", 108 | Attrs: waBinary.Attrs{"call-id": callID, "call-creator": callFrom, "count": "0"}, 109 | Content: nil, 110 | }}, 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket_test 8 | 9 | import ( 10 | "context" 11 | "fmt" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | 16 | "github.com/idlethorn/waSocket" 17 | "github.com/idlethorn/waSocket/store/sqlstore" 18 | "github.com/idlethorn/waSocket/types/events" 19 | waLog "github.com/idlethorn/waSocket/util/log" 20 | ) 21 | 22 | func eventHandler(evt interface{}) { 23 | switch v := evt.(type) { 24 | case *events.Message: 25 | fmt.Println("Received a message!", v.Message.GetConversation()) 26 | } 27 | } 28 | 29 | func Example() { 30 | // |------------------------------------------------------------------------------------------------------| 31 | // | NOTE: You must also import the appropriate DB connector, e.g. github.com/mattn/go-sqlite3 for SQLite | 32 | // |------------------------------------------------------------------------------------------------------| 33 | 34 | dbLog := waLog.Stdout("Database", "DEBUG", true) 35 | container, err := sqlstore.New("sqlite3", "file:examplestore.db?_foreign_keys=on", dbLog) 36 | if err != nil { 37 | panic(err) 38 | } 39 | // If you want multiple sessions, remember their JIDs and use .GetDevice(jid) or .GetAllDevices() instead. 40 | deviceStore, err := container.GetFirstDevice() 41 | if err != nil { 42 | panic(err) 43 | } 44 | clientLog := waLog.Stdout("Client", "DEBUG", true) 45 | client := waSocket.NewClient(deviceStore, clientLog) 46 | client.AddEventHandler(eventHandler) 47 | 48 | if client.Store.ID == nil { 49 | // No ID stored, new login 50 | qrChan, _ := client.GetQRChannel(context.Background()) 51 | err = client.Connect() 52 | if err != nil { 53 | panic(err) 54 | } 55 | for evt := range qrChan { 56 | if evt.Event == "code" { 57 | // Render the QR code here 58 | // e.g. qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) 59 | // or just manually `echo 2@... | qrencode -t ansiutf8` in a terminal 60 | fmt.Println("QR code:", evt.Code) 61 | } else { 62 | fmt.Println("Login event:", evt.Event) 63 | } 64 | } 65 | } else { 66 | // Already logged in, just connect 67 | err = client.Connect() 68 | if err != nil { 69 | panic(err) 70 | } 71 | } 72 | 73 | // Listen to Ctrl+C (you can also do something else that prevents the program from exiting) 74 | c := make(chan os.Signal, 1) 75 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 76 | <-c 77 | 78 | client.Disconnect() 79 | } 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/idlethorn/waSocket 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/google/uuid v1.6.0 9 | github.com/gorilla/websocket v1.5.0 10 | github.com/rs/zerolog v1.33.0 11 | go.mau.fi/libsignal v0.1.2 12 | go.mau.fi/util v0.8.6 13 | golang.org/x/crypto v0.36.0 14 | golang.org/x/net v0.37.0 15 | google.golang.org/protobuf v1.36.5 16 | ) 17 | 18 | require ( 19 | filippo.io/edwards25519 v1.1.0 // indirect 20 | github.com/mattn/go-colorable v0.1.13 // indirect 21 | github.com/mattn/go-isatty v0.0.19 // indirect 22 | github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect 23 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect 24 | golang.org/x/sys v0.31.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= 4 | github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= 5 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 10 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 11 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 12 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 13 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 14 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 15 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 16 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 17 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 18 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 19 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 20 | github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= 21 | github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 22 | github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs= 23 | github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= 24 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 25 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 26 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 27 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 28 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= 29 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 30 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 31 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 32 | go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= 33 | go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= 34 | go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= 35 | go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 36 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 37 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 38 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= 39 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= 40 | golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= 41 | golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 42 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 46 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 47 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 48 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 49 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 50 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 51 | -------------------------------------------------------------------------------- /internals_generate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | //go:build ignore 8 | 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "go/ast" 14 | "go/parser" 15 | "go/token" 16 | "os" 17 | "strings" 18 | 19 | "go.mau.fi/util/exerrors" 20 | ) 21 | 22 | const header = `// GENERATED BY internals_generate.go; DO NOT EDIT 23 | 24 | //go:generate go run internals_generate.go 25 | //go:generate goimports -local github.com/idlethorn/waSocket -w internals.go 26 | 27 | package waSocket 28 | 29 | ` 30 | const postImportHeader = ` 31 | type DangerousInternalClient struct { 32 | c *Client 33 | } 34 | 35 | // DangerousInternals allows access to all unexported methods in Client. 36 | // 37 | // Deprecated: dangerous 38 | func (cli *Client) DangerousInternals() *DangerousInternalClient { 39 | return &DangerousInternalClient{cli} 40 | } 41 | 42 | type DangerousInfoQuery = infoQuery 43 | type DangerousInfoQueryType = infoQueryType 44 | 45 | ` 46 | 47 | func getTypeName(expr ast.Expr) string { 48 | switch e := expr.(type) { 49 | case *ast.Ident: 50 | return e.Name 51 | case *ast.StarExpr: 52 | return "*" + getTypeName(e.X) 53 | case *ast.ArrayType: 54 | return "[]" + getTypeName(e.Elt) 55 | case *ast.MapType: 56 | return fmt.Sprintf("map[%s]%s", getTypeName(e.Key), getTypeName(e.Value)) 57 | case *ast.ChanType: 58 | if e.Dir == ast.SEND { 59 | return fmt.Sprintf("chan<- %s", getTypeName(e.Value)) 60 | } else if e.Dir == ast.RECV { 61 | return fmt.Sprintf("<-chan %s", getTypeName(e.Value)) 62 | } 63 | return fmt.Sprintf("chan %s", getTypeName(e.Value)) 64 | case *ast.FuncType: 65 | var params []string 66 | for _, param := range e.Params.List { 67 | params = append(params, getTypeName(param.Type)) 68 | } 69 | var results []string 70 | if e.Results != nil { 71 | for _, result := range e.Results.List { 72 | results = append(results, getTypeName(result.Type)) 73 | } 74 | } 75 | return fmt.Sprintf("func(%s) %s", strings.Join(params, ", "), strings.Join(results, ", ")) 76 | case *ast.SelectorExpr: 77 | return fmt.Sprintf("%s.%s", getTypeName(e.X), e.Sel.Name) 78 | case *ast.StructType: 79 | // This isn't technically correct, but struct literals shouldn't be used for anything else 80 | return "struct{}" 81 | case *ast.Ellipsis: 82 | return fmt.Sprintf("...%s", e.Elt) 83 | default: 84 | panic(fmt.Errorf("unknown type %T", e)) 85 | } 86 | } 87 | 88 | var write func(str string) 89 | var writef func(format string, args ...any) 90 | 91 | func main() { 92 | fset := token.NewFileSet() 93 | fileNames := []string{ 94 | "appstate.go", "armadillomessage.go", "broadcast.go", "call.go", "client.go", 95 | "connectionevents.go", "download.go", "download-to-file.go", "group.go", "handshake.go", 96 | "keepalive.go", "mediaconn.go", "mediaretry.go", "message.go", "msgsecret.go", 97 | "newsletter.go", "notification.go", "pair-code.go", "pair.go", "prekeys.go", 98 | "presence.go", "privacysettings.go", "push.go", "qrchan.go", "receipt.go", "request.go", 99 | "retry.go", "sendfb.go", "send.go", "upload.go", "user.go", 100 | } 101 | files := make([]*ast.File, len(fileNames)) 102 | for i, name := range fileNames { 103 | files[i] = exerrors.Must(parser.ParseFile(fset, name, nil, parser.SkipObjectResolution)) 104 | } 105 | file := exerrors.Must(os.OpenFile("internals.go", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)) 106 | write = func(str string) { 107 | exerrors.Must(file.WriteString(str)) 108 | } 109 | writef = func(format string, args ...any) { 110 | exerrors.Must(fmt.Fprintf(file, format, args...)) 111 | } 112 | write(header) 113 | write("import (\n") 114 | for _, i := range files[0].Imports { 115 | write("\t") 116 | if i.Name != nil { 117 | writef("%s ", i.Name.Name) 118 | } 119 | writef("%s\n", i.Path.Value) 120 | } 121 | write(")\n") 122 | write(postImportHeader) 123 | for _, f := range files { 124 | processFile(f) 125 | } 126 | exerrors.PanicIfNotNil(file.Close()) 127 | } 128 | 129 | func processFile(f *ast.File) { 130 | ast.Inspect(f, func(node ast.Node) (retVal bool) { 131 | retVal = true 132 | funcDecl, ok := node.(*ast.FuncDecl) 133 | if !ok || funcDecl.Name.IsExported() { 134 | return 135 | } 136 | if funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 || len(funcDecl.Recv.List[0].Names) == 0 || 137 | funcDecl.Recv.List[0].Names[0].Name != "cli" { 138 | return 139 | } 140 | writef("\nfunc (int *DangerousInternalClient) %s%s(", strings.ToUpper(funcDecl.Name.Name[0:1]), funcDecl.Name.Name[1:]) 141 | for i, param := range funcDecl.Type.Params.List { 142 | if i != 0 { 143 | write(", ") 144 | } 145 | for j, name := range param.Names { 146 | if j != 0 { 147 | write(", ") 148 | } 149 | write(name.Name) 150 | } 151 | if len(param.Names) > 0 { 152 | write(" ") 153 | } 154 | write(getTypeName(param.Type)) 155 | } 156 | write(") ") 157 | if funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) > 0 { 158 | needsParentheses := len(funcDecl.Type.Results.List) > 1 || len(funcDecl.Type.Results.List[0].Names) > 0 159 | if needsParentheses { 160 | write("(") 161 | } 162 | for i, result := range funcDecl.Type.Results.List { 163 | if i != 0 { 164 | write(", ") 165 | } 166 | for j, name := range result.Names { 167 | if j != 0 { 168 | write(", ") 169 | } 170 | write(name.Name) 171 | } 172 | if len(result.Names) > 0 { 173 | write(" ") 174 | } 175 | write(getTypeName(result.Type)) 176 | } 177 | if needsParentheses { 178 | write(")") 179 | } 180 | write(" ") 181 | } 182 | write("{\n\t") 183 | if funcDecl.Type.Results != nil { 184 | write("return ") 185 | } 186 | writef("int.c.%s(", funcDecl.Name.Name) 187 | for i, param := range funcDecl.Type.Params.List { 188 | for j, name := range param.Names { 189 | if i != 0 || j != 0 { 190 | write(", ") 191 | } 192 | write(name.Name) 193 | _, isEllipsis := param.Type.(*ast.Ellipsis) 194 | if isEllipsis { 195 | write("...") 196 | } 197 | } 198 | } 199 | write(")\n}\n") 200 | return 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /keepalive.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "context" 11 | "math/rand" 12 | "time" 13 | 14 | "github.com/idlethorn/waSocket/types" 15 | "github.com/idlethorn/waSocket/types/events" 16 | ) 17 | 18 | var ( 19 | // KeepAliveResponseDeadline specifies the duration to wait for a response to websocket keepalive pings. 20 | KeepAliveResponseDeadline = 10 * time.Second 21 | // KeepAliveIntervalMin specifies the minimum interval for websocket keepalive pings. 22 | KeepAliveIntervalMin = 20 * time.Second 23 | // KeepAliveIntervalMax specifies the maximum interval for websocket keepalive pings. 24 | KeepAliveIntervalMax = 30 * time.Second 25 | 26 | // KeepAliveMaxFailTime specifies the maximum time to wait before forcing a reconnect if keepalives fail repeatedly. 27 | KeepAliveMaxFailTime = 3 * time.Minute 28 | ) 29 | 30 | func (cli *Client) keepAliveLoop(ctx context.Context) { 31 | lastSuccess := time.Now() 32 | var errorCount int 33 | for { 34 | interval := rand.Int63n(KeepAliveIntervalMax.Milliseconds()-KeepAliveIntervalMin.Milliseconds()) + KeepAliveIntervalMin.Milliseconds() 35 | select { 36 | case <-time.After(time.Duration(interval) * time.Millisecond): 37 | isSuccess, shouldContinue := cli.sendKeepAlive(ctx) 38 | if !shouldContinue { 39 | return 40 | } else if !isSuccess { 41 | errorCount++ 42 | go cli.dispatchEvent(&events.KeepAliveTimeout{ 43 | ErrorCount: errorCount, 44 | LastSuccess: lastSuccess, 45 | }) 46 | if cli.EnableAutoReconnect && time.Since(lastSuccess) > KeepAliveMaxFailTime { 47 | cli.Log.Debugf("Forcing reconnect due to keepalive failure") 48 | cli.Disconnect() 49 | go cli.autoReconnect() 50 | } 51 | } else { 52 | if errorCount > 0 { 53 | errorCount = 0 54 | go cli.dispatchEvent(&events.KeepAliveRestored{}) 55 | } 56 | lastSuccess = time.Now() 57 | } 58 | case <-ctx.Done(): 59 | return 60 | } 61 | } 62 | } 63 | 64 | func (cli *Client) sendKeepAlive(ctx context.Context) (isSuccess, shouldContinue bool) { 65 | respCh, err := cli.sendIQAsync(infoQuery{ 66 | Namespace: "w:p", 67 | Type: "get", 68 | To: types.ServerJID, 69 | }) 70 | if err != nil { 71 | cli.Log.Warnf("Failed to send keepalive: %v", err) 72 | return false, true 73 | } 74 | select { 75 | case <-respCh: 76 | // All good 77 | return true, true 78 | case <-time.After(KeepAliveResponseDeadline): 79 | cli.Log.Warnf("Keepalive timed out") 80 | return false, true 81 | case <-ctx.Done(): 82 | return false, false 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /mediaconn.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "fmt" 11 | "time" 12 | 13 | waBinary "github.com/idlethorn/waSocket/binary" 14 | "github.com/idlethorn/waSocket/types" 15 | ) 16 | 17 | //type MediaConnIP struct { 18 | // IP4 net.IP 19 | // IP6 net.IP 20 | //} 21 | 22 | // MediaConnHost represents a single host to download media from. 23 | type MediaConnHost struct { 24 | Hostname string 25 | //IPs []MediaConnIP 26 | } 27 | 28 | // MediaConn contains a list of WhatsApp servers from which attachments can be downloaded from. 29 | type MediaConn struct { 30 | Auth string 31 | AuthTTL int 32 | TTL int 33 | MaxBuckets int 34 | FetchedAt time.Time 35 | Hosts []MediaConnHost 36 | } 37 | 38 | // Expiry returns the time when the MediaConn expires. 39 | func (mc *MediaConn) Expiry() time.Time { 40 | return mc.FetchedAt.Add(time.Duration(mc.TTL) * time.Second) 41 | } 42 | 43 | func (cli *Client) refreshMediaConn(force bool) (*MediaConn, error) { 44 | if cli == nil { 45 | return nil, ErrClientIsNil 46 | } 47 | cli.mediaConnLock.Lock() 48 | defer cli.mediaConnLock.Unlock() 49 | if cli.mediaConnCache == nil || force || time.Now().After(cli.mediaConnCache.Expiry()) { 50 | var err error 51 | cli.mediaConnCache, err = cli.queryMediaConn() 52 | if err != nil { 53 | return nil, err 54 | } 55 | } 56 | return cli.mediaConnCache, nil 57 | } 58 | 59 | func (cli *Client) queryMediaConn() (*MediaConn, error) { 60 | resp, err := cli.sendIQ(infoQuery{ 61 | Namespace: "w:m", 62 | Type: "set", 63 | To: types.ServerJID, 64 | Content: []waBinary.Node{{Tag: "media_conn"}}, 65 | }) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to query media connections: %w", err) 68 | } else if len(resp.GetChildren()) == 0 || resp.GetChildren()[0].Tag != "media_conn" { 69 | return nil, fmt.Errorf("failed to query media connections: unexpected child tag") 70 | } 71 | respMC := resp.GetChildren()[0] 72 | var mc MediaConn 73 | ag := respMC.AttrGetter() 74 | mc.FetchedAt = time.Now() 75 | mc.Auth = ag.String("auth") 76 | mc.TTL = ag.Int("ttl") 77 | mc.AuthTTL = ag.Int("auth_ttl") 78 | mc.MaxBuckets = ag.Int("max_buckets") 79 | if !ag.OK() { 80 | return nil, fmt.Errorf("failed to parse media connections: %+v", ag.Errors) 81 | } 82 | for _, child := range respMC.GetChildren() { 83 | if child.Tag != "host" { 84 | cli.Log.Warnf("Unexpected child in media_conn element: %s", child.XMLString()) 85 | continue 86 | } 87 | cag := child.AttrGetter() 88 | mc.Hosts = append(mc.Hosts, MediaConnHost{ 89 | Hostname: cag.String("hostname"), 90 | }) 91 | if !cag.OK() { 92 | return nil, fmt.Errorf("failed to parse media connection host: %+v", ag.Errors) 93 | } 94 | } 95 | return &mc, nil 96 | } 97 | -------------------------------------------------------------------------------- /presence.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "fmt" 11 | 12 | waBinary "github.com/idlethorn/waSocket/binary" 13 | "github.com/idlethorn/waSocket/types" 14 | "github.com/idlethorn/waSocket/types/events" 15 | ) 16 | 17 | func (cli *Client) handleChatState(node *waBinary.Node) { 18 | source, err := cli.parseMessageSource(node, true) 19 | if err != nil { 20 | cli.Log.Warnf("Failed to parse chat state update: %v", err) 21 | } else if len(node.GetChildren()) != 1 { 22 | cli.Log.Warnf("Failed to parse chat state update: unexpected number of children in element (%d)", len(node.GetChildren())) 23 | } else { 24 | child := node.GetChildren()[0] 25 | presence := types.ChatPresence(child.Tag) 26 | if presence != types.ChatPresenceComposing && presence != types.ChatPresencePaused { 27 | cli.Log.Warnf("Unrecognized chat presence state %s", child.Tag) 28 | } 29 | media := types.ChatPresenceMedia(child.AttrGetter().OptionalString("media")) 30 | cli.dispatchEvent(&events.ChatPresence{ 31 | MessageSource: source, 32 | State: presence, 33 | Media: media, 34 | }) 35 | } 36 | } 37 | 38 | func (cli *Client) handlePresence(node *waBinary.Node) { 39 | var evt events.Presence 40 | ag := node.AttrGetter() 41 | evt.From = ag.JID("from") 42 | presenceType := ag.OptionalString("type") 43 | if presenceType == "unavailable" { 44 | evt.Unavailable = true 45 | } else if presenceType != "" { 46 | cli.Log.Debugf("Unrecognized presence type '%s' in presence event from %s", presenceType, evt.From) 47 | } 48 | lastSeen := ag.OptionalString("last") 49 | if lastSeen != "" && lastSeen != "deny" { 50 | evt.LastSeen = ag.UnixTime("last") 51 | } 52 | if !ag.OK() { 53 | cli.Log.Warnf("Error parsing presence event: %+v", ag.Errors) 54 | } else { 55 | cli.dispatchEvent(&evt) 56 | } 57 | } 58 | 59 | // SendPresence updates the user's presence status on WhatsApp. 60 | // 61 | // You should call this at least once after connecting so that the server has your pushname. 62 | // Otherwise, other users will see "-" as the name. 63 | func (cli *Client) SendPresence(state types.Presence) error { 64 | if len(cli.Store.PushName) == 0 { 65 | return ErrNoPushName 66 | } 67 | if state == types.PresenceAvailable { 68 | cli.sendActiveReceipts.CompareAndSwap(0, 1) 69 | } else { 70 | cli.sendActiveReceipts.CompareAndSwap(1, 0) 71 | } 72 | return cli.sendNode(waBinary.Node{ 73 | Tag: "presence", 74 | Attrs: waBinary.Attrs{ 75 | "name": cli.Store.PushName, 76 | "type": string(state), 77 | }, 78 | }) 79 | } 80 | 81 | // SubscribePresence asks the WhatsApp servers to send presence updates of a specific user to this client. 82 | // 83 | // After subscribing to this event, you should start receiving *events.Presence for that user in normal event handlers. 84 | // 85 | // Also, it seems that the WhatsApp servers require you to be online to receive presence status from other users, 86 | // so you should mark yourself as online before trying to use this function: 87 | // 88 | // cli.SendPresence(types.PresenceAvailable) 89 | func (cli *Client) SubscribePresence(jid types.JID) error { 90 | privacyToken, err := cli.Store.PrivacyTokens.GetPrivacyToken(jid) 91 | if err != nil { 92 | return fmt.Errorf("failed to get privacy token: %w", err) 93 | } else if privacyToken == nil { 94 | if cli.ErrorOnSubscribePresenceWithoutToken { 95 | return fmt.Errorf("%w for %v", ErrNoPrivacyToken, jid.ToNonAD()) 96 | } else { 97 | cli.Log.Debugf("Trying to subscribe to presence of %s without privacy token", jid) 98 | } 99 | } 100 | req := waBinary.Node{ 101 | Tag: "presence", 102 | Attrs: waBinary.Attrs{ 103 | "type": "subscribe", 104 | "to": jid, 105 | }, 106 | } 107 | if privacyToken != nil { 108 | req.Content = []waBinary.Node{{ 109 | Tag: "tctoken", 110 | Content: privacyToken.Token, 111 | }} 112 | } 113 | return cli.sendNode(req) 114 | } 115 | 116 | // SendChatPresence updates the user's typing status in a specific chat. 117 | // 118 | // The media parameter can be set to indicate the user is recording media (like a voice message) rather than typing a text message. 119 | func (cli *Client) SendChatPresence(jid types.JID, state types.ChatPresence, media types.ChatPresenceMedia) error { 120 | ownID := cli.getOwnID() 121 | if ownID.IsEmpty() { 122 | return ErrNotLoggedIn 123 | } 124 | content := []waBinary.Node{{Tag: string(state)}} 125 | if state == types.ChatPresenceComposing && len(media) > 0 { 126 | content[0].Attrs = waBinary.Attrs{ 127 | "media": string(media), 128 | } 129 | } 130 | return cli.sendNode(waBinary.Node{ 131 | Tag: "chatstate", 132 | Attrs: waBinary.Attrs{ 133 | "from": ownID, 134 | "to": jid, 135 | }, 136 | Content: content, 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /privacysettings.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "strconv" 11 | "time" 12 | 13 | waBinary "github.com/idlethorn/waSocket/binary" 14 | "github.com/idlethorn/waSocket/types" 15 | "github.com/idlethorn/waSocket/types/events" 16 | ) 17 | 18 | // TryFetchPrivacySettings will fetch the user's privacy settings, either from the in-memory cache or from the server. 19 | func (cli *Client) TryFetchPrivacySettings(ignoreCache bool) (*types.PrivacySettings, error) { 20 | if cli == nil { 21 | return nil, ErrClientIsNil 22 | } else if val := cli.privacySettingsCache.Load(); val != nil && !ignoreCache { 23 | return val.(*types.PrivacySettings), nil 24 | } 25 | resp, err := cli.sendIQ(infoQuery{ 26 | Namespace: "privacy", 27 | Type: iqGet, 28 | To: types.ServerJID, 29 | Content: []waBinary.Node{{Tag: "privacy"}}, 30 | }) 31 | if err != nil { 32 | return nil, err 33 | } 34 | privacyNode, ok := resp.GetOptionalChildByTag("privacy") 35 | if !ok { 36 | return nil, &ElementMissingError{Tag: "privacy", In: "response to privacy settings query"} 37 | } 38 | var settings types.PrivacySettings 39 | cli.parsePrivacySettings(&privacyNode, &settings) 40 | cli.privacySettingsCache.Store(&settings) 41 | return &settings, nil 42 | } 43 | 44 | // GetPrivacySettings will get the user's privacy settings. If an error occurs while fetching them, the error will be 45 | // logged, but the method will just return an empty struct. 46 | func (cli *Client) GetPrivacySettings() (settings types.PrivacySettings) { 47 | if cli == nil || cli.MessengerConfig != nil { 48 | return 49 | } 50 | settingsPtr, err := cli.TryFetchPrivacySettings(false) 51 | if err != nil { 52 | cli.Log.Errorf("Failed to fetch privacy settings: %v", err) 53 | } else { 54 | settings = *settingsPtr 55 | } 56 | return 57 | } 58 | 59 | // SetPrivacySetting will set the given privacy setting to the given value. 60 | // The privacy settings will be fetched from the server after the change and the new settings will be returned. 61 | // If an error occurs while fetching the new settings, will return an empty struct. 62 | func (cli *Client) SetPrivacySetting(name types.PrivacySettingType, value types.PrivacySetting) (settings types.PrivacySettings, err error) { 63 | settingsPtr, err := cli.TryFetchPrivacySettings(false) 64 | if err != nil { 65 | return settings, err 66 | } 67 | _, err = cli.sendIQ(infoQuery{ 68 | Namespace: "privacy", 69 | Type: iqSet, 70 | To: types.ServerJID, 71 | Content: []waBinary.Node{{ 72 | Tag: "privacy", 73 | Content: []waBinary.Node{{ 74 | Tag: "category", 75 | Attrs: waBinary.Attrs{ 76 | "name": string(name), 77 | "value": string(value), 78 | }, 79 | }}, 80 | }}, 81 | }) 82 | if err != nil { 83 | return settings, err 84 | } 85 | settings = *settingsPtr 86 | switch name { 87 | case types.PrivacySettingTypeGroupAdd: 88 | settings.GroupAdd = value 89 | case types.PrivacySettingTypeLastSeen: 90 | settings.LastSeen = value 91 | case types.PrivacySettingTypeStatus: 92 | settings.Status = value 93 | case types.PrivacySettingTypeProfile: 94 | settings.Profile = value 95 | case types.PrivacySettingTypeReadReceipts: 96 | settings.ReadReceipts = value 97 | case types.PrivacySettingTypeOnline: 98 | settings.Online = value 99 | case types.PrivacySettingTypeCallAdd: 100 | settings.CallAdd = value 101 | } 102 | cli.privacySettingsCache.Store(&settings) 103 | return 104 | } 105 | 106 | // SetDefaultDisappearingTimer will set the default disappearing message timer. 107 | func (cli *Client) SetDefaultDisappearingTimer(timer time.Duration) (err error) { 108 | _, err = cli.sendIQ(infoQuery{ 109 | Namespace: "disappearing_mode", 110 | Type: iqSet, 111 | To: types.ServerJID, 112 | Content: []waBinary.Node{{ 113 | Tag: "disappearing_mode", 114 | Attrs: waBinary.Attrs{ 115 | "duration": strconv.Itoa(int(timer.Seconds())), 116 | }, 117 | }}, 118 | }) 119 | return 120 | } 121 | 122 | func (cli *Client) parsePrivacySettings(privacyNode *waBinary.Node, settings *types.PrivacySettings) *events.PrivacySettings { 123 | var evt events.PrivacySettings 124 | for _, child := range privacyNode.GetChildren() { 125 | if child.Tag != "category" { 126 | continue 127 | } 128 | ag := child.AttrGetter() 129 | name := types.PrivacySettingType(ag.String("name")) 130 | value := types.PrivacySetting(ag.String("value")) 131 | switch name { 132 | case types.PrivacySettingTypeGroupAdd: 133 | settings.GroupAdd = value 134 | evt.GroupAddChanged = true 135 | case types.PrivacySettingTypeLastSeen: 136 | settings.LastSeen = value 137 | evt.LastSeenChanged = true 138 | case types.PrivacySettingTypeStatus: 139 | settings.Status = value 140 | evt.StatusChanged = true 141 | case types.PrivacySettingTypeProfile: 142 | settings.Profile = value 143 | evt.ProfileChanged = true 144 | case types.PrivacySettingTypeReadReceipts: 145 | settings.ReadReceipts = value 146 | evt.ReadReceiptsChanged = true 147 | case types.PrivacySettingTypeOnline: 148 | settings.Online = value 149 | evt.OnlineChanged = true 150 | case types.PrivacySettingTypeCallAdd: 151 | settings.CallAdd = value 152 | evt.CallAddChanged = true 153 | } 154 | } 155 | return &evt 156 | } 157 | 158 | func (cli *Client) handlePrivacySettingsNotification(privacyNode *waBinary.Node) { 159 | cli.Log.Debugf("Parsing privacy settings change notification") 160 | settings, err := cli.TryFetchPrivacySettings(false) 161 | if err != nil { 162 | cli.Log.Errorf("Failed to fetch privacy settings when handling change: %v", err) 163 | return 164 | } 165 | evt := cli.parsePrivacySettings(privacyNode, settings) 166 | // The data isn't be reliable if the fetch failed, so only cache if it didn't fail 167 | if err == nil { 168 | cli.privacySettingsCache.Store(settings) 169 | } 170 | cli.dispatchEvent(evt) 171 | } 172 | -------------------------------------------------------------------------------- /proto/.gitignore: -------------------------------------------------------------------------------- 1 | protos.js 2 | -------------------------------------------------------------------------------- /proto/armadilloutil/decode.go: -------------------------------------------------------------------------------- 1 | package armadilloutil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "google.golang.org/protobuf/proto" 8 | 9 | "github.com/idlethorn/waSocket/proto/waCommon" 10 | ) 11 | 12 | var ErrUnsupportedVersion = errors.New("unsupported subprotocol version") 13 | 14 | func Unmarshal[T proto.Message](into T, msg *waCommon.SubProtocol, expectedVersion int32) (T, error) { 15 | if msg.GetVersion() != expectedVersion { 16 | return into, fmt.Errorf("%w %d in %T (expected %d)", ErrUnsupportedVersion, msg.GetVersion(), into, expectedVersion) 17 | } 18 | 19 | err := proto.Unmarshal(msg.GetPayload(), into) 20 | return into, err 21 | } 22 | 23 | func Marshal[T proto.Message](msg T, version int32) (*waCommon.SubProtocol, error) { 24 | payload, err := proto.Marshal(msg) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &waCommon.SubProtocol{ 29 | Payload: payload, 30 | Version: &version, 31 | }, nil 32 | } 33 | -------------------------------------------------------------------------------- /proto/extra.go: -------------------------------------------------------------------------------- 1 | package armadillo 2 | 3 | import ( 4 | "google.golang.org/protobuf/proto" 5 | 6 | "github.com/idlethorn/waSocket/proto/waArmadilloApplication" 7 | "github.com/idlethorn/waSocket/proto/waCommon" 8 | "github.com/idlethorn/waSocket/proto/waConsumerApplication" 9 | "github.com/idlethorn/waSocket/proto/waMultiDevice" 10 | ) 11 | 12 | type MessageApplicationSub interface { 13 | IsMessageApplicationSub() 14 | } 15 | 16 | type RealMessageApplicationSub interface { 17 | MessageApplicationSub 18 | proto.Message 19 | } 20 | 21 | type Unsupported_BusinessApplication waCommon.SubProtocol 22 | type Unsupported_PaymentApplication waCommon.SubProtocol 23 | type Unsupported_Voip waCommon.SubProtocol 24 | 25 | var ( 26 | _ MessageApplicationSub = (*waConsumerApplication.ConsumerApplication)(nil) // 2 27 | _ MessageApplicationSub = (*Unsupported_BusinessApplication)(nil) // 3 28 | _ MessageApplicationSub = (*Unsupported_PaymentApplication)(nil) // 4 29 | _ MessageApplicationSub = (*waMultiDevice.MultiDevice)(nil) // 5 30 | _ MessageApplicationSub = (*Unsupported_Voip)(nil) // 6 31 | _ MessageApplicationSub = (*waArmadilloApplication.Armadillo)(nil) // 7 32 | ) 33 | 34 | func (*Unsupported_BusinessApplication) IsMessageApplicationSub() {} 35 | func (*Unsupported_PaymentApplication) IsMessageApplicationSub() {} 36 | func (*Unsupported_Voip) IsMessageApplicationSub() {} 37 | -------------------------------------------------------------------------------- /proto/waAdv/WAAdv.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waAdv/WAAdv.pb.raw -------------------------------------------------------------------------------- /proto/waAdv/WAAdv.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAAdv; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waAdv"; 4 | 5 | enum ADVEncryptionType { 6 | E2EE = 0; 7 | HOSTED = 1; 8 | } 9 | 10 | message ADVKeyIndexList { 11 | optional uint32 rawID = 1; 12 | optional uint64 timestamp = 2; 13 | optional uint32 currentIndex = 3; 14 | repeated uint32 validIndexes = 4 [packed=true]; 15 | optional ADVEncryptionType accountType = 5; 16 | } 17 | 18 | message ADVSignedKeyIndexList { 19 | optional bytes details = 1; 20 | optional bytes accountSignature = 2; 21 | optional bytes accountSignatureKey = 3; 22 | } 23 | 24 | message ADVDeviceIdentity { 25 | optional uint32 rawID = 1; 26 | optional uint64 timestamp = 2; 27 | optional uint32 keyIndex = 3; 28 | optional ADVEncryptionType accountType = 4; 29 | optional ADVEncryptionType deviceType = 5; 30 | } 31 | 32 | message ADVSignedDeviceIdentity { 33 | optional bytes details = 1; 34 | optional bytes accountSignatureKey = 2; 35 | optional bytes accountSignature = 3; 36 | optional bytes deviceSignature = 4; 37 | } 38 | 39 | message ADVSignedDeviceIdentityHMAC { 40 | optional bytes details = 1; 41 | optional bytes HMAC = 2; 42 | optional ADVEncryptionType accountType = 3; 43 | } 44 | -------------------------------------------------------------------------------- /proto/waArmadilloApplication/WAArmadilloApplication.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waArmadilloApplication/WAArmadilloApplication.pb.raw -------------------------------------------------------------------------------- /proto/waArmadilloApplication/extra.go: -------------------------------------------------------------------------------- 1 | package waArmadilloApplication 2 | 3 | func (*Armadillo) IsMessageApplicationSub() {} 4 | -------------------------------------------------------------------------------- /proto/waArmadilloBackupCommon/WAArmadilloBackupCommon.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.1 4 | // protoc v3.21.12 5 | // source: waArmadilloBackupCommon/WAArmadilloBackupCommon.proto 6 | 7 | package waArmadilloBackupCommon 8 | 9 | import ( 10 | reflect "reflect" 11 | sync "sync" 12 | 13 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 14 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 15 | 16 | _ "embed" 17 | ) 18 | 19 | const ( 20 | // Verify that this generated code is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 22 | // Verify that runtime/protoimpl is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 24 | ) 25 | 26 | type Subprotocol struct { 27 | state protoimpl.MessageState `protogen:"open.v1"` 28 | Payload []byte `protobuf:"bytes,1,opt,name=payload" json:"payload,omitempty"` 29 | Version *int32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"` 30 | unknownFields protoimpl.UnknownFields 31 | sizeCache protoimpl.SizeCache 32 | } 33 | 34 | func (x *Subprotocol) Reset() { 35 | *x = Subprotocol{} 36 | mi := &file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_msgTypes[0] 37 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 38 | ms.StoreMessageInfo(mi) 39 | } 40 | 41 | func (x *Subprotocol) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*Subprotocol) ProtoMessage() {} 46 | 47 | func (x *Subprotocol) ProtoReflect() protoreflect.Message { 48 | mi := &file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_msgTypes[0] 49 | if x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use Subprotocol.ProtoReflect.Descriptor instead. 60 | func (*Subprotocol) Descriptor() ([]byte, []int) { 61 | return file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *Subprotocol) GetPayload() []byte { 65 | if x != nil { 66 | return x.Payload 67 | } 68 | return nil 69 | } 70 | 71 | func (x *Subprotocol) GetVersion() int32 { 72 | if x != nil && x.Version != nil { 73 | return *x.Version 74 | } 75 | return 0 76 | } 77 | 78 | var File_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto protoreflect.FileDescriptor 79 | 80 | //go:embed WAArmadilloBackupCommon.pb.raw 81 | var file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDesc []byte 82 | 83 | var ( 84 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescOnce sync.Once 85 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescData = file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDesc 86 | ) 87 | 88 | func file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescGZIP() []byte { 89 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescOnce.Do(func() { 90 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescData = protoimpl.X.CompressGZIP(file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescData) 91 | }) 92 | return file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDescData 93 | } 94 | 95 | var file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 96 | var file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_goTypes = []any{ 97 | (*Subprotocol)(nil), // 0: WAArmadilloBackupCommon.Subprotocol 98 | } 99 | var file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_depIdxs = []int32{ 100 | 0, // [0:0] is the sub-list for method output_type 101 | 0, // [0:0] is the sub-list for method input_type 102 | 0, // [0:0] is the sub-list for extension type_name 103 | 0, // [0:0] is the sub-list for extension extendee 104 | 0, // [0:0] is the sub-list for field type_name 105 | } 106 | 107 | func init() { file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_init() } 108 | func file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_init() { 109 | if File_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto != nil { 110 | return 111 | } 112 | type x struct{} 113 | out := protoimpl.TypeBuilder{ 114 | File: protoimpl.DescBuilder{ 115 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 116 | RawDescriptor: file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDesc, 117 | NumEnums: 0, 118 | NumMessages: 1, 119 | NumExtensions: 0, 120 | NumServices: 0, 121 | }, 122 | GoTypes: file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_goTypes, 123 | DependencyIndexes: file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_depIdxs, 124 | MessageInfos: file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_msgTypes, 125 | }.Build() 126 | File_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto = out.File 127 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_rawDesc = nil 128 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_goTypes = nil 129 | file_waArmadilloBackupCommon_WAArmadilloBackupCommon_proto_depIdxs = nil 130 | } 131 | -------------------------------------------------------------------------------- /proto/waArmadilloBackupCommon/WAArmadilloBackupCommon.pb.raw: -------------------------------------------------------------------------------- 1 | 2 | 5waArmadilloBackupCommon/WAArmadilloBackupCommon.protoWAArmadilloBackupCommon"A 3 | Subprotocol 4 | payload ( Rpayload 5 | version (RversionB3Z1go.mau.fi/whatsmeow/proto/waArmadilloBackupCommon -------------------------------------------------------------------------------- /proto/waArmadilloBackupCommon/WAArmadilloBackupCommon.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAArmadilloBackupCommon; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waArmadilloBackupCommon"; 4 | 5 | message Subprotocol { 6 | optional bytes payload = 1; 7 | optional int32 version = 2; 8 | } 9 | -------------------------------------------------------------------------------- /proto/waArmadilloBackupMessage/WAArmadilloBackupMessage.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waArmadilloBackupMessage/WAArmadilloBackupMessage.pb.raw -------------------------------------------------------------------------------- /proto/waArmadilloBackupMessage/WAArmadilloBackupMessage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAArmadilloBackupMessage; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waArmadilloBackupMessage"; 4 | 5 | import "waArmadilloBackupCommon/WAArmadilloBackupCommon.proto"; 6 | 7 | message BackupMessage { 8 | message Metadata { 9 | message FrankingMetadata { 10 | optional bytes frankingTag = 3; 11 | optional bytes reportingTag = 4; 12 | } 13 | 14 | optional string senderID = 1; 15 | optional string messageID = 2; 16 | optional int64 timestampMS = 3; 17 | optional FrankingMetadata frankingMetadata = 4; 18 | optional int32 payloadVersion = 5; 19 | optional int32 futureProofBehavior = 6; 20 | optional int32 threadTypeTag = 7; 21 | } 22 | 23 | oneof payload { 24 | bytes encryptedTransportMessage = 2; 25 | WAArmadilloBackupCommon.Subprotocol encryptedTransportEvent = 5; 26 | WAArmadilloBackupCommon.Subprotocol encryptedTransportLocallyTransformedMessage = 6; 27 | } 28 | 29 | optional Metadata metadata = 1; 30 | } 31 | -------------------------------------------------------------------------------- /proto/waArmadilloICDC/WAArmadilloICDC.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waArmadilloICDC/WAArmadilloICDC.pb.raw -------------------------------------------------------------------------------- /proto/waArmadilloICDC/WAArmadilloICDC.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAArmadilloICDC; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waArmadilloICDC"; 4 | 5 | message ICDCIdentityList { 6 | optional int32 seq = 1; 7 | optional int64 timestamp = 2; 8 | repeated bytes devices = 3; 9 | optional int32 signingDeviceIndex = 4; 10 | } 11 | 12 | message SignedICDCIdentityList { 13 | optional bytes details = 1; 14 | optional bytes signature = 2; 15 | } 16 | -------------------------------------------------------------------------------- /proto/waArmadilloTransportEvent/WAArmadilloTransportEvent.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waArmadilloTransportEvent/WAArmadilloTransportEvent.pb.raw -------------------------------------------------------------------------------- /proto/waArmadilloTransportEvent/WAArmadilloTransportEvent.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAArmadilloTransportEvent; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waArmadilloTransportEvent"; 4 | 5 | message TransportEvent { 6 | message Event { 7 | message IcdcAlert { 8 | enum Type { 9 | NONE = 0; 10 | DETECTED = 1; 11 | CLEARED = 2; 12 | } 13 | 14 | optional Type type = 1; 15 | } 16 | 17 | message DeviceChange { 18 | enum Type { 19 | NONE = 0; 20 | ADDED = 1; 21 | REMOVED = 2; 22 | REPLACED = 3; 23 | } 24 | 25 | optional Type type = 1; 26 | optional string deviceName = 2; 27 | } 28 | 29 | oneof event { 30 | DeviceChange deviceChange = 1; 31 | IcdcAlert icdcAlert = 2; 32 | } 33 | } 34 | 35 | message Placeholder { 36 | enum Type { 37 | DECRYPTION_FAILURE = 1; 38 | UNAVAILABLE_MESSAGE = 2; 39 | } 40 | 41 | optional Type type = 1; 42 | } 43 | 44 | oneof content { 45 | Placeholder placeholder = 1; 46 | Event event = 2; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /proto/waArmadilloXMA/WAArmadilloXMA.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waArmadilloXMA/WAArmadilloXMA.pb.raw -------------------------------------------------------------------------------- /proto/waArmadilloXMA/WAArmadilloXMA.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAArmadilloXMA; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waArmadilloXMA"; 4 | 5 | import "waCommon/WACommon.proto"; 6 | 7 | message ExtendedContentMessage { 8 | enum OverlayIconGlyph { 9 | INFO = 0; 10 | EYE_OFF = 1; 11 | NEWS_OFF = 2; 12 | WARNING = 3; 13 | PRIVATE = 4; 14 | NONE = 5; 15 | MEDIA_LABEL = 6; 16 | POST_COVER = 7; 17 | POST_LABEL = 8; 18 | WARNING_SCREENS = 9; 19 | } 20 | 21 | enum CtaButtonType { 22 | OPEN_NATIVE = 11; 23 | } 24 | 25 | enum XmaLayoutType { 26 | SINGLE = 0; 27 | HSCROLL = 1; 28 | PORTRAIT = 3; 29 | STANDARD_DXMA = 12; 30 | LIST_DXMA = 15; 31 | GRID = 16; 32 | } 33 | 34 | enum ExtendedContentType { 35 | UNSUPPORTED = 0; 36 | IG_STORY_PHOTO_MENTION = 4; 37 | IG_SINGLE_IMAGE_POST_SHARE = 9; 38 | IG_MULTIPOST_SHARE = 10; 39 | IG_SINGLE_VIDEO_POST_SHARE = 11; 40 | IG_STORY_PHOTO_SHARE = 12; 41 | IG_STORY_VIDEO_SHARE = 13; 42 | IG_CLIPS_SHARE = 14; 43 | IG_IGTV_SHARE = 15; 44 | IG_SHOP_SHARE = 16; 45 | IG_PROFILE_SHARE = 19; 46 | IG_STORY_PHOTO_HIGHLIGHT_SHARE = 20; 47 | IG_STORY_VIDEO_HIGHLIGHT_SHARE = 21; 48 | IG_STORY_REPLY = 22; 49 | IG_STORY_REACTION = 23; 50 | IG_STORY_VIDEO_MENTION = 24; 51 | IG_STORY_HIGHLIGHT_REPLY = 25; 52 | IG_STORY_HIGHLIGHT_REACTION = 26; 53 | IG_EXTERNAL_LINK = 27; 54 | IG_RECEIVER_FETCH = 28; 55 | FB_FEED_SHARE = 1000; 56 | FB_STORY_REPLY = 1001; 57 | FB_STORY_SHARE = 1002; 58 | FB_STORY_MENTION = 1003; 59 | FB_FEED_VIDEO_SHARE = 1004; 60 | FB_GAMING_CUSTOM_UPDATE = 1005; 61 | FB_PRODUCER_STORY_REPLY = 1006; 62 | FB_EVENT = 1007; 63 | FB_FEED_POST_PRIVATE_REPLY = 1008; 64 | FB_SHORT = 1009; 65 | FB_COMMENT_MENTION_SHARE = 1010; 66 | FB_POST_MENTION = 1011; 67 | MSG_EXTERNAL_LINK_SHARE = 2000; 68 | MSG_P2P_PAYMENT = 2001; 69 | MSG_LOCATION_SHARING = 2002; 70 | MSG_LOCATION_SHARING_V2 = 2003; 71 | MSG_HIGHLIGHTS_TAB_FRIEND_UPDATES_REPLY = 2004; 72 | MSG_HIGHLIGHTS_TAB_LOCAL_EVENT_REPLY = 2005; 73 | MSG_RECEIVER_FETCH = 2006; 74 | MSG_IG_MEDIA_SHARE = 2007; 75 | MSG_GEN_AI_SEARCH_PLUGIN_RESPONSE = 2008; 76 | MSG_REELS_LIST = 2009; 77 | MSG_CONTACT = 2010; 78 | MSG_THREADS_POST_SHARE = 2011; 79 | MSG_FILE = 2012; 80 | MSG_AVATAR_DETAILS = 2013; 81 | MSG_AI_CONTACT = 2014; 82 | MSG_MEMORIES_SHARE = 2015; 83 | MSG_SHARED_ALBUM_REPLY = 2016; 84 | MSG_SHARED_ALBUM = 2017; 85 | MSG_OCCAMADILLO_XMA = 2018; 86 | MSG_GEN_AI_SUBSCRIPTION = 2021; 87 | MSG_GEN_AI_REMINDER = 2022; 88 | MSG_GEN_AI_MEMU_ONBOARDING_RESPONSE = 2023; 89 | MSG_NOTE_REPLY = 2024; 90 | MSG_NOTE_MENTION = 2025; 91 | RTC_AUDIO_CALL = 3000; 92 | RTC_VIDEO_CALL = 3001; 93 | RTC_MISSED_AUDIO_CALL = 3002; 94 | RTC_MISSED_VIDEO_CALL = 3003; 95 | RTC_GROUP_AUDIO_CALL = 3004; 96 | RTC_GROUP_VIDEO_CALL = 3005; 97 | RTC_MISSED_GROUP_AUDIO_CALL = 3006; 98 | RTC_MISSED_GROUP_VIDEO_CALL = 3007; 99 | RTC_ONGOING_AUDIO_CALL = 3008; 100 | RTC_ONGOING_VIDEO_CALL = 3009; 101 | MSG_RECEIVER_FETCH_FALLBACK = 3025; 102 | DATACLASS_SENDER_COPY = 4000; 103 | } 104 | 105 | message CTA { 106 | optional CtaButtonType buttonType = 1; 107 | optional string title = 2; 108 | optional string actionURL = 3; 109 | optional string nativeURL = 4; 110 | optional string ctaType = 5; 111 | optional string actionContentBlob = 6; 112 | } 113 | 114 | optional WACommon.SubProtocol associatedMessage = 1; 115 | optional ExtendedContentType targetType = 2; 116 | optional string targetUsername = 3; 117 | optional string targetID = 4; 118 | optional int64 targetExpiringAtSec = 5; 119 | optional XmaLayoutType xmaLayoutType = 6; 120 | repeated CTA ctas = 7; 121 | repeated WACommon.SubProtocol previews = 8; 122 | optional string titleText = 9; 123 | optional string subtitleText = 10; 124 | optional uint32 maxTitleNumOfLines = 11; 125 | optional uint32 maxSubtitleNumOfLines = 12; 126 | optional WACommon.SubProtocol favicon = 13; 127 | optional WACommon.SubProtocol headerImage = 14; 128 | optional string headerTitle = 15; 129 | optional OverlayIconGlyph overlayIconGlyph = 16; 130 | optional string overlayTitle = 17; 131 | optional string overlayDescription = 18; 132 | optional string sentWithMessageID = 19; 133 | optional string messageText = 20; 134 | optional string headerSubtitle = 21; 135 | optional string xmaDataclass = 22; 136 | optional string contentRef = 23; 137 | repeated string mentionedJID = 24; 138 | repeated WACommon.Command commands = 25; 139 | repeated WACommon.Mention mentions = 26; 140 | } 141 | -------------------------------------------------------------------------------- /proto/waCert/WACert.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waCert/WACert.pb.raw -------------------------------------------------------------------------------- /proto/waCert/WACert.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WACert; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waCert"; 4 | 5 | message NoiseCertificate { 6 | message Details { 7 | optional uint32 serial = 1; 8 | optional string issuer = 2; 9 | optional uint64 expires = 3; 10 | optional string subject = 4; 11 | optional bytes key = 5; 12 | } 13 | 14 | optional bytes details = 1; 15 | optional bytes signature = 2; 16 | } 17 | 18 | message CertChain { 19 | message NoiseCertificate { 20 | message Details { 21 | optional uint32 serial = 1; 22 | optional uint32 issuerSerial = 2; 23 | optional bytes key = 3; 24 | optional uint64 notBefore = 4; 25 | optional uint64 notAfter = 5; 26 | } 27 | 28 | optional bytes details = 1; 29 | optional bytes signature = 2; 30 | } 31 | 32 | optional NoiseCertificate leaf = 1; 33 | optional NoiseCertificate intermediate = 2; 34 | } 35 | -------------------------------------------------------------------------------- /proto/waChatLockSettings/WAProtobufsChatLockSettings.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.1 4 | // protoc v3.21.12 5 | // source: waChatLockSettings/WAProtobufsChatLockSettings.proto 6 | 7 | package waChatLockSettings 8 | 9 | import ( 10 | reflect "reflect" 11 | sync "sync" 12 | 13 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 14 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 15 | 16 | waUserPassword "github.com/idlethorn/waSocket/proto/waUserPassword" 17 | 18 | _ "embed" 19 | ) 20 | 21 | const ( 22 | // Verify that this generated code is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 24 | // Verify that runtime/protoimpl is sufficiently up-to-date. 25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 26 | ) 27 | 28 | type ChatLockSettings struct { 29 | state protoimpl.MessageState `protogen:"open.v1"` 30 | HideLockedChats *bool `protobuf:"varint,1,opt,name=hideLockedChats" json:"hideLockedChats,omitempty"` 31 | SecretCode *waUserPassword.UserPassword `protobuf:"bytes,2,opt,name=secretCode" json:"secretCode,omitempty"` 32 | unknownFields protoimpl.UnknownFields 33 | sizeCache protoimpl.SizeCache 34 | } 35 | 36 | func (x *ChatLockSettings) Reset() { 37 | *x = ChatLockSettings{} 38 | mi := &file_waChatLockSettings_WAProtobufsChatLockSettings_proto_msgTypes[0] 39 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 40 | ms.StoreMessageInfo(mi) 41 | } 42 | 43 | func (x *ChatLockSettings) String() string { 44 | return protoimpl.X.MessageStringOf(x) 45 | } 46 | 47 | func (*ChatLockSettings) ProtoMessage() {} 48 | 49 | func (x *ChatLockSettings) ProtoReflect() protoreflect.Message { 50 | mi := &file_waChatLockSettings_WAProtobufsChatLockSettings_proto_msgTypes[0] 51 | if x != nil { 52 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 53 | if ms.LoadMessageInfo() == nil { 54 | ms.StoreMessageInfo(mi) 55 | } 56 | return ms 57 | } 58 | return mi.MessageOf(x) 59 | } 60 | 61 | // Deprecated: Use ChatLockSettings.ProtoReflect.Descriptor instead. 62 | func (*ChatLockSettings) Descriptor() ([]byte, []int) { 63 | return file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescGZIP(), []int{0} 64 | } 65 | 66 | func (x *ChatLockSettings) GetHideLockedChats() bool { 67 | if x != nil && x.HideLockedChats != nil { 68 | return *x.HideLockedChats 69 | } 70 | return false 71 | } 72 | 73 | func (x *ChatLockSettings) GetSecretCode() *waUserPassword.UserPassword { 74 | if x != nil { 75 | return x.SecretCode 76 | } 77 | return nil 78 | } 79 | 80 | var File_waChatLockSettings_WAProtobufsChatLockSettings_proto protoreflect.FileDescriptor 81 | 82 | //go:embed WAProtobufsChatLockSettings.pb.raw 83 | var file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDesc []byte 84 | 85 | var ( 86 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescOnce sync.Once 87 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescData = file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDesc 88 | ) 89 | 90 | func file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescGZIP() []byte { 91 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescOnce.Do(func() { 92 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescData = protoimpl.X.CompressGZIP(file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescData) 93 | }) 94 | return file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDescData 95 | } 96 | 97 | var file_waChatLockSettings_WAProtobufsChatLockSettings_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 98 | var file_waChatLockSettings_WAProtobufsChatLockSettings_proto_goTypes = []any{ 99 | (*ChatLockSettings)(nil), // 0: WAProtobufsChatLockSettings.ChatLockSettings 100 | (*waUserPassword.UserPassword)(nil), // 1: WAProtobufsUserPassword.UserPassword 101 | } 102 | var file_waChatLockSettings_WAProtobufsChatLockSettings_proto_depIdxs = []int32{ 103 | 1, // 0: WAProtobufsChatLockSettings.ChatLockSettings.secretCode:type_name -> WAProtobufsUserPassword.UserPassword 104 | 1, // [1:1] is the sub-list for method output_type 105 | 1, // [1:1] is the sub-list for method input_type 106 | 1, // [1:1] is the sub-list for extension type_name 107 | 1, // [1:1] is the sub-list for extension extendee 108 | 0, // [0:1] is the sub-list for field type_name 109 | } 110 | 111 | func init() { file_waChatLockSettings_WAProtobufsChatLockSettings_proto_init() } 112 | func file_waChatLockSettings_WAProtobufsChatLockSettings_proto_init() { 113 | if File_waChatLockSettings_WAProtobufsChatLockSettings_proto != nil { 114 | return 115 | } 116 | type x struct{} 117 | out := protoimpl.TypeBuilder{ 118 | File: protoimpl.DescBuilder{ 119 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 120 | RawDescriptor: file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDesc, 121 | NumEnums: 0, 122 | NumMessages: 1, 123 | NumExtensions: 0, 124 | NumServices: 0, 125 | }, 126 | GoTypes: file_waChatLockSettings_WAProtobufsChatLockSettings_proto_goTypes, 127 | DependencyIndexes: file_waChatLockSettings_WAProtobufsChatLockSettings_proto_depIdxs, 128 | MessageInfos: file_waChatLockSettings_WAProtobufsChatLockSettings_proto_msgTypes, 129 | }.Build() 130 | File_waChatLockSettings_WAProtobufsChatLockSettings_proto = out.File 131 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_rawDesc = nil 132 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_goTypes = nil 133 | file_waChatLockSettings_WAProtobufsChatLockSettings_proto_depIdxs = nil 134 | } 135 | -------------------------------------------------------------------------------- /proto/waChatLockSettings/WAProtobufsChatLockSettings.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waChatLockSettings/WAProtobufsChatLockSettings.pb.raw -------------------------------------------------------------------------------- /proto/waChatLockSettings/WAProtobufsChatLockSettings.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAProtobufsChatLockSettings; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waChatLockSettings"; 4 | 5 | import "waUserPassword/WAProtobufsUserPassword.proto"; 6 | 7 | message ChatLockSettings { 8 | optional bool hideLockedChats = 1; 9 | optional WAProtobufsUserPassword.UserPassword secretCode = 2; 10 | } 11 | -------------------------------------------------------------------------------- /proto/waCommon/WACommon.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waCommon/WACommon.pb.raw -------------------------------------------------------------------------------- /proto/waCommon/WACommon.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WACommon; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waCommon"; 4 | 5 | enum FutureProofBehavior { 6 | PLACEHOLDER = 0; 7 | NO_PLACEHOLDER = 1; 8 | IGNORE = 2; 9 | } 10 | 11 | message MessageKey { 12 | optional string remoteJID = 1; 13 | optional bool fromMe = 2; 14 | optional string ID = 3; 15 | optional string participant = 4; 16 | } 17 | 18 | message Command { 19 | enum CommandType { 20 | EVERYONE = 1; 21 | SILENT = 2; 22 | AI = 3; 23 | AI_IMAGINE = 4; 24 | } 25 | 26 | optional CommandType commandType = 1; 27 | optional uint32 offset = 2; 28 | optional uint32 length = 3; 29 | optional string validationToken = 4; 30 | } 31 | 32 | message Mention { 33 | enum MentionType { 34 | PROFILE = 0; 35 | } 36 | 37 | optional MentionType mentionType = 1; 38 | optional string mentionedJID = 2; 39 | optional uint32 offset = 3; 40 | optional uint32 length = 4; 41 | } 42 | 43 | message MessageText { 44 | optional string text = 1; 45 | repeated string mentionedJID = 2; 46 | repeated Command commands = 3; 47 | repeated Mention mentions = 4; 48 | } 49 | 50 | message SubProtocol { 51 | optional bytes payload = 1; 52 | optional int32 version = 2; 53 | } 54 | 55 | message LimitSharing { 56 | enum Trigger { 57 | UNKNOWN = 0; 58 | CHAT_SETTING = 1; 59 | BIZ_SUPPORTS_FB_HOSTING = 2; 60 | UNKNOWN_GROUP = 3; 61 | } 62 | 63 | optional bool sharingLimited = 1; 64 | optional Trigger trigger = 2; 65 | optional int64 limitSharingSettingTimestamp = 3; 66 | optional bool initiatedByMe = 4; 67 | } 68 | -------------------------------------------------------------------------------- /proto/waCommon/legacy.go: -------------------------------------------------------------------------------- 1 | package waCommon 2 | 3 | // Deprecated: Use GetID 4 | func (x *MessageKey) GetId() string { 5 | return x.GetID() 6 | } 7 | 8 | // Deprecated: Use GetRemoteJID 9 | func (x *MessageKey) GetRemoteJid() string { 10 | return x.GetRemoteJID() 11 | } 12 | -------------------------------------------------------------------------------- /proto/waCompanionReg/WACompanionReg.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waCompanionReg/WACompanionReg.pb.raw -------------------------------------------------------------------------------- /proto/waCompanionReg/WACompanionReg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WACompanionReg; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waCompanionReg"; 4 | 5 | message DeviceProps { 6 | enum PlatformType { 7 | UNKNOWN = 0; 8 | CHROME = 1; 9 | FIREFOX = 2; 10 | IE = 3; 11 | OPERA = 4; 12 | SAFARI = 5; 13 | EDGE = 6; 14 | DESKTOP = 7; 15 | IPAD = 8; 16 | ANDROID_TABLET = 9; 17 | OHANA = 10; 18 | ALOHA = 11; 19 | CATALINA = 12; 20 | TCL_TV = 13; 21 | IOS_PHONE = 14; 22 | IOS_CATALYST = 15; 23 | ANDROID_PHONE = 16; 24 | ANDROID_AMBIGUOUS = 17; 25 | WEAR_OS = 18; 26 | AR_WRIST = 19; 27 | AR_DEVICE = 20; 28 | UWP = 21; 29 | VR = 22; 30 | CLOUD_API = 23; 31 | SMARTGLASSES = 24; 32 | } 33 | 34 | message HistorySyncConfig { 35 | optional uint32 fullSyncDaysLimit = 1; 36 | optional uint32 fullSyncSizeMbLimit = 2; 37 | optional uint32 storageQuotaMb = 3; 38 | optional bool inlineInitialPayloadInE2EeMsg = 4; 39 | optional uint32 recentSyncDaysLimit = 5; 40 | optional bool supportCallLogHistory = 6; 41 | optional bool supportBotUserAgentChatHistory = 7; 42 | optional bool supportCagReactionsAndPolls = 8; 43 | optional bool supportBizHostedMsg = 9; 44 | optional bool supportRecentSyncChunkMessageCountTuning = 10; 45 | optional bool supportHostedGroupMsg = 11; 46 | optional bool supportFbidBotChatHistory = 12; 47 | optional bool supportAddOnHistorySyncMigration = 13; 48 | optional bool supportMessageAssociation = 14; 49 | } 50 | 51 | message AppVersion { 52 | optional uint32 primary = 1; 53 | optional uint32 secondary = 2; 54 | optional uint32 tertiary = 3; 55 | optional uint32 quaternary = 4; 56 | optional uint32 quinary = 5; 57 | } 58 | 59 | optional string os = 1; 60 | optional AppVersion version = 2; 61 | optional PlatformType platformType = 3; 62 | optional bool requireFullSync = 4; 63 | optional HistorySyncConfig historySyncConfig = 5; 64 | } 65 | 66 | message CompanionEphemeralIdentity { 67 | optional bytes publicKey = 1; 68 | optional DeviceProps.PlatformType deviceType = 2; 69 | optional string ref = 3; 70 | } 71 | 72 | message CompanionCommitment { 73 | optional bytes hash = 1; 74 | } 75 | 76 | message ProloguePayload { 77 | optional bytes companionEphemeralIdentity = 1; 78 | optional CompanionCommitment commitment = 2; 79 | } 80 | 81 | message PrimaryEphemeralIdentity { 82 | optional bytes publicKey = 1; 83 | optional bytes nonce = 2; 84 | } 85 | 86 | message PairingRequest { 87 | optional bytes companionPublicKey = 1; 88 | optional bytes companionIdentityKey = 2; 89 | optional bytes advSecret = 3; 90 | } 91 | 92 | message EncryptedPairingRequest { 93 | optional bytes encryptedPayload = 1; 94 | optional bytes IV = 2; 95 | } 96 | 97 | message ClientPairingProps { 98 | optional bool isChatDbLidMigrated = 1; 99 | optional bool isSyncdPureLidSession = 2; 100 | } 101 | -------------------------------------------------------------------------------- /proto/waConsumerApplication/WAConsumerApplication.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waConsumerApplication/WAConsumerApplication.pb.raw -------------------------------------------------------------------------------- /proto/waConsumerApplication/WAConsumerApplication.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAConsumerApplication; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waConsumerApplication"; 4 | 5 | import "waCommon/WACommon.proto"; 6 | 7 | message ConsumerApplication { 8 | message Payload { 9 | oneof payload { 10 | Content content = 1; 11 | ApplicationData applicationData = 2; 12 | Signal signal = 3; 13 | SubProtocolPayload subProtocol = 4; 14 | } 15 | } 16 | 17 | message SubProtocolPayload { 18 | optional WACommon.FutureProofBehavior futureProof = 1; 19 | } 20 | 21 | message Metadata { 22 | enum SpecialTextSize { 23 | SMALL = 1; 24 | MEDIUM = 2; 25 | LARGE = 3; 26 | } 27 | 28 | optional SpecialTextSize specialTextSize = 1; 29 | } 30 | 31 | message Signal { 32 | } 33 | 34 | message ApplicationData { 35 | oneof applicationContent { 36 | RevokeMessage revoke = 1; 37 | } 38 | } 39 | 40 | message Content { 41 | oneof content { 42 | WACommon.MessageText messageText = 1; 43 | ImageMessage imageMessage = 2; 44 | ContactMessage contactMessage = 3; 45 | LocationMessage locationMessage = 4; 46 | ExtendedTextMessage extendedTextMessage = 5; 47 | StatusTextMesage statusTextMessage = 6; 48 | DocumentMessage documentMessage = 7; 49 | AudioMessage audioMessage = 8; 50 | VideoMessage videoMessage = 9; 51 | ContactsArrayMessage contactsArrayMessage = 10; 52 | LiveLocationMessage liveLocationMessage = 11; 53 | StickerMessage stickerMessage = 12; 54 | GroupInviteMessage groupInviteMessage = 13; 55 | ViewOnceMessage viewOnceMessage = 14; 56 | ReactionMessage reactionMessage = 16; 57 | PollCreationMessage pollCreationMessage = 17; 58 | PollUpdateMessage pollUpdateMessage = 18; 59 | EditMessage editMessage = 19; 60 | } 61 | } 62 | 63 | message EditMessage { 64 | optional WACommon.MessageKey key = 1; 65 | optional WACommon.MessageText message = 2; 66 | optional int64 timestampMS = 3; 67 | } 68 | 69 | message PollAddOptionMessage { 70 | repeated Option pollOption = 1; 71 | } 72 | 73 | message PollVoteMessage { 74 | repeated bytes selectedOptions = 1; 75 | optional int64 senderTimestampMS = 2; 76 | } 77 | 78 | message PollEncValue { 79 | optional bytes encPayload = 1; 80 | optional bytes encIV = 2; 81 | } 82 | 83 | message PollUpdateMessage { 84 | optional WACommon.MessageKey pollCreationMessageKey = 1; 85 | optional PollEncValue vote = 2; 86 | optional PollEncValue addOption = 3; 87 | } 88 | 89 | message PollCreationMessage { 90 | optional bytes encKey = 1; 91 | optional string name = 2; 92 | repeated Option options = 3; 93 | optional uint32 selectableOptionsCount = 4; 94 | } 95 | 96 | message Option { 97 | optional string optionName = 1; 98 | } 99 | 100 | message ReactionMessage { 101 | optional WACommon.MessageKey key = 1; 102 | optional string text = 2; 103 | optional string groupingKey = 3; 104 | optional int64 senderTimestampMS = 4; 105 | optional string reactionMetadataDataclassData = 5; 106 | optional int32 style = 6; 107 | } 108 | 109 | message RevokeMessage { 110 | optional WACommon.MessageKey key = 1; 111 | } 112 | 113 | message ViewOnceMessage { 114 | oneof viewOnceContent { 115 | ImageMessage imageMessage = 1; 116 | VideoMessage videoMessage = 2; 117 | } 118 | } 119 | 120 | message GroupInviteMessage { 121 | optional string groupJID = 1; 122 | optional string inviteCode = 2; 123 | optional int64 inviteExpiration = 3; 124 | optional string groupName = 4; 125 | optional bytes JPEGThumbnail = 5; 126 | optional WACommon.MessageText caption = 6; 127 | } 128 | 129 | message LiveLocationMessage { 130 | optional Location location = 1; 131 | optional uint32 accuracyInMeters = 2; 132 | optional float speedInMps = 3; 133 | optional uint32 degreesClockwiseFromMagneticNorth = 4; 134 | optional WACommon.MessageText caption = 5; 135 | optional int64 sequenceNumber = 6; 136 | optional uint32 timeOffset = 7; 137 | } 138 | 139 | message ContactsArrayMessage { 140 | optional string displayName = 1; 141 | repeated ContactMessage contacts = 2; 142 | } 143 | 144 | message ContactMessage { 145 | optional WACommon.SubProtocol contact = 1; 146 | } 147 | 148 | message StatusTextMesage { 149 | enum FontType { 150 | SANS_SERIF = 0; 151 | SERIF = 1; 152 | NORICAN_REGULAR = 2; 153 | BRYNDAN_WRITE = 3; 154 | BEBASNEUE_REGULAR = 4; 155 | OSWALD_HEAVY = 5; 156 | } 157 | 158 | optional ExtendedTextMessage text = 1; 159 | optional fixed32 textArgb = 6; 160 | optional fixed32 backgroundArgb = 7; 161 | optional FontType font = 8; 162 | } 163 | 164 | message ExtendedTextMessage { 165 | enum PreviewType { 166 | NONE = 0; 167 | VIDEO = 1; 168 | } 169 | 170 | optional WACommon.MessageText text = 1; 171 | optional string matchedText = 2; 172 | optional string canonicalURL = 3; 173 | optional string description = 4; 174 | optional string title = 5; 175 | optional WACommon.SubProtocol thumbnail = 6; 176 | optional PreviewType previewType = 7; 177 | } 178 | 179 | message LocationMessage { 180 | optional Location location = 1; 181 | optional string address = 2; 182 | } 183 | 184 | message StickerMessage { 185 | optional WACommon.SubProtocol sticker = 1; 186 | } 187 | 188 | message DocumentMessage { 189 | optional WACommon.SubProtocol document = 1; 190 | optional string fileName = 2; 191 | } 192 | 193 | message VideoMessage { 194 | optional WACommon.SubProtocol video = 1; 195 | optional WACommon.MessageText caption = 2; 196 | } 197 | 198 | message AudioMessage { 199 | optional WACommon.SubProtocol audio = 1; 200 | optional bool PTT = 2; 201 | } 202 | 203 | message ImageMessage { 204 | optional WACommon.SubProtocol image = 1; 205 | optional WACommon.MessageText caption = 2; 206 | } 207 | 208 | message InteractiveAnnotation { 209 | oneof action { 210 | Location location = 2; 211 | } 212 | 213 | repeated Point polygonVertices = 1; 214 | } 215 | 216 | message Point { 217 | optional double x = 1; 218 | optional double y = 2; 219 | } 220 | 221 | message Location { 222 | optional double degreesLatitude = 1; 223 | optional double degreesLongitude = 2; 224 | optional string name = 3; 225 | } 226 | 227 | message MediaPayload { 228 | optional WACommon.SubProtocol protocol = 1; 229 | } 230 | 231 | optional Payload payload = 1; 232 | optional Metadata metadata = 2; 233 | } 234 | -------------------------------------------------------------------------------- /proto/waConsumerApplication/extra.go: -------------------------------------------------------------------------------- 1 | package waConsumerApplication 2 | 3 | import ( 4 | "github.com/idlethorn/waSocket/proto/armadilloutil" 5 | "github.com/idlethorn/waSocket/proto/waMediaTransport" 6 | ) 7 | 8 | type ConsumerApplication_Content_Content = isConsumerApplication_Content_Content 9 | 10 | func (*ConsumerApplication) IsMessageApplicationSub() {} 11 | 12 | const ( 13 | ImageTransportVersion = 1 14 | StickerTransportVersion = 1 15 | VideoTransportVersion = 1 16 | AudioTransportVersion = 1 17 | DocumentTransportVersion = 1 18 | ContactTransportVersion = 1 19 | ) 20 | 21 | func (msg *ConsumerApplication_ImageMessage) Decode() (dec *waMediaTransport.ImageTransport, err error) { 22 | return armadilloutil.Unmarshal(&waMediaTransport.ImageTransport{}, msg.GetImage(), ImageTransportVersion) 23 | } 24 | 25 | func (msg *ConsumerApplication_ImageMessage) Set(payload *waMediaTransport.ImageTransport) (err error) { 26 | msg.Image, err = armadilloutil.Marshal(payload, ImageTransportVersion) 27 | return 28 | } 29 | 30 | func (msg *ConsumerApplication_StickerMessage) Decode() (dec *waMediaTransport.StickerTransport, err error) { 31 | return armadilloutil.Unmarshal(&waMediaTransport.StickerTransport{}, msg.GetSticker(), StickerTransportVersion) 32 | } 33 | 34 | func (msg *ConsumerApplication_StickerMessage) Set(payload *waMediaTransport.StickerTransport) (err error) { 35 | msg.Sticker, err = armadilloutil.Marshal(payload, StickerTransportVersion) 36 | return 37 | } 38 | 39 | func (msg *ConsumerApplication_ExtendedTextMessage) DecodeThumbnail() (dec *waMediaTransport.ImageTransport, err error) { 40 | return armadilloutil.Unmarshal(&waMediaTransport.ImageTransport{}, msg.GetThumbnail(), ImageTransportVersion) 41 | } 42 | 43 | func (msg *ConsumerApplication_ExtendedTextMessage) SetThumbnail(payload *waMediaTransport.ImageTransport) (err error) { 44 | msg.Thumbnail, err = armadilloutil.Marshal(payload, ImageTransportVersion) 45 | return 46 | } 47 | 48 | func (msg *ConsumerApplication_VideoMessage) Decode() (dec *waMediaTransport.VideoTransport, err error) { 49 | return armadilloutil.Unmarshal(&waMediaTransport.VideoTransport{}, msg.GetVideo(), VideoTransportVersion) 50 | } 51 | 52 | func (msg *ConsumerApplication_VideoMessage) Set(payload *waMediaTransport.VideoTransport) (err error) { 53 | msg.Video, err = armadilloutil.Marshal(payload, VideoTransportVersion) 54 | return 55 | } 56 | 57 | func (msg *ConsumerApplication_AudioMessage) Decode() (dec *waMediaTransport.AudioTransport, err error) { 58 | return armadilloutil.Unmarshal(&waMediaTransport.AudioTransport{}, msg.GetAudio(), AudioTransportVersion) 59 | } 60 | 61 | func (msg *ConsumerApplication_AudioMessage) Set(payload *waMediaTransport.AudioTransport) (err error) { 62 | msg.Audio, err = armadilloutil.Marshal(payload, AudioTransportVersion) 63 | return 64 | } 65 | 66 | func (msg *ConsumerApplication_DocumentMessage) Decode() (dec *waMediaTransport.DocumentTransport, err error) { 67 | return armadilloutil.Unmarshal(&waMediaTransport.DocumentTransport{}, msg.GetDocument(), DocumentTransportVersion) 68 | } 69 | 70 | func (msg *ConsumerApplication_DocumentMessage) Set(payload *waMediaTransport.DocumentTransport) (err error) { 71 | msg.Document, err = armadilloutil.Marshal(payload, DocumentTransportVersion) 72 | return 73 | } 74 | 75 | func (msg *ConsumerApplication_ContactMessage) Decode() (dec *waMediaTransport.ContactTransport, err error) { 76 | return armadilloutil.Unmarshal(&waMediaTransport.ContactTransport{}, msg.GetContact(), ContactTransportVersion) 77 | } 78 | 79 | func (msg *ConsumerApplication_ContactMessage) Set(payload *waMediaTransport.ContactTransport) (err error) { 80 | msg.Contact, err = armadilloutil.Marshal(payload, ContactTransportVersion) 81 | return 82 | } 83 | -------------------------------------------------------------------------------- /proto/waDeviceCapabilities/WAProtobufsDeviceCapabilities.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waDeviceCapabilities/WAProtobufsDeviceCapabilities.pb.raw -------------------------------------------------------------------------------- /proto/waDeviceCapabilities/WAProtobufsDeviceCapabilities.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAProtobufsDeviceCapabilities; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waDeviceCapabilities"; 4 | 5 | message DeviceCapabilities { 6 | enum ChatLockSupportLevel { 7 | NONE = 0; 8 | MINIMAL = 1; 9 | FULL = 2; 10 | } 11 | 12 | message LIDMigration { 13 | optional uint64 chatDbMigrationTimestamp = 1; 14 | } 15 | 16 | optional ChatLockSupportLevel chatLockSupportLevel = 1; 17 | optional LIDMigration lidMigration = 2; 18 | } 19 | -------------------------------------------------------------------------------- /proto/waE2E/WAWebProtobufsE2E.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waE2E/WAWebProtobufsE2E.pb.raw -------------------------------------------------------------------------------- /proto/waE2E/legacy.go: -------------------------------------------------------------------------------- 1 | package waE2E 2 | 3 | // Deprecated: Use GetKeyID 4 | func (x *AppStateSyncKey) GetKeyId() *AppStateSyncKeyId { 5 | return x.GetKeyID() 6 | } 7 | 8 | // Deprecated: Use GetKeyID 9 | func (x *AppStateSyncKeyId) GetKeyId() []byte { 10 | return x.GetKeyID() 11 | } 12 | 13 | // Deprecated: Use GetStanzaID 14 | func (x *PeerDataOperationRequestResponseMessage) GetStanzaId() string { 15 | return x.GetStanzaID() 16 | } 17 | 18 | // Deprecated: Use GetMentionedJID 19 | func (x *ContextInfo) GetMentionedJid() []string { 20 | return x.GetMentionedJID() 21 | } 22 | 23 | // Deprecated: Use GetRemoteJID 24 | func (x *ContextInfo) GetRemoteJid() string { 25 | return x.GetRemoteJID() 26 | } 27 | 28 | // Deprecated: Use GetStanzaID 29 | func (x *ContextInfo) GetStanzaId() string { 30 | return x.GetStanzaID() 31 | } 32 | -------------------------------------------------------------------------------- /proto/waEphemeral/WAWebProtobufsEphemeral.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.1 4 | // protoc v3.21.12 5 | // source: waEphemeral/WAWebProtobufsEphemeral.proto 6 | 7 | package waEphemeral 8 | 9 | import ( 10 | reflect "reflect" 11 | sync "sync" 12 | 13 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 14 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 15 | 16 | _ "embed" 17 | ) 18 | 19 | const ( 20 | // Verify that this generated code is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 22 | // Verify that runtime/protoimpl is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 24 | ) 25 | 26 | type EphemeralSetting struct { 27 | state protoimpl.MessageState `protogen:"open.v1"` 28 | Duration *int32 `protobuf:"fixed32,1,opt,name=duration" json:"duration,omitempty"` 29 | Timestamp *int64 `protobuf:"fixed64,2,opt,name=timestamp" json:"timestamp,omitempty"` 30 | unknownFields protoimpl.UnknownFields 31 | sizeCache protoimpl.SizeCache 32 | } 33 | 34 | func (x *EphemeralSetting) Reset() { 35 | *x = EphemeralSetting{} 36 | mi := &file_waEphemeral_WAWebProtobufsEphemeral_proto_msgTypes[0] 37 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 38 | ms.StoreMessageInfo(mi) 39 | } 40 | 41 | func (x *EphemeralSetting) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*EphemeralSetting) ProtoMessage() {} 46 | 47 | func (x *EphemeralSetting) ProtoReflect() protoreflect.Message { 48 | mi := &file_waEphemeral_WAWebProtobufsEphemeral_proto_msgTypes[0] 49 | if x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use EphemeralSetting.ProtoReflect.Descriptor instead. 60 | func (*EphemeralSetting) Descriptor() ([]byte, []int) { 61 | return file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *EphemeralSetting) GetDuration() int32 { 65 | if x != nil && x.Duration != nil { 66 | return *x.Duration 67 | } 68 | return 0 69 | } 70 | 71 | func (x *EphemeralSetting) GetTimestamp() int64 { 72 | if x != nil && x.Timestamp != nil { 73 | return *x.Timestamp 74 | } 75 | return 0 76 | } 77 | 78 | var File_waEphemeral_WAWebProtobufsEphemeral_proto protoreflect.FileDescriptor 79 | 80 | //go:embed WAWebProtobufsEphemeral.pb.raw 81 | var file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDesc []byte 82 | 83 | var ( 84 | file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescOnce sync.Once 85 | file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescData = file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDesc 86 | ) 87 | 88 | func file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescGZIP() []byte { 89 | file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescOnce.Do(func() { 90 | file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescData = protoimpl.X.CompressGZIP(file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescData) 91 | }) 92 | return file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDescData 93 | } 94 | 95 | var file_waEphemeral_WAWebProtobufsEphemeral_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 96 | var file_waEphemeral_WAWebProtobufsEphemeral_proto_goTypes = []any{ 97 | (*EphemeralSetting)(nil), // 0: WAWebProtobufsEphemeral.EphemeralSetting 98 | } 99 | var file_waEphemeral_WAWebProtobufsEphemeral_proto_depIdxs = []int32{ 100 | 0, // [0:0] is the sub-list for method output_type 101 | 0, // [0:0] is the sub-list for method input_type 102 | 0, // [0:0] is the sub-list for extension type_name 103 | 0, // [0:0] is the sub-list for extension extendee 104 | 0, // [0:0] is the sub-list for field type_name 105 | } 106 | 107 | func init() { file_waEphemeral_WAWebProtobufsEphemeral_proto_init() } 108 | func file_waEphemeral_WAWebProtobufsEphemeral_proto_init() { 109 | if File_waEphemeral_WAWebProtobufsEphemeral_proto != nil { 110 | return 111 | } 112 | type x struct{} 113 | out := protoimpl.TypeBuilder{ 114 | File: protoimpl.DescBuilder{ 115 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 116 | RawDescriptor: file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDesc, 117 | NumEnums: 0, 118 | NumMessages: 1, 119 | NumExtensions: 0, 120 | NumServices: 0, 121 | }, 122 | GoTypes: file_waEphemeral_WAWebProtobufsEphemeral_proto_goTypes, 123 | DependencyIndexes: file_waEphemeral_WAWebProtobufsEphemeral_proto_depIdxs, 124 | MessageInfos: file_waEphemeral_WAWebProtobufsEphemeral_proto_msgTypes, 125 | }.Build() 126 | File_waEphemeral_WAWebProtobufsEphemeral_proto = out.File 127 | file_waEphemeral_WAWebProtobufsEphemeral_proto_rawDesc = nil 128 | file_waEphemeral_WAWebProtobufsEphemeral_proto_goTypes = nil 129 | file_waEphemeral_WAWebProtobufsEphemeral_proto_depIdxs = nil 130 | } 131 | -------------------------------------------------------------------------------- /proto/waEphemeral/WAWebProtobufsEphemeral.pb.raw: -------------------------------------------------------------------------------- 1 | 2 | )waEphemeral/WAWebProtobufsEphemeral.protoWAWebProtobufsEphemeral"L 3 | EphemeralSetting 4 | duration (Rduration 5 | timestamp (R timestampB'Z%go.mau.fi/whatsmeow/proto/waEphemeral -------------------------------------------------------------------------------- /proto/waEphemeral/WAWebProtobufsEphemeral.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAWebProtobufsEphemeral; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waEphemeral"; 4 | 5 | message EphemeralSetting { 6 | optional sfixed32 duration = 1; 7 | optional sfixed64 timestamp = 2; 8 | } 9 | -------------------------------------------------------------------------------- /proto/waFingerprint/WAFingerprint.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waFingerprint/WAFingerprint.pb.raw -------------------------------------------------------------------------------- /proto/waFingerprint/WAFingerprint.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAFingerprint; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waFingerprint"; 4 | 5 | enum HostedState { 6 | E2EE = 0; 7 | HOSTED = 1; 8 | } 9 | 10 | message FingerprintData { 11 | optional bytes publicKey = 1; 12 | optional bytes pnIdentifier = 2; 13 | optional bytes lidIdentifier = 3; 14 | optional bytes usernameIdentifier = 4; 15 | optional HostedState hostedState = 5; 16 | optional bytes hashedPublicKey = 6; 17 | } 18 | 19 | message CombinedFingerprint { 20 | optional uint32 version = 1; 21 | optional FingerprintData localFingerprint = 2; 22 | optional FingerprintData remoteFingerprint = 3; 23 | } 24 | -------------------------------------------------------------------------------- /proto/waHistorySync/WAWebProtobufsHistorySync.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waHistorySync/WAWebProtobufsHistorySync.pb.raw -------------------------------------------------------------------------------- /proto/waHistorySync/legacy.go: -------------------------------------------------------------------------------- 1 | package waHistorySync 2 | 3 | // Deprecated: Use GetID 4 | func (x *Conversation) GetId() string { 5 | return x.GetID() 6 | } 7 | 8 | // Deprecated: Use GetID 9 | func (x *Pushname) GetId() string { 10 | return x.GetID() 11 | } 12 | -------------------------------------------------------------------------------- /proto/waLidMigrationSyncPayload/WAWebProtobufLidMigrationSyncPayload.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waLidMigrationSyncPayload/WAWebProtobufLidMigrationSyncPayload.pb.raw -------------------------------------------------------------------------------- /proto/waLidMigrationSyncPayload/WAWebProtobufLidMigrationSyncPayload.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAWebProtobufLidMigrationSyncPayload; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waLidMigrationSyncPayload"; 4 | 5 | message LIDMigrationMapping { 6 | required uint64 pn = 1; 7 | required uint64 assignedLid = 2; 8 | optional uint64 latestLid = 3; 9 | } 10 | 11 | message LIDMigrationMappingSyncPayload { 12 | repeated LIDMigrationMapping pnToLidMappings = 1; 13 | optional uint64 chatDbMigrationTimestamp = 2; 14 | } 15 | -------------------------------------------------------------------------------- /proto/waMediaEntryData/WAMediaEntryData.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waMediaEntryData/WAMediaEntryData.pb.raw -------------------------------------------------------------------------------- /proto/waMediaEntryData/WAMediaEntryData.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAMediaEntryData; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waMediaEntryData"; 4 | 5 | message MediaEntry { 6 | message ProgressiveJpegDetails { 7 | repeated uint32 scanLengths = 1; 8 | optional bytes sidecar = 2; 9 | } 10 | 11 | message DownloadableThumbnail { 12 | optional bytes fileSHA256 = 1; 13 | optional bytes fileEncSHA256 = 2; 14 | optional string directPath = 3; 15 | optional bytes mediaKey = 4; 16 | optional int64 mediaKeyTimestamp = 5; 17 | optional string objectID = 6; 18 | } 19 | 20 | optional bytes fileSHA256 = 1; 21 | optional bytes mediaKey = 2; 22 | optional bytes fileEncSHA256 = 3; 23 | optional string directPath = 4; 24 | optional int64 mediaKeyTimestamp = 5; 25 | optional string serverMediaType = 6; 26 | optional bytes uploadToken = 7; 27 | optional bytes validatedTimestamp = 8; 28 | optional bytes sidecar = 9; 29 | optional string objectID = 10; 30 | optional string FBID = 11; 31 | optional DownloadableThumbnail downloadableThumbnail = 12; 32 | optional string handle = 13; 33 | optional string filename = 14; 34 | optional ProgressiveJpegDetails progressiveJPEGDetails = 15; 35 | optional int64 size = 16; 36 | optional int64 lastDownloadAttemptTimestamp = 17; 37 | } 38 | -------------------------------------------------------------------------------- /proto/waMediaTransport/WAMediaTransport.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waMediaTransport/WAMediaTransport.pb.raw -------------------------------------------------------------------------------- /proto/waMediaTransport/WAMediaTransport.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAMediaTransport; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waMediaTransport"; 4 | 5 | import "waCommon/WACommon.proto"; 6 | 7 | message WAMediaTransport { 8 | message Ancillary { 9 | message Thumbnail { 10 | message DownloadableThumbnail { 11 | optional bytes fileSHA256 = 1; 12 | optional bytes fileEncSHA256 = 2; 13 | optional string directPath = 3; 14 | optional bytes mediaKey = 4; 15 | optional int64 mediaKeyTimestamp = 5; 16 | optional string objectID = 6; 17 | optional bytes thumbnailScansSidecar = 7; 18 | repeated uint32 thumbnailScanLengths = 8; 19 | } 20 | 21 | optional bytes JPEGThumbnail = 1; 22 | optional DownloadableThumbnail downloadableThumbnail = 2; 23 | optional uint32 thumbnailWidth = 3; 24 | optional uint32 thumbnailHeight = 4; 25 | } 26 | 27 | optional uint64 fileLength = 1; 28 | optional string mimetype = 2; 29 | optional Thumbnail thumbnail = 3; 30 | optional string objectID = 4; 31 | } 32 | 33 | message Integral { 34 | optional bytes fileSHA256 = 1; 35 | optional bytes mediaKey = 2; 36 | optional bytes fileEncSHA256 = 3; 37 | optional string directPath = 4; 38 | optional int64 mediaKeyTimestamp = 5; 39 | } 40 | 41 | optional Integral integral = 1; 42 | optional Ancillary ancillary = 2; 43 | } 44 | 45 | message ImageTransport { 46 | message Ancillary { 47 | enum HdType { 48 | NONE = 0; 49 | LQ_4K = 1; 50 | HQ_4K = 2; 51 | } 52 | 53 | optional uint32 height = 1; 54 | optional uint32 width = 2; 55 | optional bytes scansSidecar = 3; 56 | repeated uint32 scanLengths = 4; 57 | optional bytes midQualityFileSHA256 = 5; 58 | optional HdType hdType = 6; 59 | repeated float memoriesConceptScores = 7 [packed=true]; 60 | repeated uint32 memoriesConceptIDs = 8 [packed=true]; 61 | } 62 | 63 | message Integral { 64 | optional WAMediaTransport transport = 1; 65 | } 66 | 67 | optional Integral integral = 1; 68 | optional Ancillary ancillary = 2; 69 | } 70 | 71 | message VideoTransport { 72 | message Ancillary { 73 | enum Attribution { 74 | NONE = 0; 75 | GIPHY = 1; 76 | TENOR = 2; 77 | } 78 | 79 | optional uint32 seconds = 1; 80 | optional WACommon.MessageText caption = 2; 81 | optional bool gifPlayback = 3; 82 | optional uint32 height = 4; 83 | optional uint32 width = 5; 84 | optional bytes sidecar = 6; 85 | optional Attribution gifAttribution = 7; 86 | optional string accessibilityLabel = 8; 87 | } 88 | 89 | message Integral { 90 | optional WAMediaTransport transport = 1; 91 | } 92 | 93 | optional Integral integral = 1; 94 | optional Ancillary ancillary = 2; 95 | } 96 | 97 | message AudioTransport { 98 | message Ancillary { 99 | message AvatarAudio { 100 | enum AnimationsType { 101 | TALKING_A = 0; 102 | IDLE_A = 1; 103 | TALKING_B = 2; 104 | IDLE_B = 3; 105 | BACKGROUND = 4; 106 | } 107 | 108 | message DownloadableAvatarAnimations { 109 | optional bytes fileSHA256 = 1; 110 | optional bytes fileEncSHA256 = 2; 111 | optional string directPath = 3; 112 | optional bytes mediaKey = 4; 113 | optional int64 mediaKeyTimestamp = 5; 114 | optional string objectID = 6; 115 | optional AnimationsType animationsType = 7; 116 | } 117 | 118 | optional uint32 poseID = 1; 119 | repeated DownloadableAvatarAnimations avatarAnimations = 2; 120 | } 121 | 122 | optional uint32 seconds = 1; 123 | optional AvatarAudio avatarAudio = 2; 124 | } 125 | 126 | message Integral { 127 | enum AudioFormat { 128 | UNKNOWN = 0; 129 | OPUS = 1; 130 | } 131 | 132 | optional WAMediaTransport transport = 1; 133 | optional AudioFormat audioFormat = 2; 134 | } 135 | 136 | optional Integral integral = 1; 137 | optional Ancillary ancillary = 2; 138 | } 139 | 140 | message DocumentTransport { 141 | message Ancillary { 142 | optional uint32 pageCount = 1; 143 | } 144 | 145 | message Integral { 146 | optional WAMediaTransport transport = 1; 147 | } 148 | 149 | optional Integral integral = 1; 150 | optional Ancillary ancillary = 2; 151 | } 152 | 153 | message StickerTransport { 154 | message Ancillary { 155 | optional uint32 pageCount = 1; 156 | optional uint32 height = 2; 157 | optional uint32 width = 3; 158 | optional uint32 firstFrameLength = 4; 159 | optional bytes firstFrameSidecar = 5; 160 | optional string mustacheText = 6; 161 | optional bool isThirdParty = 7; 162 | optional string receiverFetchID = 8; 163 | optional string accessibilityLabel = 9; 164 | } 165 | 166 | message Integral { 167 | optional WAMediaTransport transport = 1; 168 | optional bool isAnimated = 2; 169 | optional string receiverFetchID = 3; 170 | } 171 | 172 | optional Integral integral = 1; 173 | optional Ancillary ancillary = 2; 174 | } 175 | 176 | message ContactTransport { 177 | message Ancillary { 178 | optional string displayName = 1; 179 | } 180 | 181 | message Integral { 182 | oneof contact { 183 | string vcard = 1; 184 | WAMediaTransport downloadableVcard = 2; 185 | } 186 | } 187 | 188 | optional Integral integral = 1; 189 | optional Ancillary ancillary = 2; 190 | } 191 | -------------------------------------------------------------------------------- /proto/waMmsRetry/WAMmsRetry.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waMmsRetry/WAMmsRetry.pb.raw -------------------------------------------------------------------------------- /proto/waMmsRetry/WAMmsRetry.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAMmsRetry; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waMmsRetry"; 4 | 5 | message MediaRetryNotification { 6 | enum ResultType { 7 | GENERAL_ERROR = 0; 8 | SUCCESS = 1; 9 | NOT_FOUND = 2; 10 | DECRYPTION_ERROR = 3; 11 | } 12 | 13 | optional string stanzaID = 1; 14 | optional string directPath = 2; 15 | optional ResultType result = 3; 16 | optional bytes messageSecret = 4; 17 | } 18 | 19 | message ServerErrorReceipt { 20 | optional string stanzaID = 1; 21 | } 22 | -------------------------------------------------------------------------------- /proto/waMsgApplication/WAMsgApplication.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waMsgApplication/WAMsgApplication.pb.raw -------------------------------------------------------------------------------- /proto/waMsgApplication/WAMsgApplication.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAMsgApplication; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waMsgApplication"; 4 | 5 | import "waCommon/WACommon.proto"; 6 | 7 | message MessageApplication { 8 | message Metadata { 9 | enum ThreadType { 10 | DEFAULT = 0; 11 | VANISH_MODE = 1; 12 | DISAPPEARING_MESSAGES = 2; 13 | } 14 | 15 | message QuotedMessage { 16 | optional string stanzaID = 1; 17 | optional string remoteJID = 2; 18 | optional string participant = 3; 19 | optional Payload payload = 4; 20 | } 21 | 22 | message EphemeralSettingMap { 23 | optional string chatJID = 1; 24 | optional EphemeralSetting ephemeralSetting = 2; 25 | } 26 | 27 | oneof ephemeral { 28 | EphemeralSetting chatEphemeralSetting = 1; 29 | EphemeralSettingMap ephemeralSettingList = 2; 30 | bytes ephemeralSharedSecret = 3; 31 | } 32 | 33 | optional uint32 forwardingScore = 5; 34 | optional bool isForwarded = 6; 35 | optional WACommon.SubProtocol businessMetadata = 7; 36 | optional bytes frankingKey = 8; 37 | optional int32 frankingVersion = 9; 38 | optional QuotedMessage quotedMessage = 10; 39 | optional ThreadType threadType = 11; 40 | optional string readonlyMetadataDataclass = 12; 41 | optional string groupID = 13; 42 | optional uint32 groupSize = 14; 43 | optional uint32 groupIndex = 15; 44 | optional string botResponseID = 16; 45 | optional string collapsibleID = 17; 46 | optional string secondaryOtid = 18; 47 | } 48 | 49 | message Payload { 50 | oneof content { 51 | Content coreContent = 1; 52 | Signal signal = 2; 53 | ApplicationData applicationData = 3; 54 | SubProtocolPayload subProtocol = 4; 55 | } 56 | } 57 | 58 | message SubProtocolPayload { 59 | oneof subProtocol { 60 | WACommon.SubProtocol consumerMessage = 2; 61 | WACommon.SubProtocol businessMessage = 3; 62 | WACommon.SubProtocol paymentMessage = 4; 63 | WACommon.SubProtocol multiDevice = 5; 64 | WACommon.SubProtocol voip = 6; 65 | WACommon.SubProtocol armadillo = 7; 66 | } 67 | 68 | optional WACommon.FutureProofBehavior futureProof = 1; 69 | } 70 | 71 | message ApplicationData { 72 | } 73 | 74 | message Signal { 75 | } 76 | 77 | message Content { 78 | } 79 | 80 | message EphemeralSetting { 81 | enum EphemeralityType { 82 | UNKNOWN = 0; 83 | SEEN_ONCE = 1; 84 | SEEN_BASED_WITH_TIMER = 2; 85 | SEND_BASED_WITH_TIMER = 3; 86 | } 87 | 88 | optional uint32 ephemeralExpiration = 2; 89 | optional int64 ephemeralSettingTimestamp = 3; 90 | optional EphemeralityType ephemeralityType = 5; 91 | optional bool isEphemeralSettingReset = 4; 92 | } 93 | 94 | optional Payload payload = 1; 95 | optional Metadata metadata = 2; 96 | } 97 | -------------------------------------------------------------------------------- /proto/waMsgApplication/extra.go: -------------------------------------------------------------------------------- 1 | package waMsgApplication 2 | 3 | import ( 4 | "github.com/idlethorn/waSocket/proto/armadilloutil" 5 | "github.com/idlethorn/waSocket/proto/waArmadilloApplication" 6 | "github.com/idlethorn/waSocket/proto/waConsumerApplication" 7 | "github.com/idlethorn/waSocket/proto/waMultiDevice" 8 | ) 9 | 10 | const ( 11 | ConsumerApplicationVersion = 1 12 | ArmadilloApplicationVersion = 1 13 | MultiDeviceApplicationVersion = 1 // TODO: check 14 | ) 15 | 16 | func (msg *MessageApplication_SubProtocolPayload_ConsumerMessage) Decode() (*waConsumerApplication.ConsumerApplication, error) { 17 | return armadilloutil.Unmarshal(&waConsumerApplication.ConsumerApplication{}, msg.ConsumerMessage, ConsumerApplicationVersion) 18 | } 19 | 20 | func (msg *MessageApplication_SubProtocolPayload_ConsumerMessage) Set(payload *waConsumerApplication.ConsumerApplication) (err error) { 21 | msg.ConsumerMessage, err = armadilloutil.Marshal(payload, ConsumerApplicationVersion) 22 | return 23 | } 24 | 25 | func (msg *MessageApplication_SubProtocolPayload_Armadillo) Decode() (*waArmadilloApplication.Armadillo, error) { 26 | return armadilloutil.Unmarshal(&waArmadilloApplication.Armadillo{}, msg.Armadillo, ArmadilloApplicationVersion) 27 | } 28 | 29 | func (msg *MessageApplication_SubProtocolPayload_Armadillo) Set(payload *waArmadilloApplication.Armadillo) (err error) { 30 | msg.Armadillo, err = armadilloutil.Marshal(payload, ArmadilloApplicationVersion) 31 | return 32 | } 33 | 34 | func (msg *MessageApplication_SubProtocolPayload_MultiDevice) Decode() (*waMultiDevice.MultiDevice, error) { 35 | return armadilloutil.Unmarshal(&waMultiDevice.MultiDevice{}, msg.MultiDevice, MultiDeviceApplicationVersion) 36 | } 37 | 38 | func (msg *MessageApplication_SubProtocolPayload_MultiDevice) Set(payload *waMultiDevice.MultiDevice) (err error) { 39 | msg.MultiDevice, err = armadilloutil.Marshal(payload, MultiDeviceApplicationVersion) 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /proto/waMsgTransport/WAMsgTransport.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waMsgTransport/WAMsgTransport.pb.raw -------------------------------------------------------------------------------- /proto/waMsgTransport/WAMsgTransport.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAMsgTransport; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waMsgTransport"; 4 | 5 | import "waCommon/WACommon.proto"; 6 | 7 | message MessageTransport { 8 | message Payload { 9 | optional WACommon.SubProtocol applicationPayload = 1; 10 | optional WACommon.FutureProofBehavior futureProof = 3; 11 | } 12 | 13 | message Protocol { 14 | message Ancillary { 15 | message BackupDirective { 16 | enum ActionType { 17 | NOOP = 0; 18 | UPSERT = 1; 19 | DELETE = 2; 20 | UPSERT_AND_DELETE = 3; 21 | } 22 | 23 | optional string messageID = 1; 24 | optional ActionType actionType = 2; 25 | optional string supplementalKey = 3; 26 | } 27 | 28 | message ICDCParticipantDevices { 29 | message ICDCIdentityListDescription { 30 | optional int32 seq = 1; 31 | optional bytes signingDevice = 2; 32 | repeated bytes unknownDevices = 3; 33 | repeated int32 unknownDeviceIDs = 4; 34 | } 35 | 36 | optional ICDCIdentityListDescription senderIdentity = 1; 37 | repeated ICDCIdentityListDescription recipientIdentities = 2; 38 | repeated string recipientUserJIDs = 3; 39 | } 40 | 41 | message SenderKeyDistributionMessage { 42 | optional string groupID = 1; 43 | optional bytes axolotlSenderKeyDistributionMessage = 2; 44 | } 45 | 46 | optional SenderKeyDistributionMessage skdm = 2; 47 | optional DeviceListMetadata deviceListMetadata = 3; 48 | optional ICDCParticipantDevices icdc = 4; 49 | optional BackupDirective backupDirective = 5; 50 | } 51 | 52 | message Integral { 53 | message DeviceSentMessage { 54 | optional string destinationJID = 1; 55 | optional string phash = 2; 56 | } 57 | 58 | optional bytes padding = 1; 59 | optional DeviceSentMessage DSM = 2; 60 | } 61 | 62 | optional Integral integral = 1; 63 | optional Ancillary ancillary = 2; 64 | } 65 | 66 | optional Payload payload = 1; 67 | optional Protocol protocol = 2; 68 | } 69 | 70 | message DeviceListMetadata { 71 | optional bytes senderKeyHash = 1; 72 | optional uint64 senderTimestamp = 2; 73 | optional bytes recipientKeyHash = 8; 74 | optional uint64 recipientTimestamp = 9; 75 | } 76 | -------------------------------------------------------------------------------- /proto/waMsgTransport/extra.go: -------------------------------------------------------------------------------- 1 | package waMsgTransport 2 | 3 | import ( 4 | "github.com/idlethorn/waSocket/proto/armadilloutil" 5 | "github.com/idlethorn/waSocket/proto/waMsgApplication" 6 | ) 7 | 8 | const ( 9 | MessageApplicationVersion = 2 10 | ) 11 | 12 | func (msg *MessageTransport_Payload) Decode() (*waMsgApplication.MessageApplication, error) { 13 | return armadilloutil.Unmarshal(&waMsgApplication.MessageApplication{}, msg.GetApplicationPayload(), MessageApplicationVersion) 14 | } 15 | 16 | func (msg *MessageTransport_Payload) Set(payload *waMsgApplication.MessageApplication) (err error) { 17 | msg.ApplicationPayload, err = armadilloutil.Marshal(payload, MessageApplicationVersion) 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /proto/waMultiDevice/WAMultiDevice.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waMultiDevice/WAMultiDevice.pb.raw -------------------------------------------------------------------------------- /proto/waMultiDevice/WAMultiDevice.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAMultiDevice; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waMultiDevice"; 4 | 5 | message MultiDevice { 6 | message Metadata { 7 | } 8 | 9 | message Payload { 10 | oneof payload { 11 | ApplicationData applicationData = 1; 12 | Signal signal = 2; 13 | } 14 | } 15 | 16 | message ApplicationData { 17 | message AppStateSyncKeyRequestMessage { 18 | repeated AppStateSyncKeyId keyIDs = 1; 19 | } 20 | 21 | message AppStateSyncKeyShareMessage { 22 | repeated AppStateSyncKey keys = 1; 23 | } 24 | 25 | message AppStateSyncKey { 26 | message AppStateSyncKeyData { 27 | message AppStateSyncKeyFingerprint { 28 | optional uint32 rawID = 1; 29 | optional uint32 currentIndex = 2; 30 | repeated uint32 deviceIndexes = 3 [packed=true]; 31 | } 32 | 33 | optional bytes keyData = 1; 34 | optional AppStateSyncKeyFingerprint fingerprint = 2; 35 | optional int64 timestamp = 3; 36 | } 37 | 38 | optional AppStateSyncKeyId keyID = 1; 39 | optional AppStateSyncKeyData keyData = 2; 40 | } 41 | 42 | message AppStateSyncKeyId { 43 | optional bytes keyID = 1; 44 | } 45 | 46 | oneof applicationData { 47 | AppStateSyncKeyShareMessage appStateSyncKeyShare = 1; 48 | AppStateSyncKeyRequestMessage appStateSyncKeyRequest = 2; 49 | } 50 | } 51 | 52 | message Signal { 53 | } 54 | 55 | optional Payload payload = 1; 56 | optional Metadata metadata = 2; 57 | } 58 | -------------------------------------------------------------------------------- /proto/waMultiDevice/extra.go: -------------------------------------------------------------------------------- 1 | package waMultiDevice 2 | 3 | func (*MultiDevice) IsMessageApplicationSub() {} 4 | -------------------------------------------------------------------------------- /proto/waQuickPromotionSurfaces/WAWebProtobufsQuickPromotionSurfaces.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waQuickPromotionSurfaces/WAWebProtobufsQuickPromotionSurfaces.pb.raw -------------------------------------------------------------------------------- /proto/waQuickPromotionSurfaces/WAWebProtobufsQuickPromotionSurfaces.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAWebProtobufsQuickPromotionSurfaces; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waQuickPromotionSurfaces"; 4 | 5 | message QP { 6 | enum FilterResult { 7 | TRUE = 1; 8 | FALSE = 2; 9 | UNKNOWN = 3; 10 | } 11 | 12 | enum FilterClientNotSupportedConfig { 13 | PASS_BY_DEFAULT = 1; 14 | FAIL_BY_DEFAULT = 2; 15 | } 16 | 17 | enum ClauseType { 18 | AND = 1; 19 | OR = 2; 20 | NOR = 3; 21 | } 22 | 23 | message FilterClause { 24 | required ClauseType clauseType = 1; 25 | repeated FilterClause clauses = 2; 26 | repeated Filter filters = 3; 27 | } 28 | 29 | message Filter { 30 | required string filterName = 1; 31 | repeated FilterParameters parameters = 2; 32 | optional FilterResult filterResult = 3; 33 | required FilterClientNotSupportedConfig clientNotSupportedConfig = 4; 34 | } 35 | 36 | message FilterParameters { 37 | optional string key = 1; 38 | optional string value = 2; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /proto/waReporting/WAWebProtobufsReporting.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waReporting/WAWebProtobufsReporting.pb.raw -------------------------------------------------------------------------------- /proto/waReporting/WAWebProtobufsReporting.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package WAWebProtobufsReporting; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waReporting"; 4 | 5 | message Reportable { 6 | uint32 minVersion = 1; 7 | uint32 maxVersion = 2; 8 | uint32 notReportableMinVersion = 3; 9 | bool never = 4; 10 | } 11 | 12 | message Config { 13 | map field = 1; 14 | uint32 version = 2; 15 | } 16 | 17 | message Field { 18 | uint32 minVersion = 1; 19 | uint32 maxVersion = 2; 20 | uint32 notReportableMinVersion = 3; 21 | bool isMessage = 4; 22 | map subfield = 5; 23 | } 24 | -------------------------------------------------------------------------------- /proto/waRoutingInfo/WAWebProtobufsRoutingInfo.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.36.1 4 | // protoc v3.21.12 5 | // source: waRoutingInfo/WAWebProtobufsRoutingInfo.proto 6 | 7 | package waRoutingInfo 8 | 9 | import ( 10 | reflect "reflect" 11 | sync "sync" 12 | 13 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 14 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 15 | 16 | _ "embed" 17 | ) 18 | 19 | const ( 20 | // Verify that this generated code is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 22 | // Verify that runtime/protoimpl is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 24 | ) 25 | 26 | type RoutingInfo struct { 27 | state protoimpl.MessageState `protogen:"open.v1"` 28 | RegionID []int32 `protobuf:"varint,1,rep,name=regionID" json:"regionID,omitempty"` 29 | ClusterID []int32 `protobuf:"varint,2,rep,name=clusterID" json:"clusterID,omitempty"` 30 | TaskID *int32 `protobuf:"varint,3,opt,name=taskID" json:"taskID,omitempty"` 31 | Debug *bool `protobuf:"varint,4,opt,name=debug" json:"debug,omitempty"` 32 | TcpBbr *bool `protobuf:"varint,5,opt,name=tcpBbr" json:"tcpBbr,omitempty"` 33 | TcpKeepalive *bool `protobuf:"varint,6,opt,name=tcpKeepalive" json:"tcpKeepalive,omitempty"` 34 | unknownFields protoimpl.UnknownFields 35 | sizeCache protoimpl.SizeCache 36 | } 37 | 38 | func (x *RoutingInfo) Reset() { 39 | *x = RoutingInfo{} 40 | mi := &file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | 45 | func (x *RoutingInfo) String() string { 46 | return protoimpl.X.MessageStringOf(x) 47 | } 48 | 49 | func (*RoutingInfo) ProtoMessage() {} 50 | 51 | func (x *RoutingInfo) ProtoReflect() protoreflect.Message { 52 | mi := &file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_msgTypes[0] 53 | if x != nil { 54 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 55 | if ms.LoadMessageInfo() == nil { 56 | ms.StoreMessageInfo(mi) 57 | } 58 | return ms 59 | } 60 | return mi.MessageOf(x) 61 | } 62 | 63 | // Deprecated: Use RoutingInfo.ProtoReflect.Descriptor instead. 64 | func (*RoutingInfo) Descriptor() ([]byte, []int) { 65 | return file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescGZIP(), []int{0} 66 | } 67 | 68 | func (x *RoutingInfo) GetRegionID() []int32 { 69 | if x != nil { 70 | return x.RegionID 71 | } 72 | return nil 73 | } 74 | 75 | func (x *RoutingInfo) GetClusterID() []int32 { 76 | if x != nil { 77 | return x.ClusterID 78 | } 79 | return nil 80 | } 81 | 82 | func (x *RoutingInfo) GetTaskID() int32 { 83 | if x != nil && x.TaskID != nil { 84 | return *x.TaskID 85 | } 86 | return 0 87 | } 88 | 89 | func (x *RoutingInfo) GetDebug() bool { 90 | if x != nil && x.Debug != nil { 91 | return *x.Debug 92 | } 93 | return false 94 | } 95 | 96 | func (x *RoutingInfo) GetTcpBbr() bool { 97 | if x != nil && x.TcpBbr != nil { 98 | return *x.TcpBbr 99 | } 100 | return false 101 | } 102 | 103 | func (x *RoutingInfo) GetTcpKeepalive() bool { 104 | if x != nil && x.TcpKeepalive != nil { 105 | return *x.TcpKeepalive 106 | } 107 | return false 108 | } 109 | 110 | var File_waRoutingInfo_WAWebProtobufsRoutingInfo_proto protoreflect.FileDescriptor 111 | 112 | //go:embed WAWebProtobufsRoutingInfo.pb.raw 113 | var file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDesc []byte 114 | 115 | var ( 116 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescOnce sync.Once 117 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescData = file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDesc 118 | ) 119 | 120 | func file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescGZIP() []byte { 121 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescOnce.Do(func() { 122 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescData = protoimpl.X.CompressGZIP(file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescData) 123 | }) 124 | return file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDescData 125 | } 126 | 127 | var file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 128 | var file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_goTypes = []any{ 129 | (*RoutingInfo)(nil), // 0: WAWebProtobufsRoutingInfo.RoutingInfo 130 | } 131 | var file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_depIdxs = []int32{ 132 | 0, // [0:0] is the sub-list for method output_type 133 | 0, // [0:0] is the sub-list for method input_type 134 | 0, // [0:0] is the sub-list for extension type_name 135 | 0, // [0:0] is the sub-list for extension extendee 136 | 0, // [0:0] is the sub-list for field type_name 137 | } 138 | 139 | func init() { file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_init() } 140 | func file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_init() { 141 | if File_waRoutingInfo_WAWebProtobufsRoutingInfo_proto != nil { 142 | return 143 | } 144 | type x struct{} 145 | out := protoimpl.TypeBuilder{ 146 | File: protoimpl.DescBuilder{ 147 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 148 | RawDescriptor: file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDesc, 149 | NumEnums: 0, 150 | NumMessages: 1, 151 | NumExtensions: 0, 152 | NumServices: 0, 153 | }, 154 | GoTypes: file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_goTypes, 155 | DependencyIndexes: file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_depIdxs, 156 | MessageInfos: file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_msgTypes, 157 | }.Build() 158 | File_waRoutingInfo_WAWebProtobufsRoutingInfo_proto = out.File 159 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_rawDesc = nil 160 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_goTypes = nil 161 | file_waRoutingInfo_WAWebProtobufsRoutingInfo_proto_depIdxs = nil 162 | } 163 | -------------------------------------------------------------------------------- /proto/waRoutingInfo/WAWebProtobufsRoutingInfo.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waRoutingInfo/WAWebProtobufsRoutingInfo.pb.raw -------------------------------------------------------------------------------- /proto/waRoutingInfo/WAWebProtobufsRoutingInfo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAWebProtobufsRoutingInfo; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waRoutingInfo"; 4 | 5 | message RoutingInfo { 6 | repeated int32 regionID = 1; 7 | repeated int32 clusterID = 2; 8 | optional int32 taskID = 3; 9 | optional bool debug = 4; 10 | optional bool tcpBbr = 5; 11 | optional bool tcpKeepalive = 6; 12 | } 13 | -------------------------------------------------------------------------------- /proto/waServerSync/WAServerSync.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waServerSync/WAServerSync.pb.raw -------------------------------------------------------------------------------- /proto/waServerSync/WAServerSync.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAServerSync; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waServerSync"; 4 | 5 | message SyncdMutation { 6 | enum SyncdOperation { 7 | SET = 0; 8 | REMOVE = 1; 9 | } 10 | 11 | optional SyncdOperation operation = 1; 12 | optional SyncdRecord record = 2; 13 | } 14 | 15 | message SyncdVersion { 16 | optional uint64 version = 1; 17 | } 18 | 19 | message ExitCode { 20 | optional uint64 code = 1; 21 | optional string text = 2; 22 | } 23 | 24 | message SyncdIndex { 25 | optional bytes blob = 1; 26 | } 27 | 28 | message SyncdValue { 29 | optional bytes blob = 1; 30 | } 31 | 32 | message KeyId { 33 | optional bytes ID = 1; 34 | } 35 | 36 | message SyncdRecord { 37 | optional SyncdIndex index = 1; 38 | optional SyncdValue value = 2; 39 | optional KeyId keyID = 3; 40 | } 41 | 42 | message ExternalBlobReference { 43 | optional bytes mediaKey = 1; 44 | optional string directPath = 2; 45 | optional string handle = 3; 46 | optional uint64 fileSizeBytes = 4; 47 | optional bytes fileSHA256 = 5; 48 | optional bytes fileEncSHA256 = 6; 49 | } 50 | 51 | message SyncdSnapshot { 52 | optional SyncdVersion version = 1; 53 | repeated SyncdRecord records = 2; 54 | optional bytes mac = 3; 55 | optional KeyId keyID = 4; 56 | } 57 | 58 | message SyncdMutations { 59 | repeated SyncdMutation mutations = 1; 60 | } 61 | 62 | message SyncdPatch { 63 | optional SyncdVersion version = 1; 64 | repeated SyncdMutation mutations = 2; 65 | optional ExternalBlobReference externalMutations = 3; 66 | optional bytes snapshotMAC = 4; 67 | optional bytes patchMAC = 5; 68 | optional KeyId keyID = 6; 69 | optional ExitCode exitCode = 7; 70 | optional uint32 deviceIndex = 8; 71 | optional bytes clientDebugData = 9; 72 | } 73 | -------------------------------------------------------------------------------- /proto/waServerSync/legacy.go: -------------------------------------------------------------------------------- 1 | package waServerSync 2 | 3 | // Deprecated: Use GetKeyID 4 | func (x *SyncdRecord) GetKeyId() *KeyId { 5 | return x.GetKeyID() 6 | } 7 | 8 | // Deprecated: Use GetKeyID 9 | func (x *SyncdSnapshot) GetKeyId() *KeyId { 10 | return x.GetKeyID() 11 | } 12 | 13 | // Deprecated: Use GetKeyID 14 | func (x *SyncdPatch) GetKeyId() *KeyId { 15 | return x.GetKeyID() 16 | } 17 | 18 | // Deprecated: Use GetSnapshotMAC 19 | func (x *SyncdPatch) GetSnapshotMac() []byte { 20 | return x.GetSnapshotMAC() 21 | } 22 | 23 | // Deprecated: Use GetPatchMAC 24 | func (x *SyncdPatch) GetPatchMac() []byte { 25 | return x.GetPatchMAC() 26 | } 27 | 28 | // Deprecated: Use GetID 29 | func (x *KeyId) GetId() []byte { 30 | return x.GetID() 31 | } 32 | -------------------------------------------------------------------------------- /proto/waSyncAction/WASyncAction.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waSyncAction/WASyncAction.pb.raw -------------------------------------------------------------------------------- /proto/waUserPassword/WAProtobufsUserPassword.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waUserPassword/WAProtobufsUserPassword.pb.raw -------------------------------------------------------------------------------- /proto/waUserPassword/WAProtobufsUserPassword.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAProtobufsUserPassword; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waUserPassword"; 4 | 5 | message UserPassword { 6 | enum Transformer { 7 | NONE = 0; 8 | PBKDF2_HMAC_SHA512 = 1; 9 | PBKDF2_HMAC_SHA384 = 2; 10 | } 11 | 12 | enum Encoding { 13 | UTF8 = 0; 14 | UTF8_BROKEN = 1; 15 | } 16 | 17 | message TransformerArg { 18 | message Value { 19 | oneof value { 20 | bytes asBlob = 1; 21 | uint32 asUnsignedInteger = 2; 22 | } 23 | } 24 | 25 | optional string key = 1; 26 | optional Value value = 2; 27 | } 28 | 29 | optional Encoding encoding = 1; 30 | optional Transformer transformer = 2; 31 | repeated TransformerArg transformerArg = 3; 32 | optional bytes transformedData = 4; 33 | } 34 | -------------------------------------------------------------------------------- /proto/waVnameCert/WAWebProtobufsVnameCert.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waVnameCert/WAWebProtobufsVnameCert.pb.raw -------------------------------------------------------------------------------- /proto/waVnameCert/WAWebProtobufsVnameCert.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAWebProtobufsVnameCert; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waVnameCert"; 4 | 5 | message BizAccountLinkInfo { 6 | enum AccountType { 7 | ENTERPRISE = 0; 8 | } 9 | 10 | enum HostStorageType { 11 | ON_PREMISE = 0; 12 | FACEBOOK = 1; 13 | } 14 | 15 | optional uint64 whatsappBizAcctFbid = 1; 16 | optional string whatsappAcctNumber = 2; 17 | optional uint64 issueTime = 3; 18 | optional HostStorageType hostStorage = 4; 19 | optional AccountType accountType = 5; 20 | } 21 | 22 | message BizIdentityInfo { 23 | enum ActualActorsType { 24 | SELF = 0; 25 | BSP = 1; 26 | } 27 | 28 | enum HostStorageType { 29 | ON_PREMISE = 0; 30 | FACEBOOK = 1; 31 | } 32 | 33 | enum VerifiedLevelValue { 34 | UNKNOWN = 0; 35 | LOW = 1; 36 | HIGH = 2; 37 | } 38 | 39 | optional VerifiedLevelValue vlevel = 1; 40 | optional VerifiedNameCertificate vnameCert = 2; 41 | optional bool signed = 3; 42 | optional bool revoked = 4; 43 | optional HostStorageType hostStorage = 5; 44 | optional ActualActorsType actualActors = 6; 45 | optional uint64 privacyModeTS = 7; 46 | optional uint64 featureControls = 8; 47 | } 48 | 49 | message LocalizedName { 50 | optional string lg = 1; 51 | optional string lc = 2; 52 | optional string verifiedName = 3; 53 | } 54 | 55 | message VerifiedNameCertificate { 56 | message Details { 57 | optional uint64 serial = 1; 58 | optional string issuer = 2; 59 | optional string verifiedName = 4; 60 | repeated LocalizedName localizedNames = 8; 61 | optional uint64 issueTime = 10; 62 | } 63 | 64 | optional bytes details = 1; 65 | optional bytes signature = 2; 66 | optional bytes serverSignature = 3; 67 | } 68 | 69 | message BizAccountPayload { 70 | optional VerifiedNameCertificate vnameCert = 1; 71 | optional bytes bizAcctLinkInfo = 2; 72 | } 73 | -------------------------------------------------------------------------------- /proto/waWa6/WAWebProtobufsWa6.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waWa6/WAWebProtobufsWa6.pb.raw -------------------------------------------------------------------------------- /proto/waWeb/WAWebProtobufsWeb.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waWeb/WAWebProtobufsWeb.pb.raw -------------------------------------------------------------------------------- /proto/waWeb/legacy.go: -------------------------------------------------------------------------------- 1 | package waWeb 2 | -------------------------------------------------------------------------------- /proto/waWinUIApi/WAWinUIApi.pb.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlethorn/waSocket/7fcc2e911b5a6de4aa0ad4ab687e0359f08902fd/proto/waWinUIApi/WAWinUIApi.pb.raw -------------------------------------------------------------------------------- /proto/waWinUIApi/WAWinUIApi.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package WAWinUIApi; 3 | option go_package = "go.mau.fi/whatsmeow/proto/waWinUIApi"; 4 | 5 | enum PositronDataSource { 6 | MESSAGES = 1; 7 | CHATS = 2; 8 | CONTACTS = 3; 9 | GROUP_METADATA = 4; 10 | GROUP_PARTICIPANTS = 5; 11 | REACTIONS = 6; 12 | } 13 | 14 | message PositronMessage { 15 | message MsgKey { 16 | optional bool fromMe = 1; 17 | optional WID remote = 2; 18 | optional string ID = 3; 19 | optional WID participant = 4; 20 | } 21 | 22 | message WID { 23 | optional string serialized = 1; 24 | } 25 | 26 | optional int64 timestamp = 1; 27 | optional string type = 2; 28 | optional string body = 3; 29 | optional MsgKey ID = 4; 30 | optional string JSON = 99; 31 | } 32 | 33 | message PositronChat { 34 | optional string ID = 1; 35 | optional string name = 2; 36 | optional int64 timestamp = 3; 37 | optional int64 unreadCount = 4; 38 | optional string JSON = 99; 39 | } 40 | 41 | message PositronContact { 42 | optional string ID = 1; 43 | optional string phoneNumber = 2; 44 | optional string name = 3; 45 | optional bool isAddressBookContact = 4; 46 | optional string JSON = 99; 47 | } 48 | 49 | message PositronGroupMetadata { 50 | optional string ID = 1; 51 | optional string subject = 2; 52 | optional string JSON = 99; 53 | } 54 | 55 | message PositronGroupParticipants { 56 | optional string ID = 1; 57 | repeated string participants = 2; 58 | optional string JSON = 99; 59 | } 60 | 61 | message PositronReaction { 62 | optional string ID = 1; 63 | optional string parentMsgKey = 2; 64 | optional string reactionText = 3; 65 | optional int64 timestamp = 4; 66 | optional string senderUserJID = 5; 67 | optional string JSON = 99; 68 | } 69 | 70 | message PositronData { 71 | optional PositronDataSource dataSource = 1; 72 | repeated PositronMessage messages = 2; 73 | repeated PositronChat chats = 3; 74 | repeated PositronContact contacts = 4; 75 | repeated PositronGroupMetadata groupMetadata = 5; 76 | repeated PositronGroupParticipants groupParticipants = 6; 77 | repeated PositronReaction reactions = 7; 78 | } 79 | -------------------------------------------------------------------------------- /push.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "context" 11 | "encoding/base64" 12 | 13 | "go.mau.fi/util/random" 14 | 15 | waBinary "github.com/idlethorn/waSocket/binary" 16 | "github.com/idlethorn/waSocket/types" 17 | ) 18 | 19 | type PushConfig interface { 20 | GetPushConfigAttrs() waBinary.Attrs 21 | } 22 | 23 | type FCMPushConfig struct { 24 | Token string `json:"token"` 25 | } 26 | 27 | func (fpc *FCMPushConfig) GetPushConfigAttrs() waBinary.Attrs { 28 | return waBinary.Attrs{ 29 | "id": fpc.Token, 30 | "num_acc": 1, 31 | "platform": "gcm", 32 | } 33 | } 34 | 35 | type APNsPushConfig struct { 36 | Token string `json:"token"` 37 | VoIPToken string `json:"voip_token"` 38 | MsgIDEncKey []byte `json:"msg_id_enc_key"` 39 | } 40 | 41 | func (apc *APNsPushConfig) GetPushConfigAttrs() waBinary.Attrs { 42 | if len(apc.MsgIDEncKey) != 32 { 43 | apc.MsgIDEncKey = random.Bytes(32) 44 | } 45 | attrs := waBinary.Attrs{ 46 | "id": apc.Token, 47 | "platform": "apple", 48 | "version": 2, 49 | "reg_push": 1, 50 | "preview": 1, 51 | "pkey": base64.RawURLEncoding.EncodeToString(apc.MsgIDEncKey), 52 | "background_location": 1, // or 0 53 | "call": "Opening.m4r", 54 | "default": "note.m4r", 55 | "groups": "note.m4r", 56 | "lg": "en", 57 | "lc": "US", 58 | "nse_call": 0, 59 | "nse_ver": 2, 60 | "nse_read": 0, 61 | "voip_payload_type": 2, 62 | } 63 | if apc.VoIPToken != "" { 64 | attrs["voip"] = apc.VoIPToken 65 | } 66 | return attrs 67 | } 68 | 69 | type WebPushConfig struct { 70 | Endpoint string `json:"endpoint"` 71 | Auth []byte `json:"auth"` 72 | P256DH []byte `json:"p256dh"` 73 | } 74 | 75 | func (wpc *WebPushConfig) GetPushConfigAttrs() waBinary.Attrs { 76 | return waBinary.Attrs{ 77 | "platform": "web", 78 | "endpoint": wpc.Endpoint, 79 | "auth": base64.StdEncoding.EncodeToString(wpc.Auth), 80 | "p256dh": base64.StdEncoding.EncodeToString(wpc.P256DH), 81 | } 82 | } 83 | 84 | func (cli *Client) GetServerPushNotificationConfig(ctx context.Context) (*waBinary.Node, error) { 85 | resp, err := cli.sendIQ(infoQuery{ 86 | Namespace: "urn:xmpp:whatsapp:push", 87 | Type: iqGet, 88 | To: types.ServerJID, 89 | Content: []waBinary.Node{{Tag: "settings"}}, 90 | Context: ctx, 91 | }) 92 | return resp, err 93 | } 94 | 95 | // RegisterForPushNotifications registers a token to receive push notifications for new WhatsApp messages. 96 | // 97 | // This is generally not necessary for anything. Don't use this if you don't know what you're doing. 98 | func (cli *Client) RegisterForPushNotifications(ctx context.Context, pc PushConfig) error { 99 | _, err := cli.sendIQ(infoQuery{ 100 | Namespace: "urn:xmpp:whatsapp:push", 101 | Type: iqSet, 102 | To: types.ServerJID, 103 | Content: []waBinary.Node{{ 104 | Tag: "config", 105 | Attrs: pc.GetPushConfigAttrs(), 106 | }}, 107 | Context: ctx, 108 | }) 109 | return err 110 | } 111 | -------------------------------------------------------------------------------- /socket/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package socket implements a subset of the Noise protocol framework on top of websockets as used by WhatsApp. 8 | // 9 | // There shouldn't be any need to manually interact with this package. 10 | // The Client struct in the top-level waSocket package handles everything. 11 | package socket 12 | 13 | import ( 14 | "errors" 15 | 16 | "github.com/idlethorn/waSocket/binary/token" 17 | ) 18 | 19 | const ( 20 | // Origin is the Origin header for all WhatsApp websocket connections 21 | Origin = "https://web.whatsapp.com" 22 | // URL is the websocket URL for the new multidevice protocol 23 | URL = "wss://web.whatsapp.com/ws/chat" 24 | ) 25 | 26 | const ( 27 | NoiseStartPattern = "Noise_XX_25519_AESGCM_SHA256\x00\x00\x00\x00" 28 | 29 | WAMagicValue = 6 30 | ) 31 | 32 | var WAConnHeader = []byte{'W', 'A', WAMagicValue, token.DictVersion} 33 | 34 | const ( 35 | FrameMaxSize = 2 << 23 36 | FrameLengthSize = 3 37 | ) 38 | 39 | var ( 40 | ErrFrameTooLarge = errors.New("frame too large") 41 | ErrSocketClosed = errors.New("frame socket is closed") 42 | ErrSocketAlreadyOpen = errors.New("frame socket is already open") 43 | ) 44 | -------------------------------------------------------------------------------- /socket/framesocket.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package socket 8 | 9 | import ( 10 | "context" 11 | "errors" 12 | "fmt" 13 | "net/http" 14 | "sync" 15 | "time" 16 | 17 | "github.com/gorilla/websocket" 18 | 19 | waLog "github.com/idlethorn/waSocket/util/log" 20 | ) 21 | 22 | type FrameSocket struct { 23 | conn *websocket.Conn 24 | ctx context.Context 25 | cancel func() 26 | log waLog.Logger 27 | lock sync.Mutex 28 | 29 | URL string 30 | HTTPHeaders http.Header 31 | 32 | Frames chan []byte 33 | OnDisconnect func(remote bool) 34 | WriteTimeout time.Duration 35 | 36 | Header []byte 37 | Dialer websocket.Dialer 38 | 39 | incomingLength int 40 | receivedLength int 41 | incoming []byte 42 | partialHeader []byte 43 | } 44 | 45 | func NewFrameSocket(log waLog.Logger, dialer websocket.Dialer) *FrameSocket { 46 | return &FrameSocket{ 47 | conn: nil, 48 | log: log, 49 | Header: WAConnHeader, 50 | Frames: make(chan []byte), 51 | 52 | URL: URL, 53 | HTTPHeaders: http.Header{"Origin": {Origin}}, 54 | 55 | Dialer: dialer, 56 | } 57 | } 58 | 59 | func (fs *FrameSocket) IsConnected() bool { 60 | return fs.conn != nil 61 | } 62 | 63 | func (fs *FrameSocket) Context() context.Context { 64 | return fs.ctx 65 | } 66 | 67 | func (fs *FrameSocket) Close(code int) { 68 | fs.lock.Lock() 69 | defer fs.lock.Unlock() 70 | 71 | if fs.conn == nil { 72 | return 73 | } 74 | 75 | if code > 0 { 76 | message := websocket.FormatCloseMessage(code, "") 77 | err := fs.conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second)) 78 | if err != nil { 79 | fs.log.Warnf("Error sending close message: %v", err) 80 | } 81 | } 82 | 83 | fs.cancel() 84 | err := fs.conn.Close() 85 | if err != nil { 86 | fs.log.Errorf("Error closing websocket: %v", err) 87 | } 88 | fs.conn = nil 89 | fs.ctx = nil 90 | fs.cancel = nil 91 | if fs.OnDisconnect != nil { 92 | go fs.OnDisconnect(code == 0) 93 | } 94 | } 95 | 96 | func (fs *FrameSocket) Connect() error { 97 | fs.lock.Lock() 98 | defer fs.lock.Unlock() 99 | 100 | if fs.conn != nil { 101 | return ErrSocketAlreadyOpen 102 | } 103 | ctx, cancel := context.WithCancel(context.Background()) 104 | 105 | fs.log.Debugf("Dialing %s", fs.URL) 106 | conn, _, err := fs.Dialer.Dial(fs.URL, fs.HTTPHeaders) 107 | if err != nil { 108 | cancel() 109 | return fmt.Errorf("couldn't dial whatsapp web websocket: %w", err) 110 | } 111 | 112 | fs.ctx, fs.cancel = ctx, cancel 113 | fs.conn = conn 114 | conn.SetCloseHandler(func(code int, text string) error { 115 | fs.log.Debugf("Server closed websocket with status %d/%s", code, text) 116 | cancel() 117 | // from default CloseHandler 118 | message := websocket.FormatCloseMessage(code, "") 119 | _ = conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second)) 120 | return nil 121 | }) 122 | 123 | go fs.readPump(conn, ctx) 124 | return nil 125 | } 126 | 127 | func (fs *FrameSocket) SendFrame(data []byte) error { 128 | conn := fs.conn 129 | if conn == nil { 130 | return ErrSocketClosed 131 | } 132 | dataLength := len(data) 133 | if dataLength >= FrameMaxSize { 134 | return fmt.Errorf("%w (got %d bytes, max %d bytes)", ErrFrameTooLarge, len(data), FrameMaxSize) 135 | } 136 | 137 | headerLength := len(fs.Header) 138 | // Whole frame is header + 3 bytes for length + data 139 | wholeFrame := make([]byte, headerLength+FrameLengthSize+dataLength) 140 | 141 | // Copy the header if it's there 142 | if fs.Header != nil { 143 | copy(wholeFrame[:headerLength], fs.Header) 144 | // We only want to send the header once 145 | fs.Header = nil 146 | } 147 | 148 | // Encode length of frame 149 | wholeFrame[headerLength] = byte(dataLength >> 16) 150 | wholeFrame[headerLength+1] = byte(dataLength >> 8) 151 | wholeFrame[headerLength+2] = byte(dataLength) 152 | 153 | // Copy actual frame data 154 | copy(wholeFrame[headerLength+FrameLengthSize:], data) 155 | 156 | if fs.WriteTimeout > 0 { 157 | err := conn.SetWriteDeadline(time.Now().Add(fs.WriteTimeout)) 158 | if err != nil { 159 | fs.log.Warnf("Failed to set write deadline: %v", err) 160 | } 161 | } 162 | return conn.WriteMessage(websocket.BinaryMessage, wholeFrame) 163 | } 164 | 165 | func (fs *FrameSocket) frameComplete() { 166 | data := fs.incoming 167 | fs.incoming = nil 168 | fs.partialHeader = nil 169 | fs.incomingLength = 0 170 | fs.receivedLength = 0 171 | fs.Frames <- data 172 | } 173 | 174 | func (fs *FrameSocket) processData(msg []byte) { 175 | for len(msg) > 0 { 176 | // This probably doesn't happen a lot (if at all), so the code is unoptimized 177 | if fs.partialHeader != nil { 178 | msg = append(fs.partialHeader, msg...) 179 | fs.partialHeader = nil 180 | } 181 | if fs.incoming == nil { 182 | if len(msg) >= FrameLengthSize { 183 | length := (int(msg[0]) << 16) + (int(msg[1]) << 8) + int(msg[2]) 184 | fs.incomingLength = length 185 | fs.receivedLength = len(msg) 186 | msg = msg[FrameLengthSize:] 187 | if len(msg) >= length { 188 | fs.incoming = msg[:length] 189 | msg = msg[length:] 190 | fs.frameComplete() 191 | } else { 192 | fs.incoming = make([]byte, length) 193 | copy(fs.incoming, msg) 194 | msg = nil 195 | } 196 | } else { 197 | fs.log.Warnf("Received partial header (report if this happens often)") 198 | fs.partialHeader = msg 199 | msg = nil 200 | } 201 | } else { 202 | if fs.receivedLength+len(msg) >= fs.incomingLength { 203 | copy(fs.incoming[fs.receivedLength:], msg[:fs.incomingLength-fs.receivedLength]) 204 | msg = msg[fs.incomingLength-fs.receivedLength:] 205 | fs.frameComplete() 206 | } else { 207 | copy(fs.incoming[fs.receivedLength:], msg) 208 | fs.receivedLength += len(msg) 209 | msg = nil 210 | } 211 | } 212 | } 213 | } 214 | 215 | func (fs *FrameSocket) readPump(conn *websocket.Conn, ctx context.Context) { 216 | fs.log.Debugf("Frame websocket read pump starting %p", fs) 217 | defer func() { 218 | fs.log.Debugf("Frame websocket read pump exiting %p", fs) 219 | go fs.Close(0) 220 | }() 221 | for { 222 | msgType, data, err := conn.ReadMessage() 223 | if err != nil { 224 | // Ignore the error if the context has been closed 225 | if !errors.Is(ctx.Err(), context.Canceled) { 226 | fs.log.Errorf("Error reading from websocket: %v", err) 227 | } 228 | return 229 | } else if msgType != websocket.BinaryMessage { 230 | fs.log.Warnf("Got unexpected websocket message type %d", msgType) 231 | continue 232 | } 233 | fs.processData(data) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /socket/noisehandshake.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package socket 8 | 9 | import ( 10 | "crypto/cipher" 11 | "crypto/sha256" 12 | "fmt" 13 | "io" 14 | "sync/atomic" 15 | 16 | "golang.org/x/crypto/curve25519" 17 | "golang.org/x/crypto/hkdf" 18 | 19 | "github.com/idlethorn/waSocket/util/gcmutil" 20 | ) 21 | 22 | type NoiseHandshake struct { 23 | hash []byte 24 | salt []byte 25 | key cipher.AEAD 26 | counter uint32 27 | } 28 | 29 | func NewNoiseHandshake() *NoiseHandshake { 30 | return &NoiseHandshake{} 31 | } 32 | 33 | func sha256Slice(data []byte) []byte { 34 | hash := sha256.Sum256(data) 35 | return hash[:] 36 | } 37 | 38 | func (nh *NoiseHandshake) Start(pattern string, header []byte) { 39 | data := []byte(pattern) 40 | if len(data) == 32 { 41 | nh.hash = data 42 | } else { 43 | nh.hash = sha256Slice(data) 44 | } 45 | nh.salt = nh.hash 46 | var err error 47 | nh.key, err = gcmutil.Prepare(nh.hash) 48 | if err != nil { 49 | panic(err) 50 | } 51 | nh.Authenticate(header) 52 | } 53 | 54 | func (nh *NoiseHandshake) Authenticate(data []byte) { 55 | nh.hash = sha256Slice(append(nh.hash, data...)) 56 | } 57 | 58 | func (nh *NoiseHandshake) postIncrementCounter() uint32 { 59 | count := atomic.AddUint32(&nh.counter, 1) 60 | return count - 1 61 | } 62 | 63 | func (nh *NoiseHandshake) Encrypt(plaintext []byte) []byte { 64 | ciphertext := nh.key.Seal(nil, generateIV(nh.postIncrementCounter()), plaintext, nh.hash) 65 | nh.Authenticate(ciphertext) 66 | return ciphertext 67 | } 68 | 69 | func (nh *NoiseHandshake) Decrypt(ciphertext []byte) (plaintext []byte, err error) { 70 | plaintext, err = nh.key.Open(nil, generateIV(nh.postIncrementCounter()), ciphertext, nh.hash) 71 | if err == nil { 72 | nh.Authenticate(ciphertext) 73 | } 74 | return 75 | } 76 | 77 | func (nh *NoiseHandshake) Finish(fs *FrameSocket, frameHandler FrameHandler, disconnectHandler DisconnectHandler) (*NoiseSocket, error) { 78 | if write, read, err := nh.extractAndExpand(nh.salt, nil); err != nil { 79 | return nil, fmt.Errorf("failed to extract final keys: %w", err) 80 | } else if writeKey, err := gcmutil.Prepare(write); err != nil { 81 | return nil, fmt.Errorf("failed to create final write cipher: %w", err) 82 | } else if readKey, err := gcmutil.Prepare(read); err != nil { 83 | return nil, fmt.Errorf("failed to create final read cipher: %w", err) 84 | } else if ns, err := newNoiseSocket(fs, writeKey, readKey, frameHandler, disconnectHandler); err != nil { 85 | return nil, fmt.Errorf("failed to create noise socket: %w", err) 86 | } else { 87 | return ns, nil 88 | } 89 | } 90 | 91 | func (nh *NoiseHandshake) MixSharedSecretIntoKey(priv, pub [32]byte) error { 92 | secret, err := curve25519.X25519(priv[:], pub[:]) 93 | if err != nil { 94 | return fmt.Errorf("failed to do x25519 scalar multiplication: %w", err) 95 | } 96 | return nh.MixIntoKey(secret) 97 | } 98 | 99 | func (nh *NoiseHandshake) MixIntoKey(data []byte) error { 100 | nh.counter = 0 101 | write, read, err := nh.extractAndExpand(nh.salt, data) 102 | if err != nil { 103 | return fmt.Errorf("failed to extract keys for mixing: %w", err) 104 | } 105 | nh.salt = write 106 | nh.key, err = gcmutil.Prepare(read) 107 | if err != nil { 108 | return fmt.Errorf("failed to create new cipher while mixing keys: %w", err) 109 | } 110 | return nil 111 | } 112 | 113 | func (nh *NoiseHandshake) extractAndExpand(salt, data []byte) (write []byte, read []byte, err error) { 114 | h := hkdf.New(sha256.New, data, salt, nil) 115 | write = make([]byte, 32) 116 | read = make([]byte, 32) 117 | 118 | if _, err = io.ReadFull(h, write); err != nil { 119 | err = fmt.Errorf("failed to read write key: %w", err) 120 | } else if _, err = io.ReadFull(h, read); err != nil { 121 | err = fmt.Errorf("failed to read read key: %w", err) 122 | } 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /socket/noisesocket.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package socket 8 | 9 | import ( 10 | "context" 11 | "crypto/cipher" 12 | "encoding/binary" 13 | "sync" 14 | "sync/atomic" 15 | 16 | "github.com/gorilla/websocket" 17 | ) 18 | 19 | type NoiseSocket struct { 20 | fs *FrameSocket 21 | onFrame FrameHandler 22 | writeKey cipher.AEAD 23 | readKey cipher.AEAD 24 | writeCounter uint32 25 | readCounter uint32 26 | writeLock sync.Mutex 27 | destroyed atomic.Bool 28 | stopConsumer chan struct{} 29 | } 30 | 31 | type DisconnectHandler func(socket *NoiseSocket, remote bool) 32 | type FrameHandler func([]byte) 33 | 34 | func newNoiseSocket(fs *FrameSocket, writeKey, readKey cipher.AEAD, frameHandler FrameHandler, disconnectHandler DisconnectHandler) (*NoiseSocket, error) { 35 | ns := &NoiseSocket{ 36 | fs: fs, 37 | writeKey: writeKey, 38 | readKey: readKey, 39 | onFrame: frameHandler, 40 | stopConsumer: make(chan struct{}), 41 | } 42 | fs.OnDisconnect = func(remote bool) { 43 | disconnectHandler(ns, remote) 44 | } 45 | go ns.consumeFrames(fs.ctx, fs.Frames) 46 | return ns, nil 47 | } 48 | 49 | func (ns *NoiseSocket) consumeFrames(ctx context.Context, frames <-chan []byte) { 50 | if ctx == nil { 51 | // ctx being nil implies the connection already closed somehow 52 | return 53 | } 54 | ctxDone := ctx.Done() 55 | for { 56 | select { 57 | case frame := <-frames: 58 | ns.receiveEncryptedFrame(frame) 59 | case <-ctxDone: 60 | return 61 | case <-ns.stopConsumer: 62 | return 63 | } 64 | } 65 | } 66 | 67 | func generateIV(count uint32) []byte { 68 | iv := make([]byte, 12) 69 | binary.BigEndian.PutUint32(iv[8:], count) 70 | return iv 71 | } 72 | 73 | func (ns *NoiseSocket) Context() context.Context { 74 | return ns.fs.Context() 75 | } 76 | 77 | func (ns *NoiseSocket) Stop(disconnect bool) { 78 | if ns.destroyed.CompareAndSwap(false, true) { 79 | close(ns.stopConsumer) 80 | ns.fs.OnDisconnect = nil 81 | if disconnect { 82 | ns.fs.Close(websocket.CloseNormalClosure) 83 | } 84 | } 85 | } 86 | 87 | func (ns *NoiseSocket) SendFrame(plaintext []byte) error { 88 | ns.writeLock.Lock() 89 | ciphertext := ns.writeKey.Seal(nil, generateIV(ns.writeCounter), plaintext, nil) 90 | ns.writeCounter++ 91 | err := ns.fs.SendFrame(ciphertext) 92 | ns.writeLock.Unlock() 93 | return err 94 | } 95 | 96 | func (ns *NoiseSocket) receiveEncryptedFrame(ciphertext []byte) { 97 | count := atomic.AddUint32(&ns.readCounter, 1) - 1 98 | plaintext, err := ns.readKey.Open(nil, generateIV(count), ciphertext, nil) 99 | if err != nil { 100 | ns.fs.log.Warnf("Failed to decrypt frame: %v", err) 101 | return 102 | } 103 | ns.onFrame(plaintext) 104 | } 105 | 106 | func (ns *NoiseSocket) IsConnected() bool { 107 | return ns.fs.IsConnected() 108 | } 109 | -------------------------------------------------------------------------------- /store/sqlstore/lidmap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package sqlstore contains an SQL-backed implementation of the interfaces in the store package. 8 | package sqlstore 9 | 10 | import ( 11 | "context" 12 | "database/sql" 13 | "errors" 14 | "slices" 15 | "sync" 16 | 17 | "go.mau.fi/util/dbutil" 18 | 19 | "github.com/idlethorn/waSocket/store" 20 | "github.com/idlethorn/waSocket/types" 21 | ) 22 | 23 | type CachedLIDMap struct { 24 | db *dbutil.Database 25 | 26 | pnToLIDCache map[string]string 27 | lidToPNCache map[string]string 28 | cacheFilled bool 29 | lidCacheLock sync.RWMutex 30 | } 31 | 32 | var _ store.LIDStore = (*CachedLIDMap)(nil) 33 | 34 | func NewCachedLIDMap(db *dbutil.Database) *CachedLIDMap { 35 | return &CachedLIDMap{ 36 | db: db, 37 | 38 | pnToLIDCache: make(map[string]string), 39 | lidToPNCache: make(map[string]string), 40 | } 41 | } 42 | 43 | const ( 44 | deleteExistingLIDMappingQuery = `DELETE FROM waSocket_lid_map WHERE (lid<>$1 AND pn=$2)` 45 | putLIDMappingQuery = ` 46 | INSERT INTO waSocket_lid_map (lid, pn) 47 | VALUES ($1, $2) 48 | ON CONFLICT (lid) DO UPDATE SET pn=excluded.pn WHERE waSocket_lid_map.pn<>excluded.pn 49 | ` 50 | getLIDForPNQuery = `SELECT lid FROM waSocket_lid_map WHERE pn=$1` 51 | getPNForLIDQuery = `SELECT pn FROM waSocket_lid_map WHERE lid=$1` 52 | getAllLIDMappingsQuery = `SELECT lid, pn FROM waSocket_lid_map` 53 | ) 54 | 55 | func (s *CachedLIDMap) FillCache(ctx context.Context) error { 56 | s.lidCacheLock.Lock() 57 | defer s.lidCacheLock.Unlock() 58 | rows, err := s.db.Query(ctx, getAllLIDMappingsQuery) 59 | if err != nil { 60 | return err 61 | } 62 | defer rows.Close() 63 | for rows.Next() { 64 | var lid, pn string 65 | err = rows.Scan(&lid, &pn) 66 | if err != nil { 67 | return err 68 | } 69 | s.pnToLIDCache[pn] = lid 70 | s.lidToPNCache[lid] = pn 71 | } 72 | s.cacheFilled = true 73 | return nil 74 | } 75 | 76 | func (s *CachedLIDMap) getLIDMapping(ctx context.Context, source types.JID, targetServer, query string, sourceToTarget, targetToSource map[string]string) (types.JID, error) { 77 | s.lidCacheLock.RLock() 78 | targetUser, ok := sourceToTarget[source.User] 79 | cacheFilled := s.cacheFilled 80 | s.lidCacheLock.RUnlock() 81 | if ok || cacheFilled { 82 | if targetUser == "" { 83 | return types.JID{}, nil 84 | } 85 | return types.JID{User: targetUser, Device: source.Device, Server: targetServer}, nil 86 | } 87 | s.lidCacheLock.Lock() 88 | defer s.lidCacheLock.Unlock() 89 | err := s.db.QueryRow(ctx, query, source.User).Scan(&targetServer) 90 | if errors.Is(err, sql.ErrNoRows) { 91 | // continue with empty result 92 | } else if err != nil { 93 | return types.JID{}, err 94 | } 95 | sourceToTarget[source.User] = targetServer 96 | if targetServer != "" { 97 | targetToSource[targetServer] = source.User 98 | return types.JID{User: targetServer, Device: source.Device, Server: targetServer}, nil 99 | } 100 | return types.JID{}, nil 101 | } 102 | 103 | func (s *CachedLIDMap) GetLIDForPN(ctx context.Context, pn types.JID) (types.JID, error) { 104 | return s.getLIDMapping( 105 | ctx, pn, types.HiddenUserServer, getLIDForPNQuery, 106 | s.pnToLIDCache, s.lidToPNCache, 107 | ) 108 | } 109 | 110 | func (s *CachedLIDMap) GetPNForLID(ctx context.Context, lid types.JID) (types.JID, error) { 111 | return s.getLIDMapping( 112 | ctx, lid, types.DefaultUserServer, getPNForLIDQuery, 113 | s.lidToPNCache, s.pnToLIDCache, 114 | ) 115 | } 116 | 117 | func (s *CachedLIDMap) PutLIDMapping(ctx context.Context, lid, pn types.JID) error { 118 | s.lidCacheLock.Lock() 119 | defer s.lidCacheLock.Unlock() 120 | cachedLID, ok := s.pnToLIDCache[pn.User] 121 | if ok && cachedLID == lid.User { 122 | return nil 123 | } 124 | return s.db.DoTxn(ctx, nil, func(ctx context.Context) error { 125 | return s.unlockedPutLIDMapping(ctx, lid, pn) 126 | }) 127 | } 128 | 129 | func (s *CachedLIDMap) PutManyLIDMappings(ctx context.Context, mappings []store.LIDMapping) error { 130 | s.lidCacheLock.Lock() 131 | defer s.lidCacheLock.Unlock() 132 | mappings = slices.DeleteFunc(mappings, func(mapping store.LIDMapping) bool { 133 | cachedLID, ok := s.pnToLIDCache[mapping.PN.User] 134 | if ok && cachedLID == mapping.LID.User { 135 | return true 136 | } 137 | return false 138 | }) 139 | if len(mappings) == 0 { 140 | return nil 141 | } 142 | return s.db.DoTxn(ctx, nil, func(ctx context.Context) error { 143 | for _, mapping := range mappings { 144 | err := s.unlockedPutLIDMapping(ctx, mapping.LID, mapping.PN) 145 | if err != nil { 146 | return err 147 | } 148 | } 149 | return nil 150 | }) 151 | } 152 | 153 | func (s *CachedLIDMap) unlockedPutLIDMapping(ctx context.Context, lid, pn types.JID) error { 154 | _, err := s.db.Exec(ctx, deleteExistingLIDMappingQuery, lid.User, pn.User) 155 | if err != nil { 156 | return err 157 | } 158 | _, err = s.db.Exec(ctx, putLIDMappingQuery, lid.User, pn.User) 159 | if err != nil { 160 | return err 161 | } 162 | s.pnToLIDCache[pn.User] = lid.User 163 | s.lidToPNCache[lid.User] = pn.User 164 | return nil 165 | } 166 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/00-latest-schema.sql: -------------------------------------------------------------------------------- 1 | -- v0 -> v8 (compatible with v8+): Latest schema 2 | CREATE TABLE waSocket_device ( 3 | jid TEXT PRIMARY KEY, 4 | lid TEXT, 5 | 6 | facebook_uuid uuid, 7 | 8 | registration_id BIGINT NOT NULL CHECK ( registration_id >= 0 AND registration_id < 4294967296 ), 9 | 10 | noise_key bytea NOT NULL CHECK ( length(noise_key) = 32 ), 11 | identity_key bytea NOT NULL CHECK ( length(identity_key) = 32 ), 12 | 13 | signed_pre_key bytea NOT NULL CHECK ( length(signed_pre_key) = 32 ), 14 | signed_pre_key_id INTEGER NOT NULL CHECK ( signed_pre_key_id >= 0 AND signed_pre_key_id < 16777216 ), 15 | signed_pre_key_sig bytea NOT NULL CHECK ( length(signed_pre_key_sig) = 64 ), 16 | 17 | adv_key bytea NOT NULL, 18 | adv_details bytea NOT NULL, 19 | adv_account_sig bytea NOT NULL CHECK ( length(adv_account_sig) = 64 ), 20 | adv_account_sig_key bytea NOT NULL CHECK ( length(adv_account_sig_key) = 32 ), 21 | adv_device_sig bytea NOT NULL CHECK ( length(adv_device_sig) = 64 ), 22 | 23 | platform TEXT NOT NULL DEFAULT '', 24 | business_name TEXT NOT NULL DEFAULT '', 25 | push_name TEXT NOT NULL DEFAULT '' 26 | ); 27 | 28 | CREATE TABLE waSocket_identity_keys ( 29 | our_jid TEXT, 30 | their_id TEXT, 31 | identity bytea NOT NULL CHECK ( length(identity) = 32 ), 32 | 33 | PRIMARY KEY (our_jid, their_id), 34 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 35 | ); 36 | 37 | CREATE TABLE waSocket_pre_keys ( 38 | jid TEXT, 39 | key_id INTEGER CHECK ( key_id >= 0 AND key_id < 16777216 ), 40 | key bytea NOT NULL CHECK ( length(key) = 32 ), 41 | uploaded BOOLEAN NOT NULL, 42 | 43 | PRIMARY KEY (jid, key_id), 44 | FOREIGN KEY (jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 45 | ); 46 | 47 | CREATE TABLE waSocket_sessions ( 48 | our_jid TEXT, 49 | their_id TEXT, 50 | session bytea, 51 | 52 | PRIMARY KEY (our_jid, their_id), 53 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 54 | ); 55 | 56 | CREATE TABLE waSocket_sender_keys ( 57 | our_jid TEXT, 58 | chat_id TEXT, 59 | sender_id TEXT, 60 | sender_key bytea NOT NULL, 61 | 62 | PRIMARY KEY (our_jid, chat_id, sender_id), 63 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 64 | ); 65 | 66 | CREATE TABLE waSocket_app_state_sync_keys ( 67 | jid TEXT, 68 | key_id bytea, 69 | key_data bytea NOT NULL, 70 | timestamp BIGINT NOT NULL, 71 | fingerprint bytea NOT NULL, 72 | 73 | PRIMARY KEY (jid, key_id), 74 | FOREIGN KEY (jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 75 | ); 76 | 77 | CREATE TABLE waSocket_app_state_version ( 78 | jid TEXT, 79 | name TEXT, 80 | version BIGINT NOT NULL, 81 | hash bytea NOT NULL CHECK ( length(hash) = 128 ), 82 | 83 | PRIMARY KEY (jid, name), 84 | FOREIGN KEY (jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 85 | ); 86 | 87 | CREATE TABLE waSocket_app_state_mutation_macs ( 88 | jid TEXT, 89 | name TEXT, 90 | version BIGINT, 91 | index_mac bytea CHECK ( length(index_mac) = 32 ), 92 | value_mac bytea NOT NULL CHECK ( length(value_mac) = 32 ), 93 | 94 | PRIMARY KEY (jid, name, version, index_mac), 95 | FOREIGN KEY (jid, name) REFERENCES waSocket_app_state_version(jid, name) ON DELETE CASCADE ON UPDATE CASCADE 96 | ); 97 | 98 | CREATE TABLE waSocket_contacts ( 99 | our_jid TEXT, 100 | their_jid TEXT, 101 | first_name TEXT, 102 | full_name TEXT, 103 | push_name TEXT, 104 | business_name TEXT, 105 | 106 | PRIMARY KEY (our_jid, their_jid), 107 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 108 | ); 109 | 110 | CREATE TABLE waSocket_chat_settings ( 111 | our_jid TEXT, 112 | chat_jid TEXT, 113 | muted_until BIGINT NOT NULL DEFAULT 0, 114 | pinned BOOLEAN NOT NULL DEFAULT false, 115 | archived BOOLEAN NOT NULL DEFAULT false, 116 | 117 | PRIMARY KEY (our_jid, chat_jid), 118 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 119 | ); 120 | 121 | CREATE TABLE waSocket_message_secrets ( 122 | our_jid TEXT, 123 | chat_jid TEXT, 124 | sender_jid TEXT, 125 | message_id TEXT, 126 | key bytea NOT NULL, 127 | 128 | PRIMARY KEY (our_jid, chat_jid, sender_jid, message_id), 129 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 130 | ); 131 | 132 | CREATE TABLE waSocket_privacy_tokens ( 133 | our_jid TEXT, 134 | their_jid TEXT, 135 | token bytea NOT NULL, 136 | timestamp BIGINT NOT NULL, 137 | PRIMARY KEY (our_jid, their_jid) 138 | ); 139 | 140 | CREATE TABLE waSocket_lid_map ( 141 | lid TEXT PRIMARY KEY, 142 | pn TEXT UNIQUE NOT NULL 143 | ); 144 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/03-message-secrets.sql: -------------------------------------------------------------------------------- 1 | -- v3: Add message secrets table 2 | CREATE TABLE waSocket_message_secrets ( 3 | our_jid TEXT, 4 | chat_jid TEXT, 5 | sender_jid TEXT, 6 | message_id TEXT, 7 | key bytea NOT NULL, 8 | 9 | PRIMARY KEY (our_jid, chat_jid, sender_jid, message_id), 10 | FOREIGN KEY (our_jid) REFERENCES waSocket_device(jid) ON DELETE CASCADE ON UPDATE CASCADE 11 | ); 12 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/04-privacy-tokens.sql: -------------------------------------------------------------------------------- 1 | -- v4: Add privacy tokens table 2 | CREATE TABLE waSocket_privacy_tokens ( 3 | our_jid TEXT, 4 | their_jid TEXT, 5 | token bytea NOT NULL, 6 | timestamp BIGINT NOT NULL, 7 | PRIMARY KEY (our_jid, their_jid) 8 | ); 9 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/05-account-jid-format.sql: -------------------------------------------------------------------------------- 1 | -- v5: Update account JID format 2 | UPDATE waSocket_device SET jid=REPLACE(jid, '.0', ''); 3 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/06-facebook-uuid.sql: -------------------------------------------------------------------------------- 1 | -- v6: Add facebook_uuid column to device table 2 | ALTER TABLE waSocket_device ADD COLUMN facebook_uuid uuid; 3 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/07-account-lid.sql: -------------------------------------------------------------------------------- 1 | -- v7 (compatible with v6+): Add lid column to device table 2 | ALTER TABLE waSocket_device ADD COLUMN lid TEXT; 3 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/08-lid-mapping.sql: -------------------------------------------------------------------------------- 1 | -- v8 (compatible with v8+): Add tables for LID<->JID mapping 2 | CREATE TABLE waSocket_lid_map ( 3 | lid TEXT PRIMARY KEY, 4 | pn TEXT UNIQUE NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /store/sqlstore/upgrades/upgrades.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package upgrades 8 | 9 | import ( 10 | "embed" 11 | 12 | "go.mau.fi/util/dbutil" 13 | ) 14 | 15 | var Table dbutil.UpgradeTable 16 | 17 | //go:embed *.sql 18 | var upgrades embed.FS 19 | 20 | func init() { 21 | Table.RegisterFS(upgrades) 22 | } 23 | -------------------------------------------------------------------------------- /types/call.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package types 8 | 9 | import "time" 10 | 11 | type BasicCallMeta struct { 12 | From JID 13 | Timestamp time.Time 14 | CallCreator JID 15 | CallID string 16 | } 17 | 18 | type CallRemoteMeta struct { 19 | RemotePlatform string // The platform of the caller's WhatsApp client 20 | RemoteVersion string // Version of the caller's WhatsApp client 21 | } 22 | -------------------------------------------------------------------------------- /types/events/call.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package events 8 | 9 | import ( 10 | waBinary "github.com/idlethorn/waSocket/binary" 11 | "github.com/idlethorn/waSocket/types" 12 | ) 13 | 14 | // CallOffer is emitted when the user receives a call on WhatsApp. 15 | type CallOffer struct { 16 | types.BasicCallMeta 17 | types.CallRemoteMeta 18 | 19 | Data *waBinary.Node // The call offer data 20 | } 21 | 22 | // CallAccept is emitted when a call is accepted on WhatsApp. 23 | type CallAccept struct { 24 | types.BasicCallMeta 25 | types.CallRemoteMeta 26 | 27 | Data *waBinary.Node 28 | } 29 | 30 | type CallPreAccept struct { 31 | types.BasicCallMeta 32 | types.CallRemoteMeta 33 | 34 | Data *waBinary.Node 35 | } 36 | 37 | type CallTransport struct { 38 | types.BasicCallMeta 39 | types.CallRemoteMeta 40 | 41 | Data *waBinary.Node 42 | } 43 | 44 | // CallOfferNotice is emitted when the user receives a notice of a call on WhatsApp. 45 | // This seems to be primarily for group calls (whereas CallOffer is for 1:1 calls). 46 | type CallOfferNotice struct { 47 | types.BasicCallMeta 48 | 49 | Media string // "audio" or "video" depending on call type 50 | Type string // "group" when it's a group call 51 | 52 | Data *waBinary.Node 53 | } 54 | 55 | // CallRelayLatency is emitted slightly after the user receives a call on WhatsApp. 56 | type CallRelayLatency struct { 57 | types.BasicCallMeta 58 | Data *waBinary.Node 59 | } 60 | 61 | // CallTerminate is emitted when the other party terminates a call on WhatsApp. 62 | type CallTerminate struct { 63 | types.BasicCallMeta 64 | Reason string 65 | Data *waBinary.Node 66 | } 67 | 68 | // CallReject is sent when the other party rejects the call on WhatsApp. 69 | type CallReject struct { 70 | types.BasicCallMeta 71 | Data *waBinary.Node 72 | } 73 | 74 | // UnknownCallEvent is emitted when a call element with unknown content is received. 75 | type UnknownCallEvent struct { 76 | Node *waBinary.Node 77 | } 78 | -------------------------------------------------------------------------------- /types/group.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package types 8 | 9 | import ( 10 | "time" 11 | ) 12 | 13 | type GroupMemberAddMode string 14 | 15 | const ( 16 | GroupMemberAddModeAdmin GroupMemberAddMode = "admin_add" 17 | GroupMemberAddModeAllMember GroupMemberAddMode = "all_member_add" 18 | ) 19 | 20 | // GroupInfo contains basic information about a group chat on WhatsApp. 21 | type GroupInfo struct { 22 | JID JID 23 | OwnerJID JID 24 | OwnerPN JID 25 | 26 | GroupName 27 | GroupTopic 28 | GroupLocked 29 | GroupAnnounce 30 | GroupEphemeral 31 | GroupIncognito 32 | 33 | GroupParent 34 | GroupLinkedParent 35 | GroupIsDefaultSub 36 | GroupMembershipApprovalMode 37 | 38 | AddressingMode AddressingMode 39 | GroupCreated time.Time 40 | CreatorCountryCode string 41 | 42 | ParticipantVersionID string 43 | Participants []GroupParticipant 44 | 45 | MemberAddMode GroupMemberAddMode 46 | } 47 | 48 | type GroupMembershipApprovalMode struct { 49 | IsJoinApprovalRequired bool 50 | } 51 | 52 | type GroupParent struct { 53 | IsParent bool 54 | DefaultMembershipApprovalMode string // request_required 55 | } 56 | 57 | type GroupLinkedParent struct { 58 | LinkedParentJID JID 59 | } 60 | 61 | type GroupIsDefaultSub struct { 62 | IsDefaultSubGroup bool 63 | } 64 | 65 | // GroupName contains the name of a group along with metadata of who set it and when. 66 | type GroupName struct { 67 | Name string 68 | NameSetAt time.Time 69 | NameSetBy JID 70 | NameSetByPN JID 71 | } 72 | 73 | // GroupTopic contains the topic (description) of a group along with metadata of who set it and when. 74 | type GroupTopic struct { 75 | Topic string 76 | TopicID string 77 | TopicSetAt time.Time 78 | TopicSetBy JID 79 | TopicSetByPN JID 80 | TopicDeleted bool 81 | } 82 | 83 | // GroupLocked specifies whether the group info can only be edited by admins. 84 | type GroupLocked struct { 85 | IsLocked bool 86 | } 87 | 88 | // GroupAnnounce specifies whether only admins can send messages in the group. 89 | type GroupAnnounce struct { 90 | IsAnnounce bool 91 | AnnounceVersionID string 92 | } 93 | 94 | type GroupIncognito struct { 95 | IsIncognito bool 96 | } 97 | 98 | // GroupParticipant contains info about a participant of a WhatsApp group chat. 99 | type GroupParticipant struct { 100 | // The primary JID that should be used to send messages to this participant. 101 | // Always equals either the LID or phone number. 102 | JID JID 103 | PhoneNumber JID 104 | LID JID 105 | 106 | IsAdmin bool 107 | IsSuperAdmin bool 108 | 109 | // This is only present for anonymous users in announcement groups, it's an obfuscated phone number 110 | DisplayName string 111 | 112 | // When creating groups, adding some participants may fail. 113 | // In such cases, the error code will be here. 114 | Error int 115 | AddRequest *GroupParticipantAddRequest 116 | } 117 | 118 | type GroupParticipantAddRequest struct { 119 | Code string 120 | Expiration time.Time 121 | } 122 | 123 | // GroupEphemeral contains the group's disappearing messages settings. 124 | type GroupEphemeral struct { 125 | IsEphemeral bool 126 | DisappearingTimer uint32 127 | } 128 | 129 | type GroupDelete struct { 130 | Deleted bool 131 | DeleteReason string 132 | } 133 | 134 | type GroupLinkChangeType string 135 | 136 | const ( 137 | GroupLinkChangeTypeParent GroupLinkChangeType = "parent_group" 138 | GroupLinkChangeTypeSub GroupLinkChangeType = "sub_group" 139 | GroupLinkChangeTypeSibling GroupLinkChangeType = "sibling_group" 140 | ) 141 | 142 | type GroupUnlinkReason string 143 | 144 | const ( 145 | GroupUnlinkReasonDefault GroupUnlinkReason = "unlink_group" 146 | GroupUnlinkReasonDelete GroupUnlinkReason = "delete_parent" 147 | ) 148 | 149 | type GroupLinkTarget struct { 150 | JID JID 151 | GroupName 152 | GroupIsDefaultSub 153 | } 154 | 155 | type GroupLinkChange struct { 156 | Type GroupLinkChangeType 157 | UnlinkReason GroupUnlinkReason 158 | Group GroupLinkTarget 159 | } 160 | 161 | type GroupParticipantRequest struct { 162 | JID JID 163 | RequestedAt time.Time 164 | } 165 | -------------------------------------------------------------------------------- /types/message.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package types 8 | 9 | import ( 10 | "fmt" 11 | "time" 12 | ) 13 | 14 | type AddressingMode string 15 | 16 | const ( 17 | AddressingModePN AddressingMode = "pn" 18 | AddressingModeLID AddressingMode = "lid" 19 | ) 20 | 21 | // MessageSource contains basic sender and chat information about a message. 22 | type MessageSource struct { 23 | Chat JID // The chat where the message was sent. 24 | Sender JID // The user who sent the message. 25 | IsFromMe bool // Whether the message was sent by the current user instead of someone else. 26 | IsGroup bool // Whether the chat is a group chat or broadcast list. 27 | 28 | AddressingMode AddressingMode // The addressing mode of the message (phone number or LID) 29 | SenderAlt JID // The alternative address of the user who sent the message 30 | RecipientAlt JID // The alternative address of the recipient of the message for DMs. 31 | 32 | // When sending a read receipt to a broadcast list message, the Chat is the broadcast list 33 | // and Sender is you, so this field contains the recipient of the read receipt. 34 | BroadcastListOwner JID 35 | } 36 | 37 | // IsIncomingBroadcast returns true if the message was sent to a broadcast list instead of directly to the user. 38 | // 39 | // If this is true, it means the message shows up in the direct chat with the Sender. 40 | func (ms *MessageSource) IsIncomingBroadcast() bool { 41 | return (!ms.IsFromMe || !ms.BroadcastListOwner.IsEmpty()) && ms.Chat.IsBroadcastList() 42 | } 43 | 44 | // DeviceSentMeta contains metadata from messages sent by another one of the user's own devices. 45 | type DeviceSentMeta struct { 46 | DestinationJID string // The destination user. This should match the MessageInfo.Recipient field. 47 | Phash string 48 | } 49 | 50 | type EditAttribute string 51 | 52 | const ( 53 | EditAttributeEmpty EditAttribute = "" 54 | EditAttributeMessageEdit EditAttribute = "1" 55 | EditAttributePinInChat EditAttribute = "2" 56 | EditAttributeAdminEdit EditAttribute = "3" // only used in newsletters 57 | EditAttributeSenderRevoke EditAttribute = "7" 58 | EditAttributeAdminRevoke EditAttribute = "8" 59 | ) 60 | 61 | type BotEditType string 62 | 63 | const ( 64 | EditTypeFirst BotEditType = "first" 65 | EditTypeInner BotEditType = "inner" 66 | EditTypeLast BotEditType = "last" 67 | ) 68 | 69 | // MsgBotInfo targets 70 | type MsgBotInfo struct { 71 | EditType BotEditType 72 | EditTargetID MessageID 73 | EditSenderTimestampMS time.Time 74 | } 75 | 76 | // MsgMetaInfo targets 77 | type MsgMetaInfo struct { 78 | // Bot things 79 | TargetID MessageID 80 | TargetSender JID 81 | 82 | DeprecatedLIDSession *bool 83 | 84 | ThreadMessageID MessageID 85 | ThreadMessageSenderJID JID 86 | } 87 | 88 | // MessageInfo contains metadata about an incoming message. 89 | type MessageInfo struct { 90 | MessageSource 91 | ID MessageID 92 | ServerID MessageServerID 93 | Type string 94 | PushName string 95 | Timestamp time.Time 96 | Category string 97 | Multicast bool 98 | MediaType string 99 | Edit EditAttribute 100 | 101 | MsgBotInfo MsgBotInfo 102 | MsgMetaInfo MsgMetaInfo 103 | 104 | VerifiedName *VerifiedName 105 | DeviceSentMeta *DeviceSentMeta // Metadata for direct messages sent from another one of the user's own devices. 106 | } 107 | 108 | // SourceString returns a log-friendly representation of who sent the message and where. 109 | func (ms *MessageSource) SourceString() string { 110 | if ms.Sender != ms.Chat { 111 | return fmt.Sprintf("%s in %s", ms.Sender, ms.Chat) 112 | } else { 113 | return ms.Chat.String() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /types/newsletter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package types 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "time" 14 | 15 | "go.mau.fi/util/jsontime" 16 | 17 | "github.com/idlethorn/waSocket/proto/waE2E" 18 | ) 19 | 20 | type NewsletterVerificationState string 21 | 22 | func (nvs *NewsletterVerificationState) UnmarshalText(text []byte) error { 23 | *nvs = NewsletterVerificationState(bytes.ToLower(text)) 24 | return nil 25 | } 26 | 27 | const ( 28 | NewsletterVerificationStateVerified NewsletterVerificationState = "verified" 29 | NewsletterVerificationStateUnverified NewsletterVerificationState = "unverified" 30 | ) 31 | 32 | type NewsletterPrivacy string 33 | 34 | func (np *NewsletterPrivacy) UnmarshalText(text []byte) error { 35 | *np = NewsletterPrivacy(bytes.ToLower(text)) 36 | return nil 37 | } 38 | 39 | const ( 40 | NewsletterPrivacyPrivate NewsletterPrivacy = "private" 41 | NewsletterPrivacyPublic NewsletterPrivacy = "public" 42 | ) 43 | 44 | type NewsletterReactionsMode string 45 | 46 | const ( 47 | NewsletterReactionsModeAll NewsletterReactionsMode = "all" 48 | NewsletterReactionsModeBasic NewsletterReactionsMode = "basic" 49 | NewsletterReactionsModeNone NewsletterReactionsMode = "none" 50 | NewsletterReactionsModeBlocklist NewsletterReactionsMode = "blocklist" 51 | ) 52 | 53 | type NewsletterState string 54 | 55 | func (ns *NewsletterState) UnmarshalText(text []byte) error { 56 | *ns = NewsletterState(bytes.ToLower(text)) 57 | return nil 58 | } 59 | 60 | const ( 61 | NewsletterStateActive NewsletterState = "active" 62 | NewsletterStateSuspended NewsletterState = "suspended" 63 | NewsletterStateGeoSuspended NewsletterState = "geosuspended" 64 | ) 65 | 66 | type NewsletterMuted struct { 67 | Muted bool 68 | } 69 | 70 | type WrappedNewsletterState struct { 71 | Type NewsletterState `json:"type"` 72 | } 73 | 74 | type NewsletterMuteState string 75 | 76 | func (nms *NewsletterMuteState) UnmarshalText(text []byte) error { 77 | *nms = NewsletterMuteState(bytes.ToLower(text)) 78 | return nil 79 | } 80 | 81 | const ( 82 | NewsletterMuteOn NewsletterMuteState = "on" 83 | NewsletterMuteOff NewsletterMuteState = "off" 84 | ) 85 | 86 | type NewsletterRole string 87 | 88 | func (nr *NewsletterRole) UnmarshalText(text []byte) error { 89 | *nr = NewsletterRole(bytes.ToLower(text)) 90 | return nil 91 | } 92 | 93 | const ( 94 | NewsletterRoleSubscriber NewsletterRole = "subscriber" 95 | NewsletterRoleGuest NewsletterRole = "guest" 96 | NewsletterRoleAdmin NewsletterRole = "admin" 97 | NewsletterRoleOwner NewsletterRole = "owner" 98 | ) 99 | 100 | type NewsletterMetadata struct { 101 | ID JID `json:"id"` 102 | State WrappedNewsletterState `json:"state"` 103 | ThreadMeta NewsletterThreadMetadata `json:"thread_metadata"` 104 | ViewerMeta *NewsletterViewerMetadata `json:"viewer_metadata"` 105 | } 106 | 107 | type NewsletterViewerMetadata struct { 108 | Mute NewsletterMuteState `json:"mute"` 109 | Role NewsletterRole `json:"role"` 110 | } 111 | 112 | type NewsletterKeyType string 113 | 114 | const ( 115 | NewsletterKeyTypeJID NewsletterKeyType = "JID" 116 | NewsletterKeyTypeInvite NewsletterKeyType = "INVITE" 117 | ) 118 | 119 | type NewsletterReactionSettings struct { 120 | Value NewsletterReactionsMode `json:"value"` 121 | } 122 | 123 | type NewsletterSettings struct { 124 | ReactionCodes NewsletterReactionSettings `json:"reaction_codes"` 125 | } 126 | 127 | type NewsletterThreadMetadata struct { 128 | CreationTime jsontime.UnixString `json:"creation_time"` 129 | InviteCode string `json:"invite"` 130 | Name NewsletterText `json:"name"` 131 | Description NewsletterText `json:"description"` 132 | SubscriberCount int `json:"subscribers_count,string"` 133 | VerificationState NewsletterVerificationState `json:"verification"` 134 | Picture *ProfilePictureInfo `json:"picture"` 135 | Preview ProfilePictureInfo `json:"preview"` 136 | Settings NewsletterSettings `json:"settings"` 137 | 138 | //NewsletterMuted `json:"-"` 139 | //PrivacyType NewsletterPrivacy `json:"-"` 140 | //ReactionsMode NewsletterReactionsMode `json:"-"` 141 | //State NewsletterState `json:"-"` 142 | } 143 | 144 | type NewsletterText struct { 145 | Text string `json:"text"` 146 | ID string `json:"id"` 147 | UpdateTime jsontime.UnixMicroString `json:"update_time"` 148 | } 149 | 150 | type NewsletterMessage struct { 151 | MessageServerID MessageServerID 152 | MessageID MessageID 153 | Type string 154 | Timestamp time.Time 155 | ViewsCount int 156 | ReactionCounts map[string]int 157 | 158 | // This is only present when fetching messages, not in live updates 159 | Message *waE2E.Message 160 | } 161 | 162 | type GraphQLErrorExtensions struct { 163 | ErrorCode int `json:"error_code"` 164 | IsRetryable bool `json:"is_retryable"` 165 | Severity string `json:"severity"` 166 | } 167 | 168 | type GraphQLError struct { 169 | Extensions GraphQLErrorExtensions `json:"extensions"` 170 | Message string `json:"message"` 171 | Path []string `json:"path"` 172 | } 173 | 174 | func (gqle GraphQLError) Error() string { 175 | return fmt.Sprintf("%d %s (%s)", gqle.Extensions.ErrorCode, gqle.Message, gqle.Extensions.Severity) 176 | } 177 | 178 | type GraphQLErrors []GraphQLError 179 | 180 | func (gqles GraphQLErrors) Unwrap() []error { 181 | errs := make([]error, len(gqles)) 182 | for i, gqle := range gqles { 183 | errs[i] = gqle 184 | } 185 | return errs 186 | } 187 | 188 | func (gqles GraphQLErrors) Error() string { 189 | if len(gqles) == 0 { 190 | return "" 191 | } else if len(gqles) == 1 { 192 | return gqles[0].Error() 193 | } else { 194 | return fmt.Sprintf("%v (and %d other errors)", gqles[0], len(gqles)-1) 195 | } 196 | } 197 | 198 | type GraphQLResponse struct { 199 | Data json.RawMessage `json:"data"` 200 | Errors GraphQLErrors `json:"errors"` 201 | } 202 | -------------------------------------------------------------------------------- /types/presence.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package types 8 | 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | type Presence string 14 | 15 | const ( 16 | PresenceAvailable Presence = "available" 17 | PresenceUnavailable Presence = "unavailable" 18 | ) 19 | 20 | type ChatPresence string 21 | 22 | const ( 23 | ChatPresenceComposing ChatPresence = "composing" 24 | ChatPresencePaused ChatPresence = "paused" 25 | ) 26 | 27 | type ChatPresenceMedia string 28 | 29 | const ( 30 | ChatPresenceMediaText ChatPresenceMedia = "" 31 | ChatPresenceMediaAudio ChatPresenceMedia = "audio" 32 | ) 33 | 34 | // ReceiptType represents the type of a Receipt event. 35 | type ReceiptType string 36 | 37 | const ( 38 | // ReceiptTypeDelivered means the message was delivered to the device (but the user might not have noticed). 39 | ReceiptTypeDelivered ReceiptType = "" 40 | // ReceiptTypeSender is sent by your other devices when a message you sent is delivered to them. 41 | ReceiptTypeSender ReceiptType = "sender" 42 | // ReceiptTypeRetry means the message was delivered to the device, but decrypting the message failed. 43 | ReceiptTypeRetry ReceiptType = "retry" 44 | // ReceiptTypeRead means the user opened the chat and saw the message. 45 | ReceiptTypeRead ReceiptType = "read" 46 | // ReceiptTypeReadSelf means the current user read a message from a different device, and has read receipts disabled in privacy settings. 47 | ReceiptTypeReadSelf ReceiptType = "read-self" 48 | // ReceiptTypePlayed means the user opened a view-once media message. 49 | // 50 | // This is dispatched for both incoming and outgoing messages when played. If the current user opened the media, 51 | // it means the media should be removed from all devices. If a recipient opened the media, it's just a notification 52 | // for the sender that the media was viewed. 53 | ReceiptTypePlayed ReceiptType = "played" 54 | // ReceiptTypePlayedSelf probably means the current user opened a view-once media message from a different device, 55 | // and has read receipts disabled in privacy settings. 56 | ReceiptTypePlayedSelf ReceiptType = "played-self" 57 | 58 | ReceiptTypeServerError ReceiptType = "server-error" 59 | ReceiptTypeInactive ReceiptType = "inactive" 60 | ReceiptTypePeerMsg ReceiptType = "peer_msg" 61 | ReceiptTypeHistorySync ReceiptType = "hist_sync" 62 | ) 63 | 64 | // GoString returns the name of the Go constant for the ReceiptType value. 65 | func (rt ReceiptType) GoString() string { 66 | switch rt { 67 | case ReceiptTypeRead: 68 | return "types.ReceiptTypeRead" 69 | case ReceiptTypeReadSelf: 70 | return "types.ReceiptTypeReadSelf" 71 | case ReceiptTypeDelivered: 72 | return "types.ReceiptTypeDelivered" 73 | case ReceiptTypePlayed: 74 | return "types.ReceiptTypePlayed" 75 | default: 76 | return fmt.Sprintf("types.ReceiptType(%#v)", string(rt)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waSocket 8 | 9 | import ( 10 | "fmt" 11 | "io" 12 | "net/http" 13 | "regexp" 14 | "strconv" 15 | 16 | "github.com/idlethorn/waSocket/socket" 17 | "github.com/idlethorn/waSocket/store" 18 | ) 19 | 20 | var clientVersionRegex = regexp.MustCompile(`"client_revision":(\d+),`) 21 | 22 | // GetLatestVersion returns the latest version number from web.whatsapp.com. 23 | // 24 | // After fetching, you can update the version to use using store.SetWAVersion, e.g. 25 | // 26 | // latestVer, err := GetLatestVersion(nil) 27 | // if err != nil { 28 | // return err 29 | // } 30 | // store.SetWAVersion(*latestVer) 31 | func GetLatestVersion(httpClient *http.Client) (*store.WAVersionContainer, error) { 32 | req, err := http.NewRequest(http.MethodGet, socket.Origin, nil) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to prepare request: %w", err) 35 | } 36 | req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36") 37 | req.Header.Set("Sec-Fetch-Dest", "document") 38 | req.Header.Set("Sec-Fetch-Mode", "navigate") 39 | req.Header.Set("Sec-Fetch-Site", "none") 40 | req.Header.Set("Sec-Fetch-User", "?1") 41 | req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") 42 | req.Header.Set("Accept-Language", "en-US,en;q=0.9") 43 | if httpClient == nil { 44 | httpClient = http.DefaultClient 45 | } 46 | resp, err := httpClient.Do(req) 47 | if err != nil { 48 | return nil, fmt.Errorf("failed to send request: %w", err) 49 | } 50 | data, err := io.ReadAll(resp.Body) 51 | _ = resp.Body.Close() 52 | if err != nil { 53 | return nil, fmt.Errorf("failed to read response: %w", err) 54 | } else if resp.StatusCode != 200 { 55 | return nil, fmt.Errorf("unexpected response with status %d: %s", resp.StatusCode, data) 56 | } else if match := clientVersionRegex.FindSubmatch(data); len(match) == 0 { 57 | return nil, fmt.Errorf("version number not found") 58 | } else if parsedVer, err := strconv.ParseInt(string(match[1]), 10, 64); err != nil { 59 | return nil, fmt.Errorf("failed to parse version number: %w", err) 60 | } else { 61 | return &store.WAVersionContainer{2, 3000, uint32(parsedVer)}, nil 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /util/gcmutil/gcm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package gcmutil 8 | 9 | import ( 10 | "crypto/aes" 11 | "crypto/cipher" 12 | "fmt" 13 | ) 14 | 15 | func Prepare(secretKey []byte) (gcm cipher.AEAD, err error) { 16 | var block cipher.Block 17 | if block, err = aes.NewCipher(secretKey); err != nil { 18 | err = fmt.Errorf("failed to initialize AES cipher: %w", err) 19 | } else if gcm, err = cipher.NewGCM(block); err != nil { 20 | err = fmt.Errorf("failed to initialize GCM: %w", err) 21 | } 22 | return 23 | } 24 | 25 | func Decrypt(secretKey, iv, ciphertext, additionalData []byte) ([]byte, error) { 26 | if gcm, err := Prepare(secretKey); err != nil { 27 | return nil, err 28 | } else if plaintext, decryptErr := gcm.Open(nil, iv, ciphertext, additionalData); decryptErr != nil { 29 | return nil, decryptErr 30 | } else { 31 | return plaintext, nil 32 | } 33 | } 34 | 35 | func Encrypt(secretKey, iv, plaintext, additionalData []byte) ([]byte, error) { 36 | if gcm, err := Prepare(secretKey); err != nil { 37 | return nil, err 38 | } else { 39 | return gcm.Seal(nil, iv, plaintext, additionalData), nil 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /util/hkdfutil/hkdf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package hkdfutil contains a simple wrapper for golang.org/x/crypto/hkdf that reads a specified number of bytes. 8 | package hkdfutil 9 | 10 | import ( 11 | "crypto/sha256" 12 | "fmt" 13 | 14 | "golang.org/x/crypto/hkdf" 15 | ) 16 | 17 | func SHA256(key, salt, info []byte, length uint8) []byte { 18 | data := make([]byte, length) 19 | h := hkdf.New(sha256.New, key, salt, info) 20 | n, err := h.Read(data) 21 | if err != nil { 22 | // Length is limited to 255 by being uint8, so these errors can't actually happen 23 | panic(fmt.Errorf("failed to expand key: %w", err)) 24 | } else if uint8(n) != length { 25 | panic(fmt.Errorf("didn't read enough bytes (got %d, wanted %d)", n, length)) 26 | } 27 | return data 28 | } 29 | -------------------------------------------------------------------------------- /util/keys/keypair.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package keys contains a utility struct for elliptic curve keypairs. 8 | package keys 9 | 10 | import ( 11 | "go.mau.fi/libsignal/ecc" 12 | "go.mau.fi/util/random" 13 | "golang.org/x/crypto/curve25519" 14 | ) 15 | 16 | type KeyPair struct { 17 | Pub *[32]byte 18 | Priv *[32]byte 19 | } 20 | 21 | var _ ecc.ECPublicKeyable 22 | 23 | func NewKeyPairFromPrivateKey(priv [32]byte) *KeyPair { 24 | var kp KeyPair 25 | kp.Priv = &priv 26 | var pub [32]byte 27 | curve25519.ScalarBaseMult(&pub, kp.Priv) 28 | kp.Pub = &pub 29 | return &kp 30 | } 31 | 32 | func NewKeyPair() *KeyPair { 33 | priv := *(*[32]byte)(random.Bytes(32)) 34 | 35 | priv[0] &= 248 36 | priv[31] &= 127 37 | priv[31] |= 64 38 | 39 | return NewKeyPairFromPrivateKey(priv) 40 | } 41 | 42 | func (kp *KeyPair) CreateSignedPreKey(keyID uint32) *PreKey { 43 | newKey := NewPreKey(keyID) 44 | newKey.Signature = kp.Sign(&newKey.KeyPair) 45 | return newKey 46 | } 47 | 48 | func (kp *KeyPair) Sign(keyToSign *KeyPair) *[64]byte { 49 | pubKeyForSignature := make([]byte, 33) 50 | pubKeyForSignature[0] = ecc.DjbType 51 | copy(pubKeyForSignature[1:], keyToSign.Pub[:]) 52 | 53 | signature := ecc.CalculateSignature(ecc.NewDjbECPrivateKey(*kp.Priv), pubKeyForSignature) 54 | return &signature 55 | } 56 | 57 | type PreKey struct { 58 | KeyPair 59 | KeyID uint32 60 | Signature *[64]byte 61 | } 62 | 63 | func NewPreKey(keyID uint32) *PreKey { 64 | return &PreKey{ 65 | KeyPair: *NewKeyPair(), 66 | KeyID: keyID, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /util/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Package waLog contains a simple logger interface used by the other waSocket packages. 8 | package waLog 9 | 10 | import ( 11 | "fmt" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // Logger is a simple logger interface that can have subloggers for specific areas. 17 | type Logger interface { 18 | Warnf(msg string, args ...interface{}) 19 | Errorf(msg string, args ...interface{}) 20 | Infof(msg string, args ...interface{}) 21 | Debugf(msg string, args ...interface{}) 22 | Sub(module string) Logger 23 | } 24 | 25 | type noopLogger struct{} 26 | 27 | func (n *noopLogger) Errorf(_ string, _ ...interface{}) {} 28 | func (n *noopLogger) Warnf(_ string, _ ...interface{}) {} 29 | func (n *noopLogger) Infof(_ string, _ ...interface{}) {} 30 | func (n *noopLogger) Debugf(_ string, _ ...interface{}) {} 31 | func (n *noopLogger) Sub(_ string) Logger { return n } 32 | 33 | // Noop is a no-op Logger implementation that silently drops everything. 34 | var Noop Logger = &noopLogger{} 35 | 36 | type stdoutLogger struct { 37 | mod string 38 | color bool 39 | min int 40 | } 41 | 42 | var colors = map[string]string{ 43 | "INFO": "\033[36m", 44 | "WARN": "\033[33m", 45 | "ERROR": "\033[31m", 46 | } 47 | 48 | var levelToInt = map[string]int{ 49 | "": -1, 50 | "DEBUG": 0, 51 | "INFO": 1, 52 | "WARN": 2, 53 | "ERROR": 3, 54 | } 55 | 56 | func (s *stdoutLogger) outputf(level, msg string, args ...interface{}) { 57 | if levelToInt[level] < s.min { 58 | return 59 | } 60 | var colorStart, colorReset string 61 | if s.color { 62 | colorStart = colors[level] 63 | colorReset = "\033[0m" 64 | } 65 | fmt.Printf("%s%s [%s %s] %s%s\n", time.Now().Format("15:04:05.000"), colorStart, s.mod, level, fmt.Sprintf(msg, args...), colorReset) 66 | } 67 | 68 | func (s *stdoutLogger) Errorf(msg string, args ...interface{}) { s.outputf("ERROR", msg, args...) } 69 | func (s *stdoutLogger) Warnf(msg string, args ...interface{}) { s.outputf("WARN", msg, args...) } 70 | func (s *stdoutLogger) Infof(msg string, args ...interface{}) { s.outputf("INFO", msg, args...) } 71 | func (s *stdoutLogger) Debugf(msg string, args ...interface{}) { s.outputf("DEBUG", msg, args...) } 72 | func (s *stdoutLogger) Sub(mod string) Logger { 73 | return &stdoutLogger{mod: fmt.Sprintf("%s/%s", s.mod, mod), color: s.color, min: s.min} 74 | } 75 | 76 | // Stdout is a simple Logger implementation that outputs to stdout. The module name given is included in log lines. 77 | // 78 | // minLevel specifies the minimum log level to output. An empty string will output all logs. 79 | // 80 | // If color is true, then info, warn and error logs will be colored cyan, yellow and red respectively using ANSI color escape codes. 81 | func Stdout(module string, minLevel string, color bool) Logger { 82 | return &stdoutLogger{mod: module, color: color, min: levelToInt[strings.ToUpper(minLevel)]} 83 | } 84 | -------------------------------------------------------------------------------- /util/log/zerolog.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Tulir Asokan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | package waLog 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/rs/zerolog" 13 | ) 14 | 15 | type zeroLogger struct { 16 | mod string 17 | zerolog.Logger 18 | } 19 | 20 | // Zerolog wraps a [zerolog.Logger] to implement the [Logger] interface. 21 | // 22 | // Subloggers will be created by setting the `sublogger` field in the log context. 23 | func Zerolog(log zerolog.Logger) Logger { 24 | return &zeroLogger{Logger: log} 25 | } 26 | 27 | func (z *zeroLogger) Warnf(msg string, args ...any) { z.Warn().Msgf(msg, args...) } 28 | func (z *zeroLogger) Errorf(msg string, args ...any) { z.Error().Msgf(msg, args...) } 29 | func (z *zeroLogger) Infof(msg string, args ...any) { z.Info().Msgf(msg, args...) } 30 | func (z *zeroLogger) Debugf(msg string, args ...any) { z.Debug().Msgf(msg, args...) } 31 | func (z *zeroLogger) Sub(module string) Logger { 32 | if z.mod != "" { 33 | module = fmt.Sprintf("%s/%s", z.mod, module) 34 | } 35 | return &zeroLogger{mod: module, Logger: z.Logger.With().Str("sublogger", module).Logger()} 36 | } 37 | 38 | var _ Logger = &zeroLogger{} 39 | --------------------------------------------------------------------------------