Update dependencies and remove old matterclient lib (#2067)
This commit is contained in:
13
vendor/go.mau.fi/whatsmeow/.editorconfig
vendored
Normal file
13
vendor/go.mau.fi/whatsmeow/.editorconfig
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
3
vendor/go.mau.fi/whatsmeow/README.md
vendored
3
vendor/go.mau.fi/whatsmeow/README.md
vendored
@@ -27,12 +27,11 @@ Most core features are already present:
|
||||
* Joining via invite messages, using and creating invite links
|
||||
* Sending and receiving typing notifications
|
||||
* Sending and receiving delivery and read receipts
|
||||
* Reading app state (contact list, chat pin/mute status, etc)
|
||||
* Reading and writing app state (contact list, chat pin/mute status, etc)
|
||||
* Sending and handling retry receipts if message decryption fails
|
||||
* Sending status messages (experimental, may not work for large contact lists)
|
||||
|
||||
Things that are not yet implemented:
|
||||
|
||||
* Writing app state (contact list, chat pin/mute status, etc)
|
||||
* Sending broadcast list messages (this is not supported on WhatsApp web either)
|
||||
* Calls
|
||||
|
||||
152
vendor/go.mau.fi/whatsmeow/appstate.go
vendored
152
vendor/go.mau.fi/whatsmeow/appstate.go
vendored
@@ -74,7 +74,7 @@ func (cli *Client) FetchAppState(name appstate.WAPatchName, fullSync, onlyIfNotS
|
||||
}
|
||||
}
|
||||
for _, mutation := range mutations {
|
||||
cli.dispatchAppState(mutation, !fullSync || cli.EmitAppStateEventsOnFullSync)
|
||||
cli.dispatchAppState(mutation, fullSync, cli.EmitAppStateEventsOnFullSync)
|
||||
}
|
||||
}
|
||||
if fullSync {
|
||||
@@ -105,7 +105,10 @@ func (cli *Client) filterContacts(mutations []appstate.Mutation) ([]appstate.Mut
|
||||
return filteredMutations, contacts
|
||||
}
|
||||
|
||||
func (cli *Client) dispatchAppState(mutation appstate.Mutation, dispatchEvts bool) {
|
||||
func (cli *Client) dispatchAppState(mutation appstate.Mutation, fullSync bool, emitOnFullSync bool) {
|
||||
|
||||
dispatchEvts := !fullSync || emitOnFullSync
|
||||
|
||||
if mutation.Operation != waProto.SyncdMutation_SET {
|
||||
return
|
||||
}
|
||||
@@ -118,87 +121,108 @@ func (cli *Client) dispatchAppState(mutation appstate.Mutation, dispatchEvts boo
|
||||
if len(mutation.Index) > 1 {
|
||||
jid, _ = types.ParseJID(mutation.Index[1])
|
||||
}
|
||||
ts := time.Unix(mutation.Action.GetTimestamp(), 0)
|
||||
ts := time.UnixMilli(mutation.Action.GetTimestamp())
|
||||
|
||||
var storeUpdateError error
|
||||
var eventToDispatch interface{}
|
||||
switch mutation.Index[0] {
|
||||
case "mute":
|
||||
case appstate.IndexMute:
|
||||
act := mutation.Action.GetMuteAction()
|
||||
eventToDispatch = &events.Mute{JID: jid, Timestamp: ts, Action: act}
|
||||
eventToDispatch = &events.Mute{JID: jid, Timestamp: ts, Action: act, FromFullSync: fullSync}
|
||||
var mutedUntil time.Time
|
||||
if act.GetMuted() {
|
||||
mutedUntil = time.Unix(act.GetMuteEndTimestamp(), 0)
|
||||
mutedUntil = time.UnixMilli(act.GetMuteEndTimestamp())
|
||||
}
|
||||
if cli.Store.ChatSettings != nil {
|
||||
storeUpdateError = cli.Store.ChatSettings.PutMutedUntil(jid, mutedUntil)
|
||||
}
|
||||
case "pin_v1":
|
||||
case appstate.IndexPin:
|
||||
act := mutation.Action.GetPinAction()
|
||||
eventToDispatch = &events.Pin{JID: jid, Timestamp: ts, Action: act}
|
||||
eventToDispatch = &events.Pin{JID: jid, Timestamp: ts, Action: act, FromFullSync: fullSync}
|
||||
if cli.Store.ChatSettings != nil {
|
||||
storeUpdateError = cli.Store.ChatSettings.PutPinned(jid, act.GetPinned())
|
||||
}
|
||||
case "archive":
|
||||
case appstate.IndexArchive:
|
||||
act := mutation.Action.GetArchiveChatAction()
|
||||
eventToDispatch = &events.Archive{JID: jid, Timestamp: ts, Action: act}
|
||||
eventToDispatch = &events.Archive{JID: jid, Timestamp: ts, Action: act, FromFullSync: fullSync}
|
||||
if cli.Store.ChatSettings != nil {
|
||||
storeUpdateError = cli.Store.ChatSettings.PutArchived(jid, act.GetArchived())
|
||||
}
|
||||
case "contact":
|
||||
case appstate.IndexContact:
|
||||
act := mutation.Action.GetContactAction()
|
||||
eventToDispatch = &events.Contact{JID: jid, Timestamp: ts, Action: act}
|
||||
eventToDispatch = &events.Contact{JID: jid, Timestamp: ts, Action: act, FromFullSync: fullSync}
|
||||
if cli.Store.Contacts != nil {
|
||||
storeUpdateError = cli.Store.Contacts.PutContactName(jid, act.GetFirstName(), act.GetFullName())
|
||||
}
|
||||
case "deleteChat":
|
||||
case appstate.IndexClearChat:
|
||||
act := mutation.Action.GetClearChatAction()
|
||||
eventToDispatch = &events.ClearChat{JID: jid, Timestamp: ts, Action: act, FromFullSync: fullSync}
|
||||
case appstate.IndexDeleteChat:
|
||||
act := mutation.Action.GetDeleteChatAction()
|
||||
eventToDispatch = &events.DeleteChat{JID: jid, Timestamp: ts, Action: act}
|
||||
case "star":
|
||||
eventToDispatch = &events.DeleteChat{JID: jid, Timestamp: ts, Action: act, FromFullSync: fullSync}
|
||||
case appstate.IndexStar:
|
||||
if len(mutation.Index) < 5 {
|
||||
return
|
||||
}
|
||||
evt := events.Star{
|
||||
ChatJID: jid,
|
||||
MessageID: mutation.Index[2],
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetStarAction(),
|
||||
IsFromMe: mutation.Index[3] == "1",
|
||||
ChatJID: jid,
|
||||
MessageID: mutation.Index[2],
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetStarAction(),
|
||||
IsFromMe: mutation.Index[3] == "1",
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
if mutation.Index[4] != "0" {
|
||||
evt.SenderJID, _ = types.ParseJID(mutation.Index[4])
|
||||
}
|
||||
eventToDispatch = &evt
|
||||
case "deleteMessageForMe":
|
||||
case appstate.IndexDeleteMessageForMe:
|
||||
if len(mutation.Index) < 5 {
|
||||
return
|
||||
}
|
||||
evt := events.DeleteForMe{
|
||||
ChatJID: jid,
|
||||
MessageID: mutation.Index[2],
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetDeleteMessageForMeAction(),
|
||||
IsFromMe: mutation.Index[3] == "1",
|
||||
ChatJID: jid,
|
||||
MessageID: mutation.Index[2],
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetDeleteMessageForMeAction(),
|
||||
IsFromMe: mutation.Index[3] == "1",
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
if mutation.Index[4] != "0" {
|
||||
evt.SenderJID, _ = types.ParseJID(mutation.Index[4])
|
||||
}
|
||||
eventToDispatch = &evt
|
||||
case "markChatAsRead":
|
||||
case appstate.IndexMarkChatAsRead:
|
||||
eventToDispatch = &events.MarkChatAsRead{
|
||||
JID: jid,
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetMarkChatAsReadAction(),
|
||||
JID: jid,
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetMarkChatAsReadAction(),
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
case appstate.IndexSettingPushName:
|
||||
eventToDispatch = &events.PushNameSetting{
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetPushNameSetting(),
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
case "setting_pushName":
|
||||
eventToDispatch = &events.PushNameSetting{Timestamp: ts, Action: mutation.Action.GetPushNameSetting()}
|
||||
cli.Store.PushName = mutation.Action.GetPushNameSetting().GetName()
|
||||
err := cli.Store.Save()
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to save device store after updating push name: %v", err)
|
||||
}
|
||||
case "setting_unarchiveChats":
|
||||
eventToDispatch = &events.UnarchiveChatsSetting{Timestamp: ts, Action: mutation.Action.GetUnarchiveChatsSetting()}
|
||||
case appstate.IndexSettingUnarchiveChats:
|
||||
eventToDispatch = &events.UnarchiveChatsSetting{
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetUnarchiveChatsSetting(),
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
case appstate.IndexUserStatusMute:
|
||||
eventToDispatch = &events.UserStatusMute{
|
||||
JID: jid,
|
||||
Timestamp: ts,
|
||||
Action: mutation.Action.GetUserStatusMuteAction(),
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
}
|
||||
if storeUpdateError != nil {
|
||||
cli.Log.Errorf("Failed to update device store after app state mutation: %v", storeUpdateError)
|
||||
@@ -280,3 +304,63 @@ func (cli *Client) requestAppStateKeys(ctx context.Context, rawKeyIDs [][]byte)
|
||||
cli.Log.Warnf("Failed to send app state key request: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SendAppState sends the given app state patch, then resyncs that app state type from the server
|
||||
// to update local caches and send events for the updates.
|
||||
//
|
||||
// You can use the Build methods in the appstate package to build the parameter for this method, e.g.
|
||||
//
|
||||
// cli.SendAppState(appstate.BuildMute(targetJID, true, 24 * time.Hour))
|
||||
func (cli *Client) SendAppState(patch appstate.PatchInfo) error {
|
||||
version, hash, err := cli.Store.AppState.GetAppStateVersion(string(patch.Type))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO create new key instead of reusing the primary client's keys
|
||||
latestKeyID, err := cli.Store.AppStateKeys.GetLatestAppStateSyncKeyID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest app state key ID: %w", err)
|
||||
} else if latestKeyID == nil {
|
||||
return fmt.Errorf("no app state keys found, creating app state keys is not yet supported")
|
||||
}
|
||||
|
||||
state := appstate.HashState{Version: version, Hash: hash}
|
||||
|
||||
encodedPatch, err := cli.appStateProc.EncodePatch(latestKeyID, state, patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "w:sync:app:state",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "sync",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "collection",
|
||||
Attrs: waBinary.Attrs{
|
||||
"name": string(patch.Type),
|
||||
"version": version,
|
||||
"return_snapshot": false,
|
||||
},
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "patch",
|
||||
Content: encodedPatch,
|
||||
}},
|
||||
}},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respCollection := resp.GetChildByTag("sync", "collection")
|
||||
respCollectionAttr := respCollection.AttrGetter()
|
||||
if respCollectionAttr.OptionalString("type") == "error" {
|
||||
// TODO parse error properly
|
||||
return fmt.Errorf("%w: %s", ErrAppStateUpdate, respCollection.XMLString())
|
||||
}
|
||||
|
||||
return cli.FetchAppState(patch.Type, false, false)
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ func (proc *Processor) DecodePatches(list *PatchList, initialState HashState, va
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
patchMAC := generatePatchMAC(patch, list.Name, keys.PatchMAC)
|
||||
patchMAC := generatePatchMAC(patch, list.Name, keys.PatchMAC, patch.GetVersion().GetVersion())
|
||||
if !bytes.Equal(patchMAC, patch.GetPatchMac()) {
|
||||
err = fmt.Errorf("failed to verify patch v%d: %w", version, ErrMismatchingPatchMAC)
|
||||
return
|
||||
|
||||
200
vendor/go.mau.fi/whatsmeow/appstate/encode.go
vendored
Normal file
200
vendor/go.mau.fi/whatsmeow/appstate/encode.go
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
package appstate
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/util/cbcutil"
|
||||
)
|
||||
|
||||
// MutationInfo contains information about a single mutation to the app state.
|
||||
type MutationInfo struct {
|
||||
// Index contains the thing being mutated (like `mute` or `pin_v1`), followed by parameters like the target JID.
|
||||
Index []string
|
||||
// Version is a static number that depends on the thing being mutated.
|
||||
Version int32
|
||||
// Value contains the data for the mutation.
|
||||
Value *waProto.SyncActionValue
|
||||
}
|
||||
|
||||
// PatchInfo contains information about a patch to the app state.
|
||||
// A patch can contain multiple mutations, as long as all mutations are in the same app state type.
|
||||
type PatchInfo struct {
|
||||
// Timestamp is the time when the patch was created. This will be filled automatically in EncodePatch if it's zero.
|
||||
Timestamp time.Time
|
||||
// Type is the app state type being mutated.
|
||||
Type WAPatchName
|
||||
// Mutations contains the individual mutations to apply to the app state in this patch.
|
||||
Mutations []MutationInfo
|
||||
}
|
||||
|
||||
// BuildMute builds an app state patch for muting or unmuting a chat.
|
||||
//
|
||||
// If mute is true and the mute duration is zero, the chat is muted forever.
|
||||
func BuildMute(target types.JID, mute bool, muteDuration time.Duration) PatchInfo {
|
||||
var muteEndTimestamp *int64
|
||||
if muteDuration > 0 {
|
||||
muteEndTimestamp = proto.Int64(time.Now().Add(muteDuration).UnixMilli())
|
||||
}
|
||||
|
||||
return PatchInfo{
|
||||
Type: WAPatchRegularHigh,
|
||||
Mutations: []MutationInfo{{
|
||||
Index: []string{IndexMute, target.String()},
|
||||
Version: 2,
|
||||
Value: &waProto.SyncActionValue{
|
||||
MuteAction: &waProto.MuteAction{
|
||||
Muted: proto.Bool(mute),
|
||||
MuteEndTimestamp: muteEndTimestamp,
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func newPinMutationInfo(target types.JID, pin bool) MutationInfo {
|
||||
return MutationInfo{
|
||||
Index: []string{IndexPin, target.String()},
|
||||
Version: 5,
|
||||
Value: &waProto.SyncActionValue{
|
||||
PinAction: &waProto.PinAction{
|
||||
Pinned: &pin,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildPin builds an app state patch for pinning or unpinning a chat.
|
||||
func BuildPin(target types.JID, pin bool) PatchInfo {
|
||||
return PatchInfo{
|
||||
Type: WAPatchRegularLow,
|
||||
Mutations: []MutationInfo{
|
||||
newPinMutationInfo(target, pin),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildArchive builds an app state patch for archiving or unarchiving a chat.
|
||||
//
|
||||
// The last message timestamp and last message key are optional and can be set to zero values (`time.Time{}` and `nil`).
|
||||
//
|
||||
// Archiving a chat will also unpin it automatically.
|
||||
func BuildArchive(target types.JID, archive bool, lastMessageTimestamp time.Time, lastMessageKey *waProto.MessageKey) PatchInfo {
|
||||
if lastMessageTimestamp.IsZero() {
|
||||
lastMessageTimestamp = time.Now()
|
||||
}
|
||||
archiveMutationInfo := MutationInfo{
|
||||
Index: []string{IndexArchive, target.String()},
|
||||
Version: 3,
|
||||
Value: &waProto.SyncActionValue{
|
||||
ArchiveChatAction: &waProto.ArchiveChatAction{
|
||||
Archived: &archive,
|
||||
MessageRange: &waProto.SyncActionMessageRange{
|
||||
LastMessageTimestamp: proto.Int64(lastMessageTimestamp.Unix()),
|
||||
// TODO set LastSystemMessageTimestamp?
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if lastMessageKey != nil {
|
||||
archiveMutationInfo.Value.ArchiveChatAction.MessageRange.Messages = []*waProto.SyncActionMessage{{
|
||||
Key: lastMessageKey,
|
||||
Timestamp: proto.Int64(lastMessageTimestamp.Unix()),
|
||||
}}
|
||||
}
|
||||
|
||||
mutations := []MutationInfo{archiveMutationInfo}
|
||||
if archive {
|
||||
mutations = append(mutations, newPinMutationInfo(target, false))
|
||||
}
|
||||
|
||||
result := PatchInfo{
|
||||
Type: WAPatchRegularLow,
|
||||
Mutations: mutations,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (proc *Processor) EncodePatch(keyID []byte, state HashState, patchInfo PatchInfo) ([]byte, error) {
|
||||
keys, err := proc.getAppStateKey(keyID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get app state key details with key ID %x: %w", keyID, err)
|
||||
}
|
||||
|
||||
if patchInfo.Timestamp.IsZero() {
|
||||
patchInfo.Timestamp = time.Now()
|
||||
}
|
||||
|
||||
mutations := make([]*waProto.SyncdMutation, 0, len(patchInfo.Mutations))
|
||||
for _, mutationInfo := range patchInfo.Mutations {
|
||||
mutationInfo.Value.Timestamp = proto.Int64(patchInfo.Timestamp.UnixMilli())
|
||||
|
||||
indexBytes, err := json.Marshal(mutationInfo.Index)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal mutation index: %w", err)
|
||||
}
|
||||
|
||||
pbObj := &waProto.SyncActionData{
|
||||
Index: indexBytes,
|
||||
Value: mutationInfo.Value,
|
||||
Padding: []byte{},
|
||||
Version: &mutationInfo.Version,
|
||||
}
|
||||
|
||||
content, err := proto.Marshal(pbObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal mutation: %w", err)
|
||||
}
|
||||
|
||||
encryptedContent, err := cbcutil.Encrypt(keys.ValueEncryption, nil, content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt mutation: %w", err)
|
||||
}
|
||||
|
||||
valueMac := generateContentMAC(waProto.SyncdMutation_SET, encryptedContent, keyID, keys.ValueMAC)
|
||||
indexMac := concatAndHMAC(sha256.New, keys.Index, indexBytes)
|
||||
|
||||
mutations = append(mutations, &waProto.SyncdMutation{
|
||||
Operation: waProto.SyncdMutation_SET.Enum(),
|
||||
Record: &waProto.SyncdRecord{
|
||||
Index: &waProto.SyncdIndex{Blob: indexMac},
|
||||
Value: &waProto.SyncdValue{Blob: append(encryptedContent, valueMac...)},
|
||||
KeyId: &waProto.KeyId{Id: keyID},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
warn, err := state.updateHash(mutations, func(indexMAC []byte, _ int) ([]byte, error) {
|
||||
return proc.Store.AppState.GetAppStateMutationMAC(string(patchInfo.Type), indexMAC)
|
||||
})
|
||||
if len(warn) > 0 {
|
||||
proc.Log.Warnf("Warnings while updating hash for %s (sending new app state): %+v", patchInfo.Type, warn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update state hash: %w", err)
|
||||
}
|
||||
|
||||
state.Version += 1
|
||||
|
||||
syncdPatch := &waProto.SyncdPatch{
|
||||
SnapshotMac: state.generateSnapshotMAC(patchInfo.Type, keys.SnapshotMAC),
|
||||
KeyId: &waProto.KeyId{Id: keyID},
|
||||
Mutations: mutations,
|
||||
}
|
||||
syncdPatch.PatchMac = generatePatchMAC(syncdPatch, patchInfo.Type, keys.PatchMAC, state.Version)
|
||||
|
||||
result, err := proto.Marshal(syncdPatch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal compiled patch: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
4
vendor/go.mau.fi/whatsmeow/appstate/hash.go
vendored
4
vendor/go.mau.fi/whatsmeow/appstate/hash.go
vendored
@@ -77,14 +77,14 @@ func (hs *HashState) generateSnapshotMAC(name WAPatchName, key []byte) []byte {
|
||||
return concatAndHMAC(sha256.New, key, hs.Hash[:], uint64ToBytes(hs.Version), []byte(name))
|
||||
}
|
||||
|
||||
func generatePatchMAC(patch *waProto.SyncdPatch, name WAPatchName, key []byte) []byte {
|
||||
func generatePatchMAC(patch *waProto.SyncdPatch, name WAPatchName, key []byte, version uint64) []byte {
|
||||
dataToHash := make([][]byte, len(patch.GetMutations())+3)
|
||||
dataToHash[0] = patch.GetSnapshotMac()
|
||||
for i, mutation := range patch.Mutations {
|
||||
val := mutation.GetRecord().GetValue().GetBlob()
|
||||
dataToHash[i+1] = val[len(val)-32:]
|
||||
}
|
||||
dataToHash[len(dataToHash)-2] = uint64ToBytes(patch.GetVersion().GetVersion())
|
||||
dataToHash[len(dataToHash)-2] = uint64ToBytes(version)
|
||||
dataToHash[len(dataToHash)-1] = []byte(name)
|
||||
return concatAndHMAC(sha256.New, key, dataToHash...)
|
||||
}
|
||||
|
||||
16
vendor/go.mau.fi/whatsmeow/appstate/keys.go
vendored
16
vendor/go.mau.fi/whatsmeow/appstate/keys.go
vendored
@@ -35,6 +35,22 @@ const (
|
||||
// AllPatchNames contains all currently known patch state names.
|
||||
var AllPatchNames = [...]WAPatchName{WAPatchCriticalBlock, WAPatchCriticalUnblockLow, WAPatchRegularHigh, WAPatchRegular, WAPatchRegularLow}
|
||||
|
||||
// Constants for the first part of app state indexes.
|
||||
const (
|
||||
IndexMute = "mute"
|
||||
IndexPin = "pin_v1"
|
||||
IndexArchive = "archive"
|
||||
IndexContact = "contact"
|
||||
IndexClearChat = "clearChat"
|
||||
IndexDeleteChat = "deleteChat"
|
||||
IndexStar = "star"
|
||||
IndexDeleteMessageForMe = "deleteMessageForMe"
|
||||
IndexMarkChatAsRead = "markChatAsRead"
|
||||
IndexSettingPushName = "setting_pushName"
|
||||
IndexSettingUnarchiveChats = "setting_unarchiveChats"
|
||||
IndexUserStatusMute = "userStatusMute"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
keyCache map[string]ExpandedAppStateKeys
|
||||
keyCacheLock sync.Mutex
|
||||
|
||||
8003
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
vendored
8003
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
vendored
File diff suppressed because it is too large
Load Diff
BIN
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
vendored
BIN
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
vendored
Binary file not shown.
440
vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
vendored
440
vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
vendored
@@ -16,6 +16,7 @@ message ADVSignedDeviceIdentity {
|
||||
message ADVSignedDeviceIdentityHMAC {
|
||||
optional bytes details = 1;
|
||||
optional bytes hmac = 2;
|
||||
optional ADVEncryptionType accountType = 3;
|
||||
}
|
||||
|
||||
message ADVKeyIndexList {
|
||||
@@ -23,12 +24,19 @@ message ADVKeyIndexList {
|
||||
optional uint64 timestamp = 2;
|
||||
optional uint32 currentIndex = 3;
|
||||
repeated uint32 validIndexes = 4 [packed=true];
|
||||
optional ADVEncryptionType accountType = 5;
|
||||
}
|
||||
|
||||
enum ADVEncryptionType {
|
||||
E2EE = 0;
|
||||
HOSTED = 1;
|
||||
}
|
||||
message ADVDeviceIdentity {
|
||||
optional uint32 rawId = 1;
|
||||
optional uint64 timestamp = 2;
|
||||
optional uint32 keyIndex = 3;
|
||||
optional ADVEncryptionType accountType = 4;
|
||||
optional ADVEncryptionType deviceType = 5;
|
||||
}
|
||||
|
||||
message DeviceProps {
|
||||
@@ -51,11 +59,18 @@ message DeviceProps {
|
||||
IOS_CATALYST = 15;
|
||||
ANDROID_PHONE = 16;
|
||||
ANDROID_AMBIGUOUS = 17;
|
||||
WEAR_OS = 18;
|
||||
AR_WRIST = 19;
|
||||
AR_DEVICE = 20;
|
||||
UWP = 21;
|
||||
VR = 22;
|
||||
}
|
||||
message HistorySyncConfig {
|
||||
optional uint32 fullSyncDaysLimit = 1;
|
||||
optional uint32 fullSyncSizeMbLimit = 2;
|
||||
optional uint32 storageQuotaMb = 3;
|
||||
optional bool inlineInitialPayloadInE2EeMsg = 4;
|
||||
optional uint32 recentSyncDaysLimit = 5;
|
||||
}
|
||||
|
||||
message AppVersion {
|
||||
@@ -73,76 +88,6 @@ message DeviceProps {
|
||||
optional HistorySyncConfig historySyncConfig = 5;
|
||||
}
|
||||
|
||||
message PeerDataOperationRequestMessage {
|
||||
message RequestUrlPreview {
|
||||
optional string url = 1;
|
||||
}
|
||||
|
||||
message RequestStickerReupload {
|
||||
optional string fileSha256 = 1;
|
||||
}
|
||||
|
||||
message HistorySyncOnDemandRequest {
|
||||
optional string chatJid = 1;
|
||||
optional string oldestMsgId = 2;
|
||||
optional bool oldestMsgFromMe = 3;
|
||||
optional int32 onDemandMsgCount = 4;
|
||||
optional int64 oldestMsgTimestampMs = 5;
|
||||
}
|
||||
|
||||
optional PeerDataOperationRequestType peerDataOperationRequestType = 1;
|
||||
repeated RequestStickerReupload requestStickerReupload = 2;
|
||||
repeated RequestUrlPreview requestUrlPreview = 3;
|
||||
optional HistorySyncOnDemandRequest historySyncOnDemandRequest = 4;
|
||||
}
|
||||
|
||||
message PaymentInviteMessage {
|
||||
enum ServiceType {
|
||||
UNKNOWN = 0;
|
||||
FBPAY = 1;
|
||||
NOVI = 2;
|
||||
UPI = 3;
|
||||
}
|
||||
optional ServiceType serviceType = 1;
|
||||
optional int64 expiryTimestamp = 2;
|
||||
}
|
||||
|
||||
message OrderMessage {
|
||||
enum OrderSurface {
|
||||
CATALOG = 1;
|
||||
}
|
||||
enum OrderStatus {
|
||||
INQUIRY = 1;
|
||||
}
|
||||
optional string orderId = 1;
|
||||
optional bytes thumbnail = 2;
|
||||
optional int32 itemCount = 3;
|
||||
optional OrderStatus status = 4;
|
||||
optional OrderSurface surface = 5;
|
||||
optional string message = 6;
|
||||
optional string orderTitle = 7;
|
||||
optional string sellerJid = 8;
|
||||
optional string token = 9;
|
||||
optional int64 totalAmount1000 = 10;
|
||||
optional string totalCurrencyCode = 11;
|
||||
optional ContextInfo contextInfo = 17;
|
||||
}
|
||||
|
||||
message LocationMessage {
|
||||
optional double degreesLatitude = 1;
|
||||
optional double degreesLongitude = 2;
|
||||
optional string name = 3;
|
||||
optional string address = 4;
|
||||
optional string url = 5;
|
||||
optional bool isLive = 6;
|
||||
optional uint32 accuracyInMeters = 7;
|
||||
optional float speedInMps = 8;
|
||||
optional uint32 degreesClockwiseFromMagneticNorth = 9;
|
||||
optional string comment = 11;
|
||||
optional bytes jpegThumbnail = 16;
|
||||
optional ContextInfo contextInfo = 17;
|
||||
}
|
||||
|
||||
message LiveLocationMessage {
|
||||
optional double degreesLatitude = 1;
|
||||
optional double degreesLongitude = 2;
|
||||
@@ -250,7 +195,12 @@ message InteractiveResponseMessage {
|
||||
}
|
||||
|
||||
message Body {
|
||||
enum Format {
|
||||
DEFAULT = 0;
|
||||
EXTENSIONS_1 = 1;
|
||||
}
|
||||
optional string text = 1;
|
||||
optional Format format = 2;
|
||||
}
|
||||
|
||||
optional Body body = 1;
|
||||
@@ -293,6 +243,7 @@ message InteractiveMessage {
|
||||
ImageMessage imageMessage = 4;
|
||||
bytes jpegThumbnail = 6;
|
||||
VideoMessage videoMessage = 7;
|
||||
LocationMessage locationMessage = 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +257,11 @@ message InteractiveMessage {
|
||||
optional int32 messageVersion = 3;
|
||||
}
|
||||
|
||||
message CarouselMessage {
|
||||
repeated InteractiveMessage cards = 1;
|
||||
optional int32 messageVersion = 2;
|
||||
}
|
||||
|
||||
message Body {
|
||||
optional string text = 1;
|
||||
}
|
||||
@@ -318,6 +274,7 @@ message InteractiveMessage {
|
||||
ShopMessage shopStorefrontMessage = 4;
|
||||
CollectionMessage collectionMessage = 5;
|
||||
NativeFlowMessage nativeFlowMessage = 6;
|
||||
CarouselMessage carouselMessage = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +331,8 @@ message HistorySyncNotification {
|
||||
optional string originalMessageId = 8;
|
||||
optional uint32 progress = 9;
|
||||
optional int64 oldestMsgInChunkTimestampSec = 10;
|
||||
optional bytes initialHistBootstrapInlinePayload = 11;
|
||||
optional string peerDataRequestSessionId = 12;
|
||||
}
|
||||
|
||||
message HighlyStructuredMessage {
|
||||
@@ -466,13 +425,10 @@ message ExtendedTextMessage {
|
||||
DEFAULT_SUB = 3;
|
||||
}
|
||||
enum FontType {
|
||||
SANS_SERIF = 0;
|
||||
SERIF = 1;
|
||||
NORICAN_REGULAR = 2;
|
||||
BRYNDAN_WRITE = 3;
|
||||
BEBASNEUE_REGULAR = 4;
|
||||
OSWALD_HEAVY = 5;
|
||||
DAMION_REGULAR = 6;
|
||||
SYSTEM = 0;
|
||||
SYSTEM_TEXT = 1;
|
||||
FB_SCRIPT = 2;
|
||||
SYSTEM_BOLD = 6;
|
||||
MORNINGBREEZE_REGULAR = 7;
|
||||
CALISTOGA_REGULAR = 8;
|
||||
EXO2_EXTRABOLD = 9;
|
||||
@@ -510,6 +466,12 @@ message EncReactionMessage {
|
||||
optional bytes encIv = 3;
|
||||
}
|
||||
|
||||
message EncCommentMessage {
|
||||
optional MessageKey targetMessageKey = 1;
|
||||
optional bytes encPayload = 2;
|
||||
optional bytes encIv = 3;
|
||||
}
|
||||
|
||||
message DocumentMessage {
|
||||
optional string url = 1;
|
||||
optional string mimetype = 2;
|
||||
@@ -629,6 +591,21 @@ message ButtonsMessage {
|
||||
}
|
||||
}
|
||||
|
||||
message BotFeedbackMessage {
|
||||
enum BotFeedbackKind {
|
||||
BOT_FEEDBACK_POSITIVE = 0;
|
||||
BOT_FEEDBACK_NEGATIVE_GENERIC = 1;
|
||||
BOT_FEEDBACK_NEGATIVE_HELPFUL = 2;
|
||||
BOT_FEEDBACK_NEGATIVE_INTERESTING = 3;
|
||||
BOT_FEEDBACK_NEGATIVE_ACCURATE = 4;
|
||||
BOT_FEEDBACK_NEGATIVE_SAFE = 5;
|
||||
BOT_FEEDBACK_NEGATIVE_OTHER = 6;
|
||||
}
|
||||
optional MessageKey messageKey = 1;
|
||||
optional BotFeedbackKind kind = 2;
|
||||
optional string text = 3;
|
||||
}
|
||||
|
||||
message AudioMessage {
|
||||
optional string url = 1;
|
||||
optional string mimetype = 2;
|
||||
@@ -729,12 +706,20 @@ message GroupMention {
|
||||
}
|
||||
|
||||
message DisappearingMode {
|
||||
enum Trigger {
|
||||
UNKNOWN = 0;
|
||||
CHAT_SETTING = 1;
|
||||
ACCOUNT_SETTING = 2;
|
||||
BULK_CHANGE = 3;
|
||||
}
|
||||
enum Initiator {
|
||||
CHANGED_IN_CHAT = 0;
|
||||
INITIATED_BY_ME = 1;
|
||||
INITIATED_BY_OTHER = 2;
|
||||
}
|
||||
optional Initiator initiator = 1;
|
||||
optional Trigger trigger = 2;
|
||||
optional string initiatorDeviceJid = 3;
|
||||
}
|
||||
|
||||
message DeviceListMetadata {
|
||||
@@ -752,6 +737,12 @@ message ContextInfo {
|
||||
optional string utmCampaign = 2;
|
||||
}
|
||||
|
||||
message ForwardedNewsletterMessageInfo {
|
||||
optional string newsletterJid = 1;
|
||||
optional int32 serverMessageId = 2;
|
||||
optional string newsletterName = 3;
|
||||
}
|
||||
|
||||
message ExternalAdReplyInfo {
|
||||
enum MediaType {
|
||||
NONE = 0;
|
||||
@@ -771,6 +762,11 @@ message ContextInfo {
|
||||
optional bool renderLargerThumbnail = 11;
|
||||
optional bool showAdAttribution = 12;
|
||||
optional string ctwaClid = 13;
|
||||
optional string ref = 14;
|
||||
}
|
||||
|
||||
message BusinessMessageForwardInfo {
|
||||
optional string businessOwnerJid = 1;
|
||||
}
|
||||
|
||||
message AdReplyInfo {
|
||||
@@ -813,6 +809,27 @@ message ContextInfo {
|
||||
optional bool isSampled = 39;
|
||||
repeated GroupMention groupMentions = 40;
|
||||
optional UTMInfo utm = 41;
|
||||
optional ForwardedNewsletterMessageInfo forwardedNewsletterMessageInfo = 43;
|
||||
optional BusinessMessageForwardInfo businessMessageForwardInfo = 44;
|
||||
optional string smbClientCampaignId = 45;
|
||||
}
|
||||
|
||||
message BotPluginMetadata {
|
||||
optional bool isPlaceholder = 1;
|
||||
}
|
||||
|
||||
message BotMetadata {
|
||||
optional BotAvatarMetadata avatarMetadata = 1;
|
||||
optional string personaId = 2;
|
||||
optional BotPluginMetadata pluginMetadata = 3;
|
||||
}
|
||||
|
||||
message BotAvatarMetadata {
|
||||
optional uint32 sentiment = 1;
|
||||
optional string behaviorGraph = 2;
|
||||
optional uint32 action = 3;
|
||||
optional uint32 intensity = 4;
|
||||
optional uint32 wordCount = 5;
|
||||
}
|
||||
|
||||
message ActionLink {
|
||||
@@ -935,10 +952,18 @@ message Message {
|
||||
optional PollCreationMessage pollCreationMessageV2 = 60;
|
||||
optional ScheduledCallCreationMessage scheduledCallCreationMessage = 61;
|
||||
optional FutureProofMessage groupMentionedMessage = 62;
|
||||
optional PinMessage pinMessage = 63;
|
||||
optional PinInChatMessage pinInChatMessage = 63;
|
||||
optional PollCreationMessage pollCreationMessageV3 = 64;
|
||||
optional ScheduledCallEditMessage scheduledCallEditMessage = 65;
|
||||
optional VideoMessage ptvMessage = 66;
|
||||
optional FutureProofMessage botInvokeMessage = 67;
|
||||
optional EncCommentMessage encCommentMessage = 68;
|
||||
}
|
||||
|
||||
message MessageSecretMessage {
|
||||
optional sfixed32 version = 1;
|
||||
optional bytes encIv = 2;
|
||||
optional bytes encPayload = 3;
|
||||
}
|
||||
|
||||
message MessageContextInfo {
|
||||
@@ -946,6 +971,9 @@ message MessageContextInfo {
|
||||
optional int32 deviceListMetadataVersion = 2;
|
||||
optional bytes messageSecret = 3;
|
||||
optional bytes paddingBytes = 4;
|
||||
optional uint32 messageAddOnDurationInSecs = 5;
|
||||
optional bytes botMessageSecret = 6;
|
||||
optional BotMetadata botMetadata = 7;
|
||||
}
|
||||
|
||||
message VideoMessage {
|
||||
@@ -1048,6 +1076,7 @@ message StickerMessage {
|
||||
optional ContextInfo contextInfo = 17;
|
||||
optional int64 stickerSentTs = 18;
|
||||
optional bool isAvatar = 19;
|
||||
optional bool isAiSticker = 20;
|
||||
}
|
||||
|
||||
message SenderKeyDistributionMessage {
|
||||
@@ -1117,6 +1146,8 @@ message ProtocolMessage {
|
||||
MESSAGE_EDIT = 14;
|
||||
PEER_DATA_OPERATION_REQUEST_MESSAGE = 16;
|
||||
PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE = 17;
|
||||
REQUEST_WELCOME_MESSAGE = 18;
|
||||
BOT_FEEDBACK_MESSAGE = 19;
|
||||
}
|
||||
optional MessageKey key = 1;
|
||||
optional Type type = 2;
|
||||
@@ -1132,6 +1163,7 @@ message ProtocolMessage {
|
||||
optional int64 timestampMs = 15;
|
||||
optional PeerDataOperationRequestMessage peerDataOperationRequestMessage = 16;
|
||||
optional PeerDataOperationRequestResponseMessage peerDataOperationRequestResponseMessage = 17;
|
||||
optional BotFeedbackMessage botFeedbackMessage = 18;
|
||||
}
|
||||
|
||||
message ProductMessage {
|
||||
@@ -1194,14 +1226,14 @@ message PollCreationMessage {
|
||||
optional ContextInfo contextInfo = 5;
|
||||
}
|
||||
|
||||
message PinMessage {
|
||||
enum PinMessageType {
|
||||
UNKNOWN_PIN_MESSAGE_TYPE = 0;
|
||||
message PinInChatMessage {
|
||||
enum Type {
|
||||
UNKNOWN_TYPE = 0;
|
||||
PIN_FOR_ALL = 1;
|
||||
UNPIN_FOR_ALL = 2;
|
||||
}
|
||||
optional MessageKey key = 1;
|
||||
optional PinMessageType pinMessageType = 2;
|
||||
optional Type type = 2;
|
||||
optional int64 senderTimestampMs = 3;
|
||||
}
|
||||
|
||||
@@ -1210,10 +1242,25 @@ enum PeerDataOperationRequestType {
|
||||
SEND_RECENT_STICKER_BOOTSTRAP = 1;
|
||||
GENERATE_LINK_PREVIEW = 2;
|
||||
HISTORY_SYNC_ON_DEMAND = 3;
|
||||
PLACEHOLDER_MESSAGE_RESEND = 4;
|
||||
}
|
||||
message PeerDataOperationRequestResponseMessage {
|
||||
message PeerDataOperationResult {
|
||||
message PlaceholderMessageResendResponse {
|
||||
optional bytes webMessageInfoBytes = 1;
|
||||
}
|
||||
|
||||
message LinkPreviewResponse {
|
||||
message LinkPreviewHighQualityThumbnail {
|
||||
optional string directPath = 1;
|
||||
optional string thumbHash = 2;
|
||||
optional string encThumbHash = 3;
|
||||
optional bytes mediaKey = 4;
|
||||
optional int64 mediaKeyTimestampMs = 5;
|
||||
optional int32 thumbWidth = 6;
|
||||
optional int32 thumbHeight = 7;
|
||||
}
|
||||
|
||||
optional string url = 1;
|
||||
optional string title = 2;
|
||||
optional string description = 3;
|
||||
@@ -1221,11 +1268,13 @@ message PeerDataOperationRequestResponseMessage {
|
||||
optional string canonicalUrl = 5;
|
||||
optional string matchText = 6;
|
||||
optional string previewType = 7;
|
||||
optional LinkPreviewHighQualityThumbnail hqThumbnail = 8;
|
||||
}
|
||||
|
||||
optional MediaRetryNotification.ResultType mediaUploadResult = 1;
|
||||
optional StickerMessage stickerMessage = 2;
|
||||
optional LinkPreviewResponse linkPreviewResponse = 3;
|
||||
optional PlaceholderMessageResendResponse placeholderMessageResendResponse = 4;
|
||||
}
|
||||
|
||||
optional PeerDataOperationRequestType peerDataOperationRequestType = 1;
|
||||
@@ -1233,6 +1282,82 @@ message PeerDataOperationRequestResponseMessage {
|
||||
repeated PeerDataOperationResult peerDataOperationResult = 3;
|
||||
}
|
||||
|
||||
message PeerDataOperationRequestMessage {
|
||||
message RequestUrlPreview {
|
||||
optional string url = 1;
|
||||
optional bool includeHqThumbnail = 2;
|
||||
}
|
||||
|
||||
message RequestStickerReupload {
|
||||
optional string fileSha256 = 1;
|
||||
}
|
||||
|
||||
message PlaceholderMessageResendRequest {
|
||||
optional MessageKey messageKey = 1;
|
||||
}
|
||||
|
||||
message HistorySyncOnDemandRequest {
|
||||
optional string chatJid = 1;
|
||||
optional string oldestMsgId = 2;
|
||||
optional bool oldestMsgFromMe = 3;
|
||||
optional int32 onDemandMsgCount = 4;
|
||||
optional int64 oldestMsgTimestampMs = 5;
|
||||
}
|
||||
|
||||
optional PeerDataOperationRequestType peerDataOperationRequestType = 1;
|
||||
repeated RequestStickerReupload requestStickerReupload = 2;
|
||||
repeated RequestUrlPreview requestUrlPreview = 3;
|
||||
optional HistorySyncOnDemandRequest historySyncOnDemandRequest = 4;
|
||||
repeated PlaceholderMessageResendRequest placeholderMessageResendRequest = 5;
|
||||
}
|
||||
|
||||
message PaymentInviteMessage {
|
||||
enum ServiceType {
|
||||
UNKNOWN = 0;
|
||||
FBPAY = 1;
|
||||
NOVI = 2;
|
||||
UPI = 3;
|
||||
}
|
||||
optional ServiceType serviceType = 1;
|
||||
optional int64 expiryTimestamp = 2;
|
||||
}
|
||||
|
||||
message OrderMessage {
|
||||
enum OrderSurface {
|
||||
CATALOG = 1;
|
||||
}
|
||||
enum OrderStatus {
|
||||
INQUIRY = 1;
|
||||
}
|
||||
optional string orderId = 1;
|
||||
optional bytes thumbnail = 2;
|
||||
optional int32 itemCount = 3;
|
||||
optional OrderStatus status = 4;
|
||||
optional OrderSurface surface = 5;
|
||||
optional string message = 6;
|
||||
optional string orderTitle = 7;
|
||||
optional string sellerJid = 8;
|
||||
optional string token = 9;
|
||||
optional int64 totalAmount1000 = 10;
|
||||
optional string totalCurrencyCode = 11;
|
||||
optional ContextInfo contextInfo = 17;
|
||||
}
|
||||
|
||||
message LocationMessage {
|
||||
optional double degreesLatitude = 1;
|
||||
optional double degreesLongitude = 2;
|
||||
optional string name = 3;
|
||||
optional string address = 4;
|
||||
optional string url = 5;
|
||||
optional bool isLive = 6;
|
||||
optional uint32 accuracyInMeters = 7;
|
||||
optional float speedInMps = 8;
|
||||
optional uint32 degreesClockwiseFromMagneticNorth = 9;
|
||||
optional string comment = 11;
|
||||
optional bytes jpegThumbnail = 16;
|
||||
optional ContextInfo contextInfo = 17;
|
||||
}
|
||||
|
||||
message EphemeralSetting {
|
||||
optional sfixed32 duration = 1;
|
||||
optional sfixed64 timestamp = 2;
|
||||
@@ -1277,6 +1402,15 @@ message PastParticipant {
|
||||
optional uint64 leaveTs = 3;
|
||||
}
|
||||
|
||||
message NotificationSettings {
|
||||
optional string messageVibrate = 1;
|
||||
optional string messagePopup = 2;
|
||||
optional string messageLight = 3;
|
||||
optional bool lowPriorityNotifications = 4;
|
||||
optional bool reactionsMuted = 5;
|
||||
optional string callVibrate = 6;
|
||||
}
|
||||
|
||||
enum MediaVisibility {
|
||||
DEFAULT = 0;
|
||||
OFF = 1;
|
||||
@@ -1332,12 +1466,20 @@ message GlobalSettings {
|
||||
optional int32 disappearingModeDuration = 9;
|
||||
optional int64 disappearingModeTimestamp = 10;
|
||||
optional AvatarUserSettings avatarUserSettings = 11;
|
||||
optional int32 fontSize = 12;
|
||||
optional bool securityNotifications = 13;
|
||||
optional bool autoUnarchiveChats = 14;
|
||||
optional int32 videoQualityMode = 15;
|
||||
optional int32 photoQualityMode = 16;
|
||||
optional NotificationSettings individualNotificationSettings = 17;
|
||||
optional NotificationSettings groupNotificationSettings = 18;
|
||||
}
|
||||
|
||||
message Conversation {
|
||||
enum EndOfHistoryTransferType {
|
||||
COMPLETE_BUT_MORE_MESSAGES_REMAIN_ON_PRIMARY = 0;
|
||||
COMPLETE_AND_NO_MORE_MESSAGE_REMAIN_ON_PRIMARY = 1;
|
||||
COMPLETE_ON_DEMAND_SYNC_BUT_MORE_MSG_REMAIN_ON_PRIMARY = 2;
|
||||
}
|
||||
required string id = 1;
|
||||
repeated HistorySyncMsg messages = 2;
|
||||
@@ -1395,50 +1537,6 @@ message AutoDownloadSettings {
|
||||
optional bool downloadDocuments = 4;
|
||||
}
|
||||
|
||||
// Duplicate type omitted
|
||||
//message PollEncValue {
|
||||
// optional bytes encPayload = 1;
|
||||
// optional bytes encIv = 2;
|
||||
//}
|
||||
|
||||
message MsgRowOpaqueData {
|
||||
optional MsgOpaqueData currentMsg = 1;
|
||||
optional MsgOpaqueData quotedMsg = 2;
|
||||
}
|
||||
|
||||
message MsgOpaqueData {
|
||||
message PollOption {
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
optional string caption = 3;
|
||||
optional double lng = 5;
|
||||
optional bool isLive = 6;
|
||||
optional double lat = 7;
|
||||
optional int32 paymentAmount1000 = 8;
|
||||
optional string paymentNoteMsgBody = 9;
|
||||
optional string canonicalUrl = 10;
|
||||
optional string matchedText = 11;
|
||||
optional string title = 12;
|
||||
optional string description = 13;
|
||||
optional bytes futureproofBuffer = 14;
|
||||
optional string clientUrl = 15;
|
||||
optional string loc = 16;
|
||||
optional string pollName = 17;
|
||||
repeated PollOption pollOptions = 18;
|
||||
optional uint32 pollSelectableOptionsCount = 20;
|
||||
optional bytes messageSecret = 21;
|
||||
optional string originalSelfAuthor = 51;
|
||||
optional int64 senderTimestampMs = 22;
|
||||
optional string pollUpdateParentKey = 23;
|
||||
optional PollEncValue encPollVote = 24;
|
||||
optional bool isSentCagPollCreation = 28;
|
||||
optional string encReactionTargetMessageKey = 25;
|
||||
optional bytes encReactionEncPayload = 26;
|
||||
optional bytes encReactionEncIv = 27;
|
||||
}
|
||||
|
||||
message ServerErrorReceipt {
|
||||
optional string stanzaId = 1;
|
||||
}
|
||||
@@ -1570,6 +1668,10 @@ message SyncActionValue {
|
||||
optional ChatAssignmentAction chatAssignment = 35;
|
||||
optional ChatAssignmentOpenedStatusAction chatAssignmentOpenedStatus = 36;
|
||||
optional PnForLidChatAction pnForLidChatAction = 37;
|
||||
optional MarketingMessageAction marketingMessageAction = 38;
|
||||
optional MarketingMessageBroadcastAction marketingMessageBroadcastAction = 39;
|
||||
optional ExternalWebBetaAction externalWebBetaAction = 40;
|
||||
optional PrivacySettingRelayAllCalls privacySettingRelayAllCalls = 41;
|
||||
}
|
||||
|
||||
message UserStatusMuteAction {
|
||||
@@ -1642,6 +1744,10 @@ message PushNameSetting {
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
message PrivacySettingRelayAllCalls {
|
||||
optional bool isEnabled = 1;
|
||||
}
|
||||
|
||||
message PrimaryVersionAction {
|
||||
optional string version = 1;
|
||||
}
|
||||
@@ -1668,6 +1774,23 @@ message MuteAction {
|
||||
optional bool autoMuted = 3;
|
||||
}
|
||||
|
||||
message MarketingMessageBroadcastAction {
|
||||
optional int32 repliedCount = 1;
|
||||
}
|
||||
|
||||
message MarketingMessageAction {
|
||||
enum MarketingMessagePrototypeType {
|
||||
PERSONALIZED = 0;
|
||||
}
|
||||
optional string name = 1;
|
||||
optional string message = 2;
|
||||
optional MarketingMessagePrototypeType type = 3;
|
||||
optional int64 createdAt = 4;
|
||||
optional int64 lastSentAt = 5;
|
||||
optional bool isDeleted = 6;
|
||||
optional string mediaId = 7;
|
||||
}
|
||||
|
||||
message MarkChatAsReadAction {
|
||||
optional bool read = 1;
|
||||
optional SyncActionMessageRange messageRange = 2;
|
||||
@@ -1692,6 +1815,10 @@ message KeyExpiration {
|
||||
optional int32 expiredKeyEpoch = 1;
|
||||
}
|
||||
|
||||
message ExternalWebBetaAction {
|
||||
optional bool isOptIn = 1;
|
||||
}
|
||||
|
||||
message DeleteMessageForMeAction {
|
||||
optional bool deleteMedia = 1;
|
||||
optional int64 messageTimestamp = 2;
|
||||
@@ -1903,6 +2030,14 @@ message ClientPayload {
|
||||
ARDEVICE = 30;
|
||||
VRDEVICE = 31;
|
||||
BLUE_WEB = 32;
|
||||
IPAD = 33;
|
||||
}
|
||||
enum DeviceType {
|
||||
PHONE = 0;
|
||||
TABLET = 1;
|
||||
DESKTOP = 2;
|
||||
WEARABLE = 3;
|
||||
VR = 4;
|
||||
}
|
||||
message AppVersion {
|
||||
optional uint32 primary = 1;
|
||||
@@ -1925,12 +2060,21 @@ message ClientPayload {
|
||||
optional string localeLanguageIso6391 = 11;
|
||||
optional string localeCountryIso31661Alpha2 = 12;
|
||||
optional string deviceBoard = 13;
|
||||
optional string deviceExpId = 14;
|
||||
optional DeviceType deviceType = 15;
|
||||
}
|
||||
|
||||
enum Product {
|
||||
WHATSAPP = 0;
|
||||
MESSENGER = 1;
|
||||
INTEROP = 2;
|
||||
}
|
||||
message InteropData {
|
||||
optional uint64 accountId = 1;
|
||||
optional uint32 integratorId = 2;
|
||||
optional bytes token = 3;
|
||||
}
|
||||
|
||||
enum IOSAppExtension {
|
||||
SHARE_EXTENSION = 0;
|
||||
SERVICE_EXTENSION = 1;
|
||||
@@ -1983,6 +2127,7 @@ message ClientPayload {
|
||||
ERROR_RECONNECT = 3;
|
||||
NETWORK_SWITCH = 4;
|
||||
PING_RECONNECT = 5;
|
||||
UNKNOWN = 6;
|
||||
}
|
||||
optional uint64 username = 1;
|
||||
optional bool passive = 3;
|
||||
@@ -2010,6 +2155,7 @@ message ClientPayload {
|
||||
optional bytes paddingBytes = 34;
|
||||
optional int32 yearClass = 36;
|
||||
optional int32 memClass = 37;
|
||||
optional InteropData interopData = 38;
|
||||
}
|
||||
|
||||
message WebNotificationsInfo {
|
||||
@@ -2183,6 +2329,30 @@ message WebMessageInfo {
|
||||
CAG_INVITE_AUTO_ADD = 159;
|
||||
BIZ_CHAT_ASSIGNMENT_UNASSIGN = 160;
|
||||
CAG_INVITE_AUTO_JOINED = 161;
|
||||
SCHEDULED_CALL_START_MESSAGE = 162;
|
||||
COMMUNITY_INVITE_RICH = 163;
|
||||
COMMUNITY_INVITE_AUTO_ADD_RICH = 164;
|
||||
SUB_GROUP_INVITE_RICH = 165;
|
||||
SUB_GROUP_PARTICIPANT_ADD_RICH = 166;
|
||||
COMMUNITY_LINK_PARENT_GROUP_RICH = 167;
|
||||
COMMUNITY_PARTICIPANT_ADD_RICH = 168;
|
||||
SILENCED_UNKNOWN_CALLER_AUDIO = 169;
|
||||
SILENCED_UNKNOWN_CALLER_VIDEO = 170;
|
||||
GROUP_MEMBER_ADD_MODE = 171;
|
||||
GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD = 172;
|
||||
COMMUNITY_CHANGE_DESCRIPTION = 173;
|
||||
SENDER_INVITE = 174;
|
||||
RECEIVER_INVITE = 175;
|
||||
COMMUNITY_ALLOW_MEMBER_ADDED_GROUPS = 176;
|
||||
PINNED_MESSAGE_IN_CHAT = 177;
|
||||
PAYMENT_INVITE_SETUP_INVITER = 178;
|
||||
PAYMENT_INVITE_SETUP_INVITEE_RECEIVE_ONLY = 179;
|
||||
PAYMENT_INVITE_SETUP_INVITEE_SEND_AND_RECEIVE = 180;
|
||||
LINKED_GROUP_CALL_START = 181;
|
||||
REPORT_TO_ADMIN_ENABLED_STATUS = 182;
|
||||
EMPTY_SUBGROUP_CREATE = 183;
|
||||
SCHEDULED_CALL_CANCEL = 184;
|
||||
SUBGROUP_ADMIN_TRIGGERED_AUTO_ADD_RICH = 185;
|
||||
}
|
||||
enum Status {
|
||||
ERROR = 0;
|
||||
@@ -2241,6 +2411,7 @@ message WebMessageInfo {
|
||||
optional KeepInChat keepInChat = 50;
|
||||
optional string originalSelfAuthorUserJidString = 51;
|
||||
optional uint64 revokeMessageTimestamp = 52;
|
||||
optional PinInChat pinInChat = 54;
|
||||
}
|
||||
|
||||
message WebFeatures {
|
||||
@@ -2331,6 +2502,19 @@ message PollAdditionalMetadata {
|
||||
optional bool pollInvalidated = 1;
|
||||
}
|
||||
|
||||
message PinInChat {
|
||||
enum Type {
|
||||
UNKNOWN_TYPE = 0;
|
||||
PIN_FOR_ALL = 1;
|
||||
UNPIN_FOR_ALL = 2;
|
||||
}
|
||||
optional Type type = 1;
|
||||
optional MessageKey key = 2;
|
||||
optional int64 senderTimestampMs = 3;
|
||||
optional int64 serverTimestampMs = 4;
|
||||
optional MessageAddOnContextInfo messageAddOnContextInfo = 5;
|
||||
}
|
||||
|
||||
message PhotoChange {
|
||||
optional bytes oldPhoto = 1;
|
||||
optional bytes newPhoto = 2;
|
||||
@@ -2412,6 +2596,10 @@ message NotificationMessageInfo {
|
||||
optional string participant = 4;
|
||||
}
|
||||
|
||||
message MessageAddOnContextInfo {
|
||||
optional uint32 messageAddOnDurationInSecs = 1;
|
||||
}
|
||||
|
||||
message MediaData {
|
||||
optional string localPath = 1;
|
||||
}
|
||||
|
||||
61
vendor/go.mau.fi/whatsmeow/client.go
vendored
61
vendor/go.mau.fi/whatsmeow/client.go
vendored
@@ -9,7 +9,6 @@ package whatsmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -29,6 +28,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// EventHandler is a function that can handle events from WhatsApp.
|
||||
@@ -65,6 +65,10 @@ type Client struct {
|
||||
// even when re-syncing the whole state.
|
||||
EmitAppStateEventsOnFullSync bool
|
||||
|
||||
AutomaticMessageRerequestFromPhone bool
|
||||
pendingPhoneRerequests map[types.MessageID]context.CancelFunc
|
||||
pendingPhoneRerequestsLock sync.RWMutex
|
||||
|
||||
appStateProc *appstate.Processor
|
||||
appStateSyncLock sync.Mutex
|
||||
|
||||
@@ -130,6 +134,8 @@ type Client struct {
|
||||
// Should SubscribePresence return an error if no privacy token is stored for the user?
|
||||
ErrorOnSubscribePresenceWithoutToken bool
|
||||
|
||||
phoneLinkingCache *phoneLinkingCache
|
||||
|
||||
uniqueID string
|
||||
idCounter uint32
|
||||
|
||||
@@ -161,8 +167,7 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
if log == nil {
|
||||
log = waLog.Noop
|
||||
}
|
||||
randomBytes := make([]byte, 2)
|
||||
_, _ = rand.Read(randomBytes)
|
||||
uniqueIDPrefix := randbytes.Make(2)
|
||||
cli := &Client{
|
||||
http: &http.Client{
|
||||
Transport: (http.DefaultTransport.(*http.Transport)).Clone(),
|
||||
@@ -172,7 +177,7 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
Log: log,
|
||||
recvLog: log.Sub("Recv"),
|
||||
sendLog: log.Sub("Send"),
|
||||
uniqueID: fmt.Sprintf("%d.%d-", randomBytes[0], randomBytes[1]),
|
||||
uniqueID: fmt.Sprintf("%d.%d-", uniqueIDPrefix[0], uniqueIDPrefix[1]),
|
||||
responseWaiters: make(map[string]chan<- *waBinary.Node),
|
||||
eventHandlers: make([]wrappedEventHandler, 0, 1),
|
||||
messageRetries: make(map[string]int),
|
||||
@@ -190,6 +195,8 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
GetMessageForRetry: func(requester, to types.JID, id types.MessageID) *waProto.Message { return nil },
|
||||
appStateKeyRequests: make(map[string]time.Time),
|
||||
|
||||
pendingPhoneRerequests: make(map[types.MessageID]context.CancelFunc),
|
||||
|
||||
EnableAutoReconnect: true,
|
||||
AutoTrustIdentity: true,
|
||||
DontSendSelfBroadcast: true,
|
||||
@@ -547,17 +554,46 @@ func (cli *Client) handleFrame(data []byte) {
|
||||
cli.handlerQueue <- node
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
} else if node.Tag != "ack" {
|
||||
cli.Log.Debugf("Didn't handle WhatsApp node %s", node.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
func stopAndDrainTimer(timer *time.Timer) {
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handlerQueueLoop(ctx context.Context) {
|
||||
timer := time.NewTimer(5 * time.Minute)
|
||||
stopAndDrainTimer(timer)
|
||||
cli.Log.Debugf("Starting handler queue loop")
|
||||
for {
|
||||
select {
|
||||
case node := <-cli.handlerQueue:
|
||||
cli.nodeHandlers[node.Tag](node)
|
||||
doneChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
start := time.Now()
|
||||
cli.nodeHandlers[node.Tag](node)
|
||||
duration := time.Since(start)
|
||||
doneChan <- struct{}{}
|
||||
if duration > 5*time.Second {
|
||||
cli.Log.Warnf("Node handling took %s for %s", duration, node.XMLString())
|
||||
}
|
||||
}()
|
||||
timer.Reset(5 * time.Minute)
|
||||
select {
|
||||
case <-doneChan:
|
||||
stopAndDrainTimer(timer)
|
||||
case <-timer.C:
|
||||
cli.Log.Warnf("Node handling is taking long for %s - continuing in background", node.XMLString())
|
||||
}
|
||||
case <-ctx.Done():
|
||||
cli.Log.Debugf("Closing handler queue loop")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -609,6 +645,13 @@ func (cli *Client) dispatchEvent(evt interface{}) {
|
||||
// yourNormalEventHandler(evt)
|
||||
// }
|
||||
func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessageInfo) (*events.Message, error) {
|
||||
var err error
|
||||
if chatJID.IsEmpty() {
|
||||
chatJID, err = types.ParseJID(webMsg.GetKey().GetRemoteJid())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no chat JID provided and failed to parse remote JID: %w", err)
|
||||
}
|
||||
}
|
||||
info := types.MessageInfo{
|
||||
MessageSource: types.MessageSource{
|
||||
Chat: chatJID,
|
||||
@@ -619,7 +662,6 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessage
|
||||
PushName: webMsg.GetPushName(),
|
||||
Timestamp: time.Unix(int64(webMsg.GetMessageTimestamp()), 0),
|
||||
}
|
||||
var err error
|
||||
if info.IsFromMe {
|
||||
info.Sender = cli.getOwnID().ToNonAD()
|
||||
if info.Sender.IsEmpty() {
|
||||
@@ -638,8 +680,9 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessage
|
||||
return nil, fmt.Errorf("failed to parse sender of message %s: %v", info.ID, err)
|
||||
}
|
||||
evt := &events.Message{
|
||||
RawMessage: webMsg.GetMessage(),
|
||||
Info: info,
|
||||
RawMessage: webMsg.GetMessage(),
|
||||
SourceWebMsg: webMsg,
|
||||
Info: info,
|
||||
}
|
||||
evt.UnwrapRaw()
|
||||
return evt, nil
|
||||
|
||||
21
vendor/go.mau.fi/whatsmeow/connectionevents.go
vendored
21
vendor/go.mau.fi/whatsmeow/connectionevents.go
vendored
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
@@ -79,9 +80,17 @@ func (cli *Client) handleIB(node *waBinary.Node) {
|
||||
func (cli *Client) handleConnectFailure(node *waBinary.Node) {
|
||||
ag := node.AttrGetter()
|
||||
reason := events.ConnectFailureReason(ag.Int("reason"))
|
||||
// Let the auto-reconnect happen for 503s, for all other failures block it
|
||||
if reason != events.ConnectFailureServiceUnavailable {
|
||||
message := ag.OptionalString("message")
|
||||
willAutoReconnect := true
|
||||
switch {
|
||||
default:
|
||||
// By default, expect a disconnect (i.e. prevent auto-reconnect)
|
||||
cli.expectDisconnect()
|
||||
willAutoReconnect = false
|
||||
case reason == events.ConnectFailureServiceUnavailable:
|
||||
// Auto-reconnect for 503s
|
||||
case reason == 500 && message == "biz vname fetch error":
|
||||
// These happen for business accounts randomly, also auto-reconnect
|
||||
}
|
||||
if reason.IsLoggedOut() {
|
||||
cli.Log.Infof("Got %s connect failure, sending LoggedOut event and deleting session", reason)
|
||||
@@ -97,13 +106,13 @@ func (cli *Client) handleConnectFailure(node *waBinary.Node) {
|
||||
Expire: time.Duration(ag.Int("expire")) * time.Second,
|
||||
})
|
||||
} else if reason == events.ConnectFailureClientOutdated {
|
||||
cli.Log.Errorf("Client outdated (405) connect failure")
|
||||
cli.Log.Errorf("Client outdated (405) connect failure (client version: %s)", store.GetWAVersion().String())
|
||||
go cli.dispatchEvent(&events.ClientOutdated{})
|
||||
} else if reason == events.ConnectFailureServiceUnavailable {
|
||||
cli.Log.Warnf("Got 503 connect failure, assuming automatic reconnect will handle it")
|
||||
} else if willAutoReconnect {
|
||||
cli.Log.Warnf("Got %d/%s connect failure, assuming automatic reconnect will handle it", int(reason), message)
|
||||
} else {
|
||||
cli.Log.Warnf("Unknown connect failure: %s", node.XMLString())
|
||||
go cli.dispatchEvent(&events.ConnectFailure{Reason: reason, Raw: node})
|
||||
go cli.dispatchEvent(&events.ConnectFailure{Reason: reason, Message: message, Raw: node})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
vendor/go.mau.fi/whatsmeow/download.go
vendored
25
vendor/go.mau.fi/whatsmeow/download.go
vendored
@@ -12,8 +12,10 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
@@ -233,7 +235,7 @@ func (cli *Client) DownloadMediaWithPath(directPath string, encFileHash, fileHas
|
||||
func (cli *Client) downloadAndDecrypt(url string, mediaKey []byte, appInfo MediaType, fileLength int, fileEncSha256, fileSha256 []byte) (data []byte, err error) {
|
||||
iv, cipherKey, macKey, _ := getMediaKeys(mediaKey, appInfo)
|
||||
var ciphertext, mac []byte
|
||||
if ciphertext, mac, err = cli.downloadEncryptedMedia(url, fileEncSha256); err != nil {
|
||||
if ciphertext, mac, err = cli.downloadEncryptedMediaWithRetries(url, fileEncSha256); err != nil {
|
||||
|
||||
} else if err = validateMedia(iv, ciphertext, macKey, mac); err != nil {
|
||||
|
||||
@@ -252,6 +254,23 @@ func getMediaKeys(mediaKey []byte, appInfo MediaType) (iv, cipherKey, macKey, re
|
||||
return mediaKeyExpanded[:16], mediaKeyExpanded[16:48], mediaKeyExpanded[48:80], mediaKeyExpanded[80:]
|
||||
}
|
||||
|
||||
func (cli *Client) downloadEncryptedMediaWithRetries(url string, checksum []byte) (file, mac []byte, err error) {
|
||||
for retryNum := 0; retryNum < 5; retryNum++ {
|
||||
file, mac, err = cli.downloadEncryptedMedia(url, checksum)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
netErr, ok := err.(net.Error)
|
||||
if !ok {
|
||||
// Not a network error, don't retry
|
||||
return
|
||||
}
|
||||
cli.Log.Warnf("Failed to download media due to network error: %w, retrying...", netErr)
|
||||
time.Sleep(time.Duration(retryNum+1) * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) downloadEncryptedMedia(url string, checksum []byte) (file, mac []byte, err error) {
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodGet, url, nil)
|
||||
@@ -268,7 +287,9 @@ func (cli *Client) downloadEncryptedMedia(url string, checksum []byte) (file, ma
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
err = ErrMediaDownloadFailedWith403
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
err = ErrMediaDownloadFailedWith404
|
||||
} else if resp.StatusCode == http.StatusGone {
|
||||
err = ErrMediaDownloadFailedWith410
|
||||
|
||||
3
vendor/go.mau.fi/whatsmeow/errors.go
vendored
3
vendor/go.mau.fi/whatsmeow/errors.go
vendored
@@ -29,6 +29,8 @@ var (
|
||||
ErrNoPushName = errors.New("can't send presence without PushName set")
|
||||
|
||||
ErrNoPrivacyToken = errors.New("no privacy token stored")
|
||||
|
||||
ErrAppStateUpdate = errors.New("server returned error updating app state")
|
||||
)
|
||||
|
||||
// Errors that happen while confirming device pairing
|
||||
@@ -107,6 +109,7 @@ var (
|
||||
|
||||
// Some errors that Client.Download can return
|
||||
var (
|
||||
ErrMediaDownloadFailedWith403 = errors.New("download failed with status code 403")
|
||||
ErrMediaDownloadFailedWith404 = errors.New("download failed with status code 404")
|
||||
ErrMediaDownloadFailedWith410 = errors.New("download failed with status code 410")
|
||||
ErrNoURLPresent = errors.New("no url present")
|
||||
|
||||
10
vendor/go.mau.fi/whatsmeow/group.go
vendored
10
vendor/go.mau.fi/whatsmeow/group.go
vendored
@@ -57,7 +57,7 @@ func (cli *Client) CreateGroup(req ReqCreateGroup) (*types.GroupInfo, error) {
|
||||
}
|
||||
}
|
||||
if req.CreateKey == "" {
|
||||
req.CreateKey = GenerateMessageID()
|
||||
req.CreateKey = cli.GenerateMessageID()
|
||||
}
|
||||
if req.IsParent {
|
||||
if req.DefaultMembershipApprovalMode == "" {
|
||||
@@ -99,7 +99,7 @@ func (cli *Client) CreateGroup(req ReqCreateGroup) (*types.GroupInfo, error) {
|
||||
func (cli *Client) UnlinkGroup(parent, child types.JID) error {
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, parent, waBinary.Node{
|
||||
Tag: "unlink",
|
||||
Attrs: waBinary.Attrs{"unlink_type": types.GroupLinkChangeTypeSub},
|
||||
Attrs: waBinary.Attrs{"unlink_type": string(types.GroupLinkChangeTypeSub)},
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "group",
|
||||
Attrs: waBinary.Attrs{"jid": child},
|
||||
@@ -116,7 +116,7 @@ func (cli *Client) LinkGroup(parent, child types.JID) error {
|
||||
Tag: "links",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "link",
|
||||
Attrs: waBinary.Attrs{"link_type": types.GroupLinkChangeTypeSub},
|
||||
Attrs: waBinary.Attrs{"link_type": string(types.GroupLinkChangeTypeSub)},
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "group",
|
||||
Attrs: waBinary.Attrs{"jid": child},
|
||||
@@ -221,7 +221,7 @@ func (cli *Client) SetGroupName(jid types.JID, name string) error {
|
||||
//
|
||||
// The previousID and newID fields are optional. If the previous ID is not specified, this will
|
||||
// automatically fetch the current group info to find the previous topic ID. If the new ID is not
|
||||
// specified, one will be generated with GenerateMessageID().
|
||||
// specified, one will be generated with Client.GenerateMessageID().
|
||||
func (cli *Client) SetGroupTopic(jid types.JID, previousID, newID, topic string) error {
|
||||
if previousID == "" {
|
||||
oldInfo, err := cli.GetGroupInfo(jid)
|
||||
@@ -231,7 +231,7 @@ func (cli *Client) SetGroupTopic(jid types.JID, previousID, newID, topic string)
|
||||
previousID = oldInfo.TopicID
|
||||
}
|
||||
if newID == "" {
|
||||
newID = GenerateMessageID()
|
||||
newID = cli.GenerateMessageID()
|
||||
}
|
||||
attrs := waBinary.Attrs{
|
||||
"id": newID,
|
||||
|
||||
5
vendor/go.mau.fi/whatsmeow/internals.go
vendored
5
vendor/go.mau.fi/whatsmeow/internals.go
vendored
@@ -10,6 +10,7 @@ import (
|
||||
"context"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
type DangerousInternalClient struct {
|
||||
@@ -62,6 +63,6 @@ func (int *DangerousInternalClient) RequestAppStateKeys(ctx context.Context, key
|
||||
int.c.requestAppStateKeys(ctx, keyIDs)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendRetryReceipt(node *waBinary.Node, forceIncludeIdentity bool) {
|
||||
int.c.sendRetryReceipt(node, forceIncludeIdentity)
|
||||
func (int *DangerousInternalClient) SendRetryReceipt(node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool) {
|
||||
int.c.sendRetryReceipt(node, info, forceIncludeIdentity)
|
||||
}
|
||||
|
||||
10
vendor/go.mau.fi/whatsmeow/keepalive.go
vendored
10
vendor/go.mau.fi/whatsmeow/keepalive.go
vendored
@@ -23,10 +23,13 @@ var (
|
||||
KeepAliveIntervalMin = 20 * time.Second
|
||||
// KeepAliveIntervalMax specifies the maximum interval for websocket keepalive pings.
|
||||
KeepAliveIntervalMax = 30 * time.Second
|
||||
|
||||
// KeepAliveMaxFailTime specifies the maximum time to wait before forcing a reconnect if keepalives fail repeatedly.
|
||||
KeepAliveMaxFailTime = 3 * time.Minute
|
||||
)
|
||||
|
||||
func (cli *Client) keepAliveLoop(ctx context.Context) {
|
||||
var lastSuccess time.Time
|
||||
lastSuccess := time.Now()
|
||||
var errorCount int
|
||||
for {
|
||||
interval := rand.Int63n(KeepAliveIntervalMax.Milliseconds()-KeepAliveIntervalMin.Milliseconds()) + KeepAliveIntervalMin.Milliseconds()
|
||||
@@ -41,6 +44,11 @@ func (cli *Client) keepAliveLoop(ctx context.Context) {
|
||||
ErrorCount: errorCount,
|
||||
LastSuccess: lastSuccess,
|
||||
})
|
||||
if cli.EnableAutoReconnect && time.Since(lastSuccess) > KeepAliveMaxFailTime {
|
||||
cli.Log.Debugf("Forcing reconnect due to keepalive failure")
|
||||
cli.Disconnect()
|
||||
go cli.autoReconnect()
|
||||
}
|
||||
} else {
|
||||
if errorCount > 0 {
|
||||
errorCount = 0
|
||||
|
||||
8
vendor/go.mau.fi/whatsmeow/mediaretry.go
vendored
8
vendor/go.mau.fi/whatsmeow/mediaretry.go
vendored
@@ -7,7 +7,6 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
func getMediaRetryKey(mediaKey []byte) (cipherKey []byte) {
|
||||
@@ -34,11 +34,7 @@ func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphe
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
iv = make([]byte, 12)
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
iv = randbytes.Make(12)
|
||||
ciphertext, err = gcmutil.Encrypt(getMediaRetryKey(mediaKey), iv, plaintext, []byte(messageID))
|
||||
return
|
||||
}
|
||||
|
||||
59
vendor/go.mau.fi/whatsmeow/message.go
vendored
59
vendor/go.mau.fi/whatsmeow/message.go
vendored
@@ -9,7 +9,6 @@ package whatsmeow
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -31,6 +30,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
var pbSerializer = store.SignalProtobufSerializer
|
||||
@@ -101,6 +101,7 @@ func (cli *Client) parseMessageInfo(node *waBinary.Node) (*types.MessageInfo, er
|
||||
info.Timestamp = ag.UnixTime("t")
|
||||
info.PushName = ag.OptionalString("notify")
|
||||
info.Category = ag.OptionalString("category")
|
||||
info.Type = ag.OptionalString("type")
|
||||
if !ag.OK() {
|
||||
return nil, ag.Error()
|
||||
}
|
||||
@@ -125,7 +126,7 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
|
||||
go cli.sendAck(node)
|
||||
if len(node.GetChildrenByTag("unavailable")) > 0 && len(node.GetChildrenByTag("enc")) == 0 {
|
||||
cli.Log.Warnf("Unavailable message %s from %s", info.ID, info.SourceString())
|
||||
go cli.sendRetryReceipt(node, true)
|
||||
go cli.sendRetryReceipt(node, info, true)
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{Info: *info, IsUnavailable: true})
|
||||
return
|
||||
}
|
||||
@@ -137,7 +138,8 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
|
||||
if child.Tag != "enc" {
|
||||
continue
|
||||
}
|
||||
encType, ok := child.Attrs["type"].(string)
|
||||
ag := child.AttrGetter()
|
||||
encType, ok := ag.GetString("type", false)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -155,8 +157,13 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Error decrypting message from %s: %v", info.SourceString(), err)
|
||||
isUnavailable := encType == "skmsg" && !containsDirectMsg && errors.Is(err, signalerror.ErrNoSenderKeyForUser)
|
||||
go cli.sendRetryReceipt(node, isUnavailable)
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{Info: *info, IsUnavailable: isUnavailable})
|
||||
go cli.sendRetryReceipt(node, info, isUnavailable)
|
||||
decryptFailMode, _ := child.Attrs["decrypt-fail"].(string)
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{
|
||||
Info: *info,
|
||||
IsUnavailable: isUnavailable,
|
||||
DecryptFailMode: events.DecryptFailMode(decryptFailMode),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -166,8 +173,12 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
|
||||
cli.Log.Warnf("Error unmarshaling decrypted message from %s: %v", info.SourceString(), err)
|
||||
continue
|
||||
}
|
||||
retryCount := ag.OptionalInt("count")
|
||||
if retryCount > 0 {
|
||||
cli.cancelDelayedRequestFromPhone(info.ID)
|
||||
}
|
||||
|
||||
cli.handleDecryptedMessage(info, &msg)
|
||||
cli.handleDecryptedMessage(info, &msg, retryCount)
|
||||
handled = true
|
||||
}
|
||||
if handled {
|
||||
@@ -246,6 +257,9 @@ func isValidPadding(plaintext []byte) bool {
|
||||
}
|
||||
|
||||
func unpadMessage(plaintext []byte) ([]byte, error) {
|
||||
if len(plaintext) == 0 {
|
||||
return nil, fmt.Errorf("plaintext is empty")
|
||||
}
|
||||
if checkPadding && !isValidPadding(plaintext) {
|
||||
return nil, fmt.Errorf("plaintext doesn't have expected padding")
|
||||
}
|
||||
@@ -253,11 +267,7 @@ func unpadMessage(plaintext []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
func padMessage(plaintext []byte) []byte {
|
||||
var pad [1]byte
|
||||
_, err := rand.Read(pad[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pad := randbytes.Make(1)
|
||||
pad[0] &= 0xf
|
||||
if pad[0] == 0 {
|
||||
pad[0] = 0xf
|
||||
@@ -357,6 +367,25 @@ func (cli *Client) handleAppStateSyncKeyShare(keys *waProto.AppStateSyncKeyShare
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handlePlaceholderResendResponse(msg *waProto.PeerDataOperationRequestResponseMessage) {
|
||||
reqID := msg.GetStanzaId()
|
||||
parts := msg.GetPeerDataOperationResult()
|
||||
cli.Log.Debugf("Handling response to placeholder resend request %s with %d items", reqID, len(parts))
|
||||
for i, part := range parts {
|
||||
var webMsg waProto.WebMessageInfo
|
||||
if resp := part.GetPlaceholderMessageResendResponse(); resp == nil {
|
||||
cli.Log.Warnf("Missing response in item #%d of response to %s", i+1, reqID)
|
||||
} else if err := proto.Unmarshal(resp.GetWebMessageInfoBytes(), &webMsg); err != nil {
|
||||
cli.Log.Warnf("Failed to unmarshal protobuf web message in item #%d of response to %s: %v", i+1, reqID, err)
|
||||
} else if msgEvt, err := cli.ParseWebMessage(types.EmptyJID, &webMsg); err != nil {
|
||||
cli.Log.Warnf("Failed to parse web message info in item #%d of response to %s: %v", i+1, reqID, err)
|
||||
} else {
|
||||
msgEvt.UnavailableRequestID = reqID
|
||||
cli.dispatchEvent(msgEvt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleProtocolMessage(info *types.MessageInfo, msg *waProto.Message) {
|
||||
protoMsg := msg.GetProtocolMessage()
|
||||
|
||||
@@ -368,6 +397,10 @@ func (cli *Client) handleProtocolMessage(info *types.MessageInfo, msg *waProto.M
|
||||
go cli.sendProtocolMessageReceipt(info.ID, "hist_sync")
|
||||
}
|
||||
|
||||
if protoMsg.GetPeerDataOperationRequestResponseMessage().GetPeerDataOperationRequestType() == waProto.PeerDataOperationRequestType_PLACEHOLDER_MESSAGE_RESEND {
|
||||
go cli.handlePlaceholderResendResponse(protoMsg.GetPeerDataOperationRequestResponseMessage())
|
||||
}
|
||||
|
||||
if protoMsg.GetAppStateSyncKeyShare() != nil && info.IsFromMe {
|
||||
go cli.handleAppStateSyncKeyShare(protoMsg.AppStateSyncKeyShare)
|
||||
}
|
||||
@@ -472,9 +505,9 @@ func (cli *Client) storeHistoricalMessageSecrets(conversations []*waProto.Conver
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.Message) {
|
||||
func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.Message, retryCount int) {
|
||||
cli.processProtocolParts(info, msg)
|
||||
evt := &events.Message{Info: *info, RawMessage: msg}
|
||||
evt := &events.Message{Info: *info, RawMessage: msg, RetryCount: retryCount}
|
||||
cli.dispatchEvent(evt.UnwrapRaw())
|
||||
}
|
||||
|
||||
|
||||
16
vendor/go.mau.fi/whatsmeow/msgsecret.go
vendored
16
vendor/go.mau.fi/whatsmeow/msgsecret.go
vendored
@@ -7,7 +7,6 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -19,6 +18,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
type MsgSecretType string
|
||||
@@ -107,11 +107,7 @@ func (cli *Client) encryptMsgSecret(chat, origSender types.JID, origMsgID types.
|
||||
}
|
||||
secretKey, additionalData := generateMsgSecretKey(useCase, ownID, origMsgID, origSender, baseEncKey)
|
||||
|
||||
iv = make([]byte, 12)
|
||||
_, err = rand.Read(iv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate iv: %w", err)
|
||||
}
|
||||
iv = randbytes.Make(12)
|
||||
ciphertext, err = gcmutil.Encrypt(secretKey, iv, plaintext, additionalData)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encrypt secret message: %w", err)
|
||||
@@ -132,7 +128,7 @@ func (cli *Client) encryptMsgSecret(chat, origSender types.JID, origMsgID types.
|
||||
// }
|
||||
func (cli *Client) DecryptReaction(reaction *events.Message) (*waProto.ReactionMessage, error) {
|
||||
encReaction := reaction.Message.GetEncReactionMessage()
|
||||
if encReaction != nil {
|
||||
if encReaction == nil {
|
||||
return nil, ErrNotEncryptedReactionMessage
|
||||
}
|
||||
plaintext, err := cli.decryptMsgSecret(reaction, EncSecretReaction, encReaction, encReaction.GetTargetMessageKey())
|
||||
@@ -225,11 +221,7 @@ func (cli *Client) BuildPollVote(pollInfo *types.MessageInfo, optionNames []stri
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildPollCreation("meow?", []string{"yes", "no"}, 1))
|
||||
func (cli *Client) BuildPollCreation(name string, optionNames []string, selectableOptionCount int) *waProto.Message {
|
||||
msgSecret := make([]byte, 32)
|
||||
_, err := rand.Read(msgSecret)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
msgSecret := randbytes.Make(32)
|
||||
if selectableOptionCount < 0 || selectableOptionCount > len(optionNames) {
|
||||
selectableOptionCount = 0
|
||||
}
|
||||
|
||||
7
vendor/go.mau.fi/whatsmeow/notification.go
vendored
7
vendor/go.mau.fi/whatsmeow/notification.go
vendored
@@ -173,6 +173,11 @@ func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) {
|
||||
cli.handlePrivacySettingsNotification(&child)
|
||||
case "devices":
|
||||
cli.handleOwnDevicesNotification(&child)
|
||||
case "picture":
|
||||
cli.dispatchEvent(&events.Picture{
|
||||
Timestamp: node.AttrGetter().UnixTime("t"),
|
||||
JID: cli.getOwnID().ToNonAD(),
|
||||
})
|
||||
default:
|
||||
cli.Log.Debugf("Unhandled account sync item %s", child.Tag)
|
||||
}
|
||||
@@ -254,6 +259,8 @@ func (cli *Client) handleNotification(node *waBinary.Node) {
|
||||
go cli.handleMediaRetryNotification(node)
|
||||
case "privacy_token":
|
||||
go cli.handlePrivacyTokenNotification(node)
|
||||
case "link_code_companion_reg":
|
||||
go cli.tryHandleCodePairNotification(node)
|
||||
// Other types: business, disappearing_mode, server, status, pay, psa
|
||||
default:
|
||||
cli.Log.Debugf("Unhandled notification with type %s", notifType)
|
||||
|
||||
252
vendor/go.mau.fi/whatsmeow/pair-code.go
vendored
Normal file
252
vendor/go.mau.fi/whatsmeow/pair-code.go
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
// Copyright (c) 2023 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// PairClientType is the type of client to use with PairCode.
|
||||
// The type is automatically filled based on store.DeviceProps.PlatformType (which is what QR login uses).
|
||||
type PairClientType int
|
||||
|
||||
const (
|
||||
PairClientUnknown PairClientType = iota
|
||||
PairClientChrome
|
||||
PairClientEdge
|
||||
PairClientFirefox
|
||||
PairClientIE
|
||||
PairClientOpera
|
||||
PairClientSafari
|
||||
PairClientElectron
|
||||
PairClientUWP
|
||||
PairClientOtherWebClient
|
||||
)
|
||||
|
||||
func platformTypeToPairClientType(platformType waProto.DeviceProps_PlatformType) PairClientType {
|
||||
switch platformType {
|
||||
case waProto.DeviceProps_CHROME:
|
||||
return PairClientChrome
|
||||
case waProto.DeviceProps_EDGE:
|
||||
return PairClientEdge
|
||||
case waProto.DeviceProps_FIREFOX:
|
||||
return PairClientFirefox
|
||||
case waProto.DeviceProps_IE:
|
||||
return PairClientIE
|
||||
case waProto.DeviceProps_OPERA:
|
||||
return PairClientOpera
|
||||
case waProto.DeviceProps_SAFARI:
|
||||
return PairClientSafari
|
||||
case waProto.DeviceProps_DESKTOP:
|
||||
return PairClientElectron
|
||||
case waProto.DeviceProps_UWP:
|
||||
return PairClientUWP
|
||||
default:
|
||||
return PairClientOtherWebClient
|
||||
}
|
||||
}
|
||||
|
||||
var notNumbers = regexp.MustCompile("[^0-9]")
|
||||
var linkingBase32 = base32.NewEncoding("123456789ABCDEFGHJKLMNPQRSTVWXYZ")
|
||||
|
||||
type phoneLinkingCache struct {
|
||||
jid types.JID
|
||||
keyPair *keys.KeyPair
|
||||
linkingCode string
|
||||
pairingRef string
|
||||
}
|
||||
|
||||
func generateCompanionEphemeralKey() (ephemeralKeyPair *keys.KeyPair, ephemeralKey []byte, encodedLinkingCode string) {
|
||||
ephemeralKeyPair = keys.NewKeyPair()
|
||||
salt := randbytes.Make(32)
|
||||
iv := randbytes.Make(16)
|
||||
linkingCode := randbytes.Make(5)
|
||||
encodedLinkingCode = linkingBase32.EncodeToString(linkingCode)
|
||||
linkCodeKey := pbkdf2.Key([]byte(encodedLinkingCode), salt, 2<<16, 32, sha256.New)
|
||||
linkCipherBlock, _ := aes.NewCipher(linkCodeKey)
|
||||
encryptedPubkey := ephemeralKeyPair.Pub[:]
|
||||
cipher.NewCTR(linkCipherBlock, iv).XORKeyStream(encryptedPubkey, encryptedPubkey)
|
||||
ephemeralKey = make([]byte, 80)
|
||||
copy(ephemeralKey[0:32], salt)
|
||||
copy(ephemeralKey[32:48], iv)
|
||||
copy(ephemeralKey[48:80], encryptedPubkey)
|
||||
return
|
||||
}
|
||||
|
||||
// PairPhone generates a pairing code that can be used to link to a phone without scanning a QR code.
|
||||
//
|
||||
// The exact expiry of pairing codes is unknown, but QR codes are always generated and the login websocket is closed
|
||||
// after the QR codes run out, which means there's a 160-second time limit. It is recommended to generate the pairing
|
||||
// code immediately after connecting to the websocket to have the maximum time.
|
||||
//
|
||||
// See https://faq.whatsapp.com/1324084875126592 for more info
|
||||
func (cli *Client) PairPhone(phone string, showPushNotification bool) (string, error) {
|
||||
clientType := platformTypeToPairClientType(store.DeviceProps.GetPlatformType())
|
||||
clientDisplayName := store.DeviceProps.GetOs()
|
||||
|
||||
ephemeralKeyPair, ephemeralKey, encodedLinkingCode := generateCompanionEphemeralKey()
|
||||
phone = notNumbers.ReplaceAllString(phone, "")
|
||||
jid := types.NewJID(phone, types.DefaultUserServer)
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "md",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "link_code_companion_reg",
|
||||
Attrs: waBinary.Attrs{
|
||||
"jid": jid,
|
||||
"stage": "companion_hello",
|
||||
|
||||
"should_show_push_notification": strconv.FormatBool(showPushNotification),
|
||||
},
|
||||
Content: []waBinary.Node{
|
||||
{Tag: "link_code_pairing_wrapped_companion_ephemeral_pub", Content: ephemeralKey},
|
||||
{Tag: "companion_server_auth_key_pub", Content: cli.Store.NoiseKey.Pub[:]},
|
||||
{Tag: "companion_platform_id", Content: strconv.Itoa(int(clientType))},
|
||||
{Tag: "companion_platform_display", Content: clientDisplayName},
|
||||
{Tag: "link_code_pairing_nonce", Content: []byte{0}},
|
||||
},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pairingRefNode, ok := resp.GetOptionalChildByTag("link_code_companion_reg", "link_code_pairing_ref")
|
||||
if !ok {
|
||||
return "", &ElementMissingError{Tag: "link_code_pairing_ref", In: "code link registration response"}
|
||||
}
|
||||
pairingRef, ok := pairingRefNode.Content.([]byte)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected type %T in content of link_code_pairing_ref tag", pairingRefNode.Content)
|
||||
}
|
||||
cli.phoneLinkingCache = &phoneLinkingCache{
|
||||
jid: jid,
|
||||
keyPair: ephemeralKeyPair,
|
||||
linkingCode: encodedLinkingCode,
|
||||
pairingRef: string(pairingRef),
|
||||
}
|
||||
return encodedLinkingCode[0:4] + "-" + encodedLinkingCode[4:], nil
|
||||
}
|
||||
|
||||
func (cli *Client) tryHandleCodePairNotification(parentNode *waBinary.Node) {
|
||||
err := cli.handleCodePairNotification(parentNode)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to handle code pair notification: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleCodePairNotification(parentNode *waBinary.Node) error {
|
||||
node, ok := parentNode.GetOptionalChildByTag("link_code_companion_reg")
|
||||
if !ok {
|
||||
return &ElementMissingError{
|
||||
Tag: "link_code_companion_reg",
|
||||
In: "notification",
|
||||
}
|
||||
}
|
||||
linkCache := cli.phoneLinkingCache
|
||||
if linkCache == nil {
|
||||
return fmt.Errorf("received code pair notification without a pending pairing")
|
||||
}
|
||||
linkCodePairingRef, _ := node.GetChildByTag("link_code_pairing_ref").Content.([]byte)
|
||||
if string(linkCodePairingRef) != linkCache.pairingRef {
|
||||
return fmt.Errorf("pairing ref mismatch in code pair notification")
|
||||
}
|
||||
wrappedPrimaryEphemeralPub, ok := node.GetChildByTag("link_code_pairing_wrapped_primary_ephemeral_pub").Content.([]byte)
|
||||
if !ok {
|
||||
return &ElementMissingError{
|
||||
Tag: "link_code_pairing_wrapped_primary_ephemeral_pub",
|
||||
In: "notification",
|
||||
}
|
||||
}
|
||||
primaryIdentityPub, ok := node.GetChildByTag("primary_identity_pub").Content.([]byte)
|
||||
if !ok {
|
||||
return &ElementMissingError{
|
||||
Tag: "primary_identity_pub",
|
||||
In: "notification",
|
||||
}
|
||||
}
|
||||
|
||||
advSecretRandom := randbytes.Make(32)
|
||||
keyBundleSalt := randbytes.Make(32)
|
||||
keyBundleNonce := randbytes.Make(12)
|
||||
|
||||
// Decrypt the primary device's ephemeral public key, which was encrypted with the 8-character pairing code,
|
||||
// then compute the DH shared secret using our ephemeral private key we generated earlier.
|
||||
primarySalt := wrappedPrimaryEphemeralPub[0:32]
|
||||
primaryIV := wrappedPrimaryEphemeralPub[32:48]
|
||||
primaryEncryptedPubkey := wrappedPrimaryEphemeralPub[48:80]
|
||||
linkCodeKey := pbkdf2.Key([]byte(linkCache.linkingCode), primarySalt, 2<<16, 32, sha256.New)
|
||||
linkCipherBlock, err := aes.NewCipher(linkCodeKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create link cipher: %w", err)
|
||||
}
|
||||
primaryDecryptedPubkey := make([]byte, 32)
|
||||
cipher.NewCTR(linkCipherBlock, primaryIV).XORKeyStream(primaryDecryptedPubkey, primaryEncryptedPubkey)
|
||||
ephemeralSharedSecret, err := curve25519.X25519(linkCache.keyPair.Priv[:], primaryDecryptedPubkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute ephemeral shared secret: %w", err)
|
||||
}
|
||||
|
||||
// Encrypt and wrap key bundle containing our identity key, the primary device's identity key and the randomness used for the adv key.
|
||||
keyBundleEncryptionKey := hkdfutil.SHA256(ephemeralSharedSecret, keyBundleSalt, []byte("link_code_pairing_key_bundle_encryption_key"), 32)
|
||||
keyBundleCipherBlock, err := aes.NewCipher(keyBundleEncryptionKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create key bundle cipher: %w", err)
|
||||
}
|
||||
keyBundleGCM, err := cipher.NewGCM(keyBundleCipherBlock)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create key bundle GCM: %w", err)
|
||||
}
|
||||
plaintextKeyBundle := concatBytes(cli.Store.IdentityKey.Pub[:], primaryIdentityPub, advSecretRandom)
|
||||
encryptedKeyBundle := keyBundleGCM.Seal(nil, keyBundleNonce, plaintextKeyBundle, nil)
|
||||
wrappedKeyBundle := concatBytes(keyBundleSalt, keyBundleNonce, encryptedKeyBundle)
|
||||
|
||||
// Compute the adv secret key (which is used to authenticate the pair-success event later)
|
||||
identitySharedKey, err := curve25519.X25519(cli.Store.IdentityKey.Priv[:], primaryIdentityPub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute identity shared key: %w", err)
|
||||
}
|
||||
advSecretInput := append(append(ephemeralSharedSecret, identitySharedKey...), advSecretRandom...)
|
||||
advSecret := hkdfutil.SHA256(advSecretInput, nil, []byte("adv_secret"), 32)
|
||||
cli.Store.AdvSecretKey = advSecret
|
||||
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
Namespace: "md",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "link_code_companion_reg",
|
||||
Attrs: waBinary.Attrs{
|
||||
"jid": linkCache.jid,
|
||||
"stage": "companion_finish",
|
||||
},
|
||||
Content: []waBinary.Node{
|
||||
{Tag: "link_code_pairing_wrapped_key_bundle", Content: wrappedKeyBundle},
|
||||
{Tag: "companion_identity_public", Content: cli.Store.IdentityKey.Pub[:]},
|
||||
{Tag: "link_code_pairing_ref", Content: linkCodePairingRef},
|
||||
},
|
||||
}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
3
vendor/go.mau.fi/whatsmeow/receipt.go
vendored
3
vendor/go.mau.fi/whatsmeow/receipt.go
vendored
@@ -124,6 +124,9 @@ func (cli *Client) sendAck(node *waBinary.Node) {
|
||||
//
|
||||
// The first JID parameter (chat) must always be set to the chat ID (user ID in DMs and group ID in group chats).
|
||||
// The second JID parameter (sender) must be set in group chats and must be the user ID who sent the message.
|
||||
//
|
||||
// You can mark multiple messages as read at the same time, but only if the messages were sent by the same user.
|
||||
// To mark messages by different users as read, you must call MarkRead multiple times (once for each user).
|
||||
func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, sender types.JID) error {
|
||||
node := waBinary.Node{
|
||||
Tag: "receipt",
|
||||
|
||||
72
vendor/go.mau.fi/whatsmeow/retry.go
vendored
72
vendor/go.mau.fi/whatsmeow/retry.go
vendored
@@ -169,7 +169,11 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
return fmt.Errorf("didn't get prekey bundle for %s (response size: %d)", senderAD, len(keys))
|
||||
}
|
||||
}
|
||||
encrypted, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, receipt.Sender, bundle)
|
||||
encAttrs := waBinary.Attrs{}
|
||||
if mediaType := getMediaTypeFromMessage(msg); mediaType != "" {
|
||||
encAttrs["mediatype"] = mediaType
|
||||
}
|
||||
encrypted, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, receipt.Sender, bundle, encAttrs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt message for retry: %w", err)
|
||||
}
|
||||
@@ -193,14 +197,10 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
if edit, ok := node.Attrs["edit"]; ok {
|
||||
attrs["edit"] = edit
|
||||
}
|
||||
content := []waBinary.Node{*encrypted}
|
||||
if includeDeviceIdentity {
|
||||
content = append(content, cli.makeDeviceIdentityNode())
|
||||
}
|
||||
err = cli.sendNode(waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
Content: content,
|
||||
Content: cli.getMessageContent(*encrypted, msg, attrs, includeDeviceIdentity),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send retry message: %w", err)
|
||||
@@ -209,8 +209,63 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *Client) cancelDelayedRequestFromPhone(msgID types.MessageID) {
|
||||
if !cli.AutomaticMessageRerequestFromPhone {
|
||||
return
|
||||
}
|
||||
cli.pendingPhoneRerequestsLock.RLock()
|
||||
cancelPendingRequest, ok := cli.pendingPhoneRerequests[msgID]
|
||||
if ok {
|
||||
cancelPendingRequest()
|
||||
}
|
||||
cli.pendingPhoneRerequestsLock.RUnlock()
|
||||
}
|
||||
|
||||
// RequestFromPhoneDelay specifies how long to wait for the sender to resend the message before requesting from your phone.
|
||||
// This is only used if Client.AutomaticMessageRerequestFromPhone is true.
|
||||
var RequestFromPhoneDelay = 5 * time.Second
|
||||
|
||||
func (cli *Client) delayedRequestMessageFromPhone(info *types.MessageInfo) {
|
||||
if !cli.AutomaticMessageRerequestFromPhone {
|
||||
return
|
||||
}
|
||||
cli.pendingPhoneRerequestsLock.Lock()
|
||||
_, alreadyRequesting := cli.pendingPhoneRerequests[info.ID]
|
||||
if alreadyRequesting {
|
||||
cli.pendingPhoneRerequestsLock.Unlock()
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
cli.pendingPhoneRerequests[info.ID] = cancel
|
||||
cli.pendingPhoneRerequestsLock.Unlock()
|
||||
|
||||
defer func() {
|
||||
cli.pendingPhoneRerequestsLock.Lock()
|
||||
delete(cli.pendingPhoneRerequests, info.ID)
|
||||
cli.pendingPhoneRerequestsLock.Unlock()
|
||||
}()
|
||||
select {
|
||||
case <-time.After(RequestFromPhoneDelay):
|
||||
case <-ctx.Done():
|
||||
cli.Log.Debugf("Cancelled delayed request for message %s from phone", info.ID)
|
||||
return
|
||||
}
|
||||
_, err := cli.SendMessage(
|
||||
ctx,
|
||||
cli.Store.ID.ToNonAD(),
|
||||
cli.BuildUnavailableMessageRequest(info.Chat, info.Sender, info.ID),
|
||||
SendRequestExtra{Peer: true},
|
||||
)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to send request for unavailable message %s to phone: %v", info.ID, err)
|
||||
} else {
|
||||
cli.Log.Debugf("Requested message %s from phone", info.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// sendRetryReceipt sends a retry receipt for an incoming message.
|
||||
func (cli *Client) sendRetryReceipt(node *waBinary.Node, forceIncludeIdentity bool) {
|
||||
func (cli *Client) sendRetryReceipt(node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool) {
|
||||
id, _ := node.Attrs["id"].(string)
|
||||
children := node.GetChildren()
|
||||
var retryCountInMsg int
|
||||
@@ -231,6 +286,9 @@ func (cli *Client) sendRetryReceipt(node *waBinary.Node, forceIncludeIdentity bo
|
||||
cli.Log.Warnf("Not sending any more retry receipts for %s", id)
|
||||
return
|
||||
}
|
||||
if retryCount == 1 {
|
||||
go cli.delayedRequestMessageFromPhone(info)
|
||||
}
|
||||
|
||||
var registrationIDBytes [4]byte
|
||||
binary.BigEndian.PutUint32(registrationIDBytes[:], cli.Store.RegistrationID)
|
||||
|
||||
351
vendor/go.mau.fi/whatsmeow/send.go
vendored
351
vendor/go.mau.fi/whatsmeow/send.go
vendored
@@ -8,9 +8,9 @@ package whatsmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -30,20 +30,35 @@ import (
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// GenerateMessageID generates a random string that can be used as a message ID on WhatsApp.
|
||||
//
|
||||
// msgID := cli.GenerateMessageID()
|
||||
// cli.SendMessage(context.Background(), targetJID, &waProto.Message{...}, whatsmeow.SendRequestExtra{ID: msgID})
|
||||
func (cli *Client) GenerateMessageID() types.MessageID {
|
||||
data := make([]byte, 8, 8+20+16)
|
||||
binary.BigEndian.PutUint64(data, uint64(time.Now().Unix()))
|
||||
ownID := cli.getOwnID()
|
||||
if !ownID.IsEmpty() {
|
||||
data = append(data, []byte(ownID.User)...)
|
||||
data = append(data, []byte("@c.us")...)
|
||||
}
|
||||
data = append(data, randbytes.Make(16)...)
|
||||
hash := sha256.Sum256(data)
|
||||
return "3EB0" + strings.ToUpper(hex.EncodeToString(hash[:9]))
|
||||
}
|
||||
|
||||
// GenerateMessageID generates a random string that can be used as a message ID on WhatsApp.
|
||||
//
|
||||
// msgID := whatsmeow.GenerateMessageID()
|
||||
// cli.SendMessage(context.Background(), targetJID, &waProto.Message{...}, whatsmeow.SendRequestExtra{ID: msgID})
|
||||
//
|
||||
// Deprecated: WhatsApp web has switched to using a hash of the current timestamp, user id and random bytes. Use Client.GenerateMessageID instead.
|
||||
func GenerateMessageID() types.MessageID {
|
||||
id := make([]byte, 8)
|
||||
_, err := rand.Read(id)
|
||||
if err != nil {
|
||||
// Out of entropy
|
||||
panic(err)
|
||||
}
|
||||
return "3EB0" + strings.ToUpper(hex.EncodeToString(id))
|
||||
return "3EB0" + strings.ToUpper(hex.EncodeToString(randbytes.Make(8)))
|
||||
}
|
||||
|
||||
type MessageDebugTimings struct {
|
||||
@@ -132,7 +147,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
}
|
||||
|
||||
if len(req.ID) == 0 {
|
||||
req.ID = GenerateMessageID()
|
||||
req.ID = cli.GenerateMessageID()
|
||||
}
|
||||
resp.ID = req.ID
|
||||
|
||||
@@ -216,17 +231,9 @@ func (cli *Client) RevokeMessage(chat types.JID, id types.MessageID) (SendRespon
|
||||
return cli.SendMessage(context.TODO(), chat, cli.BuildRevoke(chat, types.EmptyJID, id))
|
||||
}
|
||||
|
||||
// BuildRevoke builds a message revocation message using the given variables.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// To revoke your own messages, pass your JID or an empty JID as the second parameter (sender).
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildRevoke(chat, types.EmptyJID, originalMessageID)
|
||||
//
|
||||
// To revoke someone else's messages when you are group admin, pass the message sender's JID as the second parameter.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildRevoke(chat, senderJID, originalMessageID)
|
||||
func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waProto.Message {
|
||||
// BuildMessageKey builds a MessageKey object, which is used to refer to previous messages
|
||||
// for things such as replies, revocations and reactions.
|
||||
func (cli *Client) BuildMessageKey(chat, sender types.JID, id types.MessageID) *waProto.MessageKey {
|
||||
key := &waProto.MessageKey{
|
||||
FromMe: proto.Bool(true),
|
||||
Id: proto.String(id),
|
||||
@@ -238,14 +245,90 @@ func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waPr
|
||||
key.Participant = proto.String(sender.ToNonAD().String())
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// BuildRevoke builds a message revocation message using the given variables.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// To revoke your own messages, pass your JID or an empty JID as the second parameter (sender).
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildRevoke(chat, types.EmptyJID, originalMessageID)
|
||||
//
|
||||
// To revoke someone else's messages when you are group admin, pass the message sender's JID as the second parameter.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildRevoke(chat, senderJID, originalMessageID)
|
||||
func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waProto.Message {
|
||||
return &waProto.Message{
|
||||
ProtocolMessage: &waProto.ProtocolMessage{
|
||||
Type: waProto.ProtocolMessage_REVOKE.Enum(),
|
||||
Key: key,
|
||||
Key: cli.BuildMessageKey(chat, sender, id),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildReaction builds a message reaction message using the given variables.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildReaction(chat, senderJID, targetMessageID, "🐈️")
|
||||
func (cli *Client) BuildReaction(chat, sender types.JID, id types.MessageID, reaction string) *waProto.Message {
|
||||
return &waProto.Message{
|
||||
ReactionMessage: &waProto.ReactionMessage{
|
||||
Key: cli.BuildMessageKey(chat, sender, id),
|
||||
Text: proto.String(reaction),
|
||||
SenderTimestampMs: proto.Int64(time.Now().UnixMilli()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildUnavailableMessageRequest builds a message to request the user's primary device to send
|
||||
// the copy of a message that this client was unable to decrypt.
|
||||
//
|
||||
// The built message can be sent using Client.SendMessage, but you must pass whatsmeow.SendRequestExtra{Peer: true} as the last parameter.
|
||||
// The full response will come as a ProtocolMessage with type `PEER_DATA_OPERATION_REQUEST_RESPONSE_MESSAGE`.
|
||||
// The response events will also be dispatched as normal *events.Message's with UnavailableRequestID set to the request message ID.
|
||||
func (cli *Client) BuildUnavailableMessageRequest(chat, sender types.JID, id string) *waProto.Message {
|
||||
return &waProto.Message{
|
||||
ProtocolMessage: &waProto.ProtocolMessage{
|
||||
Type: waProto.ProtocolMessage_PEER_DATA_OPERATION_REQUEST_MESSAGE.Enum(),
|
||||
PeerDataOperationRequestMessage: &waProto.PeerDataOperationRequestMessage{
|
||||
PeerDataOperationRequestType: waProto.PeerDataOperationRequestType_PLACEHOLDER_MESSAGE_RESEND.Enum(),
|
||||
PlaceholderMessageResendRequest: []*waProto.PeerDataOperationRequestMessage_PlaceholderMessageResendRequest{{
|
||||
MessageKey: cli.BuildMessageKey(chat, sender, id),
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildHistorySyncRequest builds a message to request additional history from the user's primary device.
|
||||
//
|
||||
// The built message can be sent using Client.SendMessage, but you must pass whatsmeow.SendRequestExtra{Peer: true} as the last parameter.
|
||||
// The response will come as an *events.HistorySync with type `ON_DEMAND`.
|
||||
//
|
||||
// The response will contain to `count` messages immediately before the given message.
|
||||
// The recommended number of messages to request at a time is 50.
|
||||
func (cli *Client) BuildHistorySyncRequest(lastKnownMessageInfo *types.MessageInfo, count int) *waProto.Message {
|
||||
return &waProto.Message{
|
||||
ProtocolMessage: &waProto.ProtocolMessage{
|
||||
Type: waProto.ProtocolMessage_PEER_DATA_OPERATION_REQUEST_MESSAGE.Enum(),
|
||||
PeerDataOperationRequestMessage: &waProto.PeerDataOperationRequestMessage{
|
||||
PeerDataOperationRequestType: waProto.PeerDataOperationRequestType_HISTORY_SYNC_ON_DEMAND.Enum(),
|
||||
HistorySyncOnDemandRequest: &waProto.PeerDataOperationRequestMessage_HistorySyncOnDemandRequest{
|
||||
ChatJid: proto.String(lastKnownMessageInfo.Chat.String()),
|
||||
OldestMsgId: proto.String(lastKnownMessageInfo.ID),
|
||||
OldestMsgFromMe: proto.Bool(lastKnownMessageInfo.IsFromMe),
|
||||
OnDemandMsgCount: proto.Int32(int32(count)),
|
||||
OldestMsgTimestampMs: proto.Int64(lastKnownMessageInfo.Timestamp.UnixMilli()),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EditWindow specifies how long a message can be edited for after it was sent.
|
||||
const EditWindow = 20 * time.Minute
|
||||
|
||||
// BuildEdit builds a message edit message using the given variables.
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
@@ -399,11 +482,15 @@ func (cli *Client) sendGroup(ctx context.Context, to, ownID types.JID, id types.
|
||||
|
||||
phash := participantListHashV2(allDevices)
|
||||
node.Attrs["phash"] = phash
|
||||
node.Content = append(node.GetChildren(), waBinary.Node{
|
||||
skMsg := waBinary.Node{
|
||||
Tag: "enc",
|
||||
Content: ciphertext,
|
||||
Attrs: waBinary.Attrs{"v": "2", "type": "skmsg"},
|
||||
})
|
||||
}
|
||||
if mediaType := getMediaTypeFromMessage(message); mediaType != "" {
|
||||
skMsg.Attrs["mediatype"] = mediaType
|
||||
}
|
||||
node.Content = append(node.GetChildren(), skMsg)
|
||||
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
@@ -463,16 +550,109 @@ func getTypeFromMessage(msg *waProto.Message) string {
|
||||
return "reaction"
|
||||
case msg.PollCreationMessage != nil, msg.PollUpdateMessage != nil:
|
||||
return "poll"
|
||||
case getMediaTypeFromMessage(msg) != "":
|
||||
return "media"
|
||||
case msg.Conversation != nil, msg.ExtendedTextMessage != nil, msg.ProtocolMessage != nil:
|
||||
return "text"
|
||||
//TODO this requires setting mediatype in the enc nodes
|
||||
//case msg.ImageMessage != nil, msg.DocumentMessage != nil, msg.AudioMessage != nil, msg.VideoMessage != nil:
|
||||
// return "media"
|
||||
default:
|
||||
return "text"
|
||||
}
|
||||
}
|
||||
|
||||
func getMediaTypeFromMessage(msg *waProto.Message) string {
|
||||
switch {
|
||||
case msg.ViewOnceMessage != nil:
|
||||
return getMediaTypeFromMessage(msg.ViewOnceMessage.Message)
|
||||
case msg.ViewOnceMessageV2 != nil:
|
||||
return getMediaTypeFromMessage(msg.ViewOnceMessageV2.Message)
|
||||
case msg.EphemeralMessage != nil:
|
||||
return getMediaTypeFromMessage(msg.EphemeralMessage.Message)
|
||||
case msg.DocumentWithCaptionMessage != nil:
|
||||
return getMediaTypeFromMessage(msg.DocumentWithCaptionMessage.Message)
|
||||
case msg.ExtendedTextMessage != nil && msg.ExtendedTextMessage.Title != nil:
|
||||
return "url"
|
||||
case msg.ImageMessage != nil:
|
||||
return "image"
|
||||
case msg.StickerMessage != nil:
|
||||
return "sticker"
|
||||
case msg.DocumentMessage != nil:
|
||||
return "document"
|
||||
case msg.AudioMessage != nil:
|
||||
if msg.AudioMessage.GetPtt() {
|
||||
return "ptt"
|
||||
} else {
|
||||
return "audio"
|
||||
}
|
||||
case msg.VideoMessage != nil:
|
||||
if msg.VideoMessage.GetGifPlayback() {
|
||||
return "gif"
|
||||
} else {
|
||||
return "video"
|
||||
}
|
||||
case msg.ContactMessage != nil:
|
||||
return "vcard"
|
||||
case msg.ContactsArrayMessage != nil:
|
||||
return "contact_array"
|
||||
case msg.ListMessage != nil:
|
||||
return "list"
|
||||
case msg.ListResponseMessage != nil:
|
||||
return "list_response"
|
||||
case msg.ButtonsResponseMessage != nil:
|
||||
return "buttons_response"
|
||||
case msg.OrderMessage != nil:
|
||||
return "order"
|
||||
case msg.ProductMessage != nil:
|
||||
return "product"
|
||||
case msg.InteractiveResponseMessage != nil:
|
||||
return "native_flow_response"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getButtonTypeFromMessage(msg *waProto.Message) string {
|
||||
switch {
|
||||
case msg.ViewOnceMessage != nil:
|
||||
return getButtonTypeFromMessage(msg.ViewOnceMessage.Message)
|
||||
case msg.ViewOnceMessageV2 != nil:
|
||||
return getButtonTypeFromMessage(msg.ViewOnceMessageV2.Message)
|
||||
case msg.EphemeralMessage != nil:
|
||||
return getButtonTypeFromMessage(msg.EphemeralMessage.Message)
|
||||
case msg.ButtonsMessage != nil:
|
||||
return "buttons"
|
||||
case msg.ButtonsResponseMessage != nil:
|
||||
return "buttons_response"
|
||||
case msg.ListMessage != nil:
|
||||
return "list"
|
||||
case msg.ListResponseMessage != nil:
|
||||
return "list_response"
|
||||
case msg.InteractiveResponseMessage != nil:
|
||||
return "interactive_response"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getButtonAttributes(msg *waProto.Message) waBinary.Attrs {
|
||||
switch {
|
||||
case msg.ViewOnceMessage != nil:
|
||||
return getButtonAttributes(msg.ViewOnceMessage.Message)
|
||||
case msg.ViewOnceMessageV2 != nil:
|
||||
return getButtonAttributes(msg.ViewOnceMessageV2.Message)
|
||||
case msg.EphemeralMessage != nil:
|
||||
return getButtonAttributes(msg.EphemeralMessage.Message)
|
||||
case msg.TemplateMessage != nil:
|
||||
return waBinary.Attrs{}
|
||||
case msg.ListMessage != nil:
|
||||
return waBinary.Attrs{
|
||||
"v": "2",
|
||||
"type": strings.ToLower(waProto.ListMessage_ListType_name[int32(msg.ListMessage.GetListType())]),
|
||||
}
|
||||
default:
|
||||
return waBinary.Attrs{}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
EditAttributeEmpty = ""
|
||||
EditAttributeMessageEdit = "1"
|
||||
@@ -484,6 +664,8 @@ const RemoveReactionText = ""
|
||||
|
||||
func getEditAttribute(msg *waProto.Message) string {
|
||||
switch {
|
||||
case msg.EditedMessage != nil && msg.EditedMessage.Message != nil:
|
||||
return getEditAttribute(msg.EditedMessage.Message)
|
||||
case msg.ProtocolMessage != nil && msg.ProtocolMessage.GetKey() != nil:
|
||||
switch msg.ProtocolMessage.GetType() {
|
||||
case waProto.ProtocolMessage_REVOKE:
|
||||
@@ -493,7 +675,7 @@ func getEditAttribute(msg *waProto.Message) string {
|
||||
return EditAttributeAdminRevoke
|
||||
}
|
||||
case waProto.ProtocolMessage_MESSAGE_EDIT:
|
||||
if msg.EditedMessage != nil {
|
||||
if msg.ProtocolMessage.EditedMessage != nil {
|
||||
return EditAttributeMessageEdit
|
||||
}
|
||||
}
|
||||
@@ -523,7 +705,7 @@ func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, mess
|
||||
return nil, err
|
||||
}
|
||||
start = time.Now()
|
||||
encrypted, isPreKey, err := cli.encryptMessageForDevice(plaintext, to, nil)
|
||||
encrypted, isPreKey, err := cli.encryptMessageForDevice(plaintext, to, nil, nil)
|
||||
timings.PeerEncrypt = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt peer message for %s: %v", to, err)
|
||||
@@ -539,34 +721,12 @@ func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, mess
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cli *Client) prepareMessageNode(ctx context.Context, to, ownID types.JID, id types.MessageID, message *waProto.Message, participants []types.JID, plaintext, dsmPlaintext []byte, timings *MessageDebugTimings) (*waBinary.Node, []types.JID, error) {
|
||||
start := time.Now()
|
||||
allDevices, err := cli.GetUserDevicesContext(ctx, participants)
|
||||
timings.GetDevices = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
attrs := waBinary.Attrs{
|
||||
"id": id,
|
||||
"type": getTypeFromMessage(message),
|
||||
"to": to,
|
||||
}
|
||||
if editAttr := getEditAttribute(message); editAttr != "" {
|
||||
attrs["edit"] = editAttr
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
participantNodes, includeIdentity := cli.encryptMessageForDevices(ctx, allDevices, ownID, id, plaintext, dsmPlaintext)
|
||||
timings.PeerEncrypt = time.Since(start)
|
||||
content := []waBinary.Node{{
|
||||
Tag: "participants",
|
||||
Content: participantNodes,
|
||||
}}
|
||||
func (cli *Client) getMessageContent(baseNode waBinary.Node, message *waProto.Message, msgAttrs waBinary.Attrs, includeIdentity bool) []waBinary.Node {
|
||||
content := []waBinary.Node{baseNode}
|
||||
if includeIdentity {
|
||||
content = append(content, cli.makeDeviceIdentityNode())
|
||||
}
|
||||
if attrs["type"] == "poll" {
|
||||
if msgAttrs["type"] == "poll" {
|
||||
pollType := "creation"
|
||||
if message.PollUpdateMessage != nil {
|
||||
pollType = "vote"
|
||||
@@ -578,10 +738,56 @@ func (cli *Client) prepareMessageNode(ctx context.Context, to, ownID types.JID,
|
||||
},
|
||||
})
|
||||
}
|
||||
if buttonType := getButtonTypeFromMessage(message); buttonType != "" {
|
||||
content = append(content, waBinary.Node{
|
||||
Tag: "biz",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: buttonType,
|
||||
Attrs: getButtonAttributes(message),
|
||||
}},
|
||||
})
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func (cli *Client) prepareMessageNode(ctx context.Context, to, ownID types.JID, id types.MessageID, message *waProto.Message, participants []types.JID, plaintext, dsmPlaintext []byte, timings *MessageDebugTimings) (*waBinary.Node, []types.JID, error) {
|
||||
start := time.Now()
|
||||
allDevices, err := cli.GetUserDevicesContext(ctx, participants)
|
||||
timings.GetDevices = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
msgType := getTypeFromMessage(message)
|
||||
encAttrs := waBinary.Attrs{}
|
||||
// Only include encMediaType for 1:1 messages (groups don't have a device-sent message plaintext)
|
||||
if encMediaType := getMediaTypeFromMessage(message); dsmPlaintext != nil && encMediaType != "" {
|
||||
encAttrs["mediatype"] = encMediaType
|
||||
}
|
||||
attrs := waBinary.Attrs{
|
||||
"id": id,
|
||||
"type": msgType,
|
||||
"to": to,
|
||||
}
|
||||
if editAttr := getEditAttribute(message); editAttr != "" {
|
||||
attrs["edit"] = editAttr
|
||||
encAttrs["decrypt-fail"] = string(events.DecryptFailHide)
|
||||
}
|
||||
if msgType == "reaction" {
|
||||
encAttrs["decrypt-fail"] = string(events.DecryptFailHide)
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
participantNodes, includeIdentity := cli.encryptMessageForDevices(ctx, allDevices, ownID, id, plaintext, dsmPlaintext, encAttrs)
|
||||
timings.PeerEncrypt = time.Since(start)
|
||||
participantNode := waBinary.Node{
|
||||
Tag: "participants",
|
||||
Content: participantNodes,
|
||||
}
|
||||
return &waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
Content: content,
|
||||
Content: cli.getMessageContent(participantNode, message, attrs, includeIdentity),
|
||||
}, allDevices, nil
|
||||
}
|
||||
|
||||
@@ -619,7 +825,7 @@ func (cli *Client) makeDeviceIdentityNode() waBinary.Node {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []types.JID, ownID types.JID, id string, msgPlaintext, dsmPlaintext []byte) ([]waBinary.Node, bool) {
|
||||
func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []types.JID, ownID types.JID, id string, msgPlaintext, dsmPlaintext []byte, encAttrs waBinary.Attrs) ([]waBinary.Node, bool) {
|
||||
includeIdentity := false
|
||||
participantNodes := make([]waBinary.Node, 0, len(allDevices))
|
||||
var retryDevices []types.JID
|
||||
@@ -631,7 +837,7 @@ func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []ty
|
||||
}
|
||||
plaintext = dsmPlaintext
|
||||
}
|
||||
encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(plaintext, jid, nil)
|
||||
encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(plaintext, jid, nil, encAttrs)
|
||||
if errors.Is(err, ErrNoSession) {
|
||||
retryDevices = append(retryDevices, jid)
|
||||
continue
|
||||
@@ -659,7 +865,7 @@ func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []ty
|
||||
if jid.User == ownID.User && dsmPlaintext != nil {
|
||||
plaintext = dsmPlaintext
|
||||
}
|
||||
encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(plaintext, jid, resp.bundle)
|
||||
encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(plaintext, jid, resp.bundle, encAttrs)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to encrypt %s for %s (retry): %v", id, jid, err)
|
||||
continue
|
||||
@@ -674,8 +880,8 @@ func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []ty
|
||||
return participantNodes, includeIdentity
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDeviceAndWrap(plaintext []byte, to types.JID, bundle *prekey.Bundle) (*waBinary.Node, bool, error) {
|
||||
node, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, to, bundle)
|
||||
func (cli *Client) encryptMessageForDeviceAndWrap(plaintext []byte, to types.JID, bundle *prekey.Bundle, encAttrs waBinary.Attrs) (*waBinary.Node, bool, error) {
|
||||
node, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, to, bundle, encAttrs)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -686,7 +892,13 @@ func (cli *Client) encryptMessageForDeviceAndWrap(plaintext []byte, to types.JID
|
||||
}, includeDeviceIdentity, nil
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDevice(plaintext []byte, to types.JID, bundle *prekey.Bundle) (*waBinary.Node, bool, error) {
|
||||
func copyAttrs(from, to waBinary.Attrs) {
|
||||
for k, v := range from {
|
||||
to[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDevice(plaintext []byte, to types.JID, bundle *prekey.Bundle, extraAttrs waBinary.Attrs) (*waBinary.Node, bool, error) {
|
||||
builder := session.NewBuilderFromSignal(cli.Store, to.SignalAddress(), pbSerializer)
|
||||
if bundle != nil {
|
||||
cli.Log.Debugf("Processing prekey bundle for %s", to)
|
||||
@@ -708,17 +920,18 @@ func (cli *Client) encryptMessageForDevice(plaintext []byte, to types.JID, bundl
|
||||
return nil, false, fmt.Errorf("cipher encryption failed: %w", err)
|
||||
}
|
||||
|
||||
encType := "msg"
|
||||
if ciphertext.Type() == protocol.PREKEY_TYPE {
|
||||
encType = "pkmsg"
|
||||
encAttrs := waBinary.Attrs{
|
||||
"v": "2",
|
||||
"type": "msg",
|
||||
}
|
||||
if ciphertext.Type() == protocol.PREKEY_TYPE {
|
||||
encAttrs["type"] = "pkmsg"
|
||||
}
|
||||
copyAttrs(extraAttrs, encAttrs)
|
||||
|
||||
return &waBinary.Node{
|
||||
Tag: "enc",
|
||||
Attrs: waBinary.Attrs{
|
||||
"v": "2",
|
||||
"type": encType,
|
||||
},
|
||||
Tag: "enc",
|
||||
Attrs: encAttrs,
|
||||
Content: ciphertext.Serialize(),
|
||||
}, encType == "pkmsg", nil
|
||||
}, encAttrs["type"] == "pkmsg", nil
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (vc WAVersionContainer) ProtoAppVersion() *waProto.ClientPayload_UserAgent_
|
||||
}
|
||||
|
||||
// waVersion is the WhatsApp web client version
|
||||
var waVersion = WAVersionContainer{2, 2310, 5}
|
||||
var waVersion = WAVersionContainer{2, 2332, 15}
|
||||
|
||||
// waVersionHash is the md5 hash of a dot-separated waVersion
|
||||
var waVersionHash [16]byte
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// Container is a wrapper for a SQL database that can contain multiple whatsmeow sessions.
|
||||
@@ -65,7 +65,12 @@ func New(dialect, address string, log waLog.Logger) (*Container, error) {
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// container, err := sqlstore.NewWithDB(db, "sqlite3", nil)
|
||||
// container := sqlstore.NewWithDB(db, "sqlite3", nil)
|
||||
//
|
||||
// This method does not call Upgrade automatically like New does, so you must call it yourself:
|
||||
//
|
||||
// container := sqlstore.NewWithDB(...)
|
||||
// err := container.Upgrade()
|
||||
func NewWithDB(db *sql.DB, dialect string, log waLog.Logger) *Container {
|
||||
if log == nil {
|
||||
log = waLog.Noop
|
||||
@@ -205,11 +210,7 @@ func (c *Container) NewDevice() *store.Device {
|
||||
NoiseKey: keys.NewKeyPair(),
|
||||
IdentityKey: keys.NewKeyPair(),
|
||||
RegistrationID: mathRand.Uint32(),
|
||||
AdvSecretKey: make([]byte, 32),
|
||||
}
|
||||
_, err := rand.Read(device.AdvSecretKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
AdvSecretKey: randbytes.Make(32),
|
||||
}
|
||||
device.SignedPreKey = device.IdentityKey.CreateSignedPreKey(1)
|
||||
return device
|
||||
|
||||
@@ -284,7 +284,8 @@ const (
|
||||
SET key_data=excluded.key_data, timestamp=excluded.timestamp, fingerprint=excluded.fingerprint
|
||||
WHERE excluded.timestamp > whatsmeow_app_state_sync_keys.timestamp
|
||||
`
|
||||
getAppStateSyncKeyQuery = `SELECT key_data, timestamp, fingerprint FROM whatsmeow_app_state_sync_keys WHERE jid=$1 AND key_id=$2`
|
||||
getAppStateSyncKeyQuery = `SELECT key_data, timestamp, fingerprint FROM whatsmeow_app_state_sync_keys WHERE jid=$1 AND key_id=$2`
|
||||
getLatestAppStateSyncKeyIDQuery = `SELECT key_id FROM whatsmeow_app_state_sync_keys WHERE jid=$1 ORDER BY timestamp DESC LIMIT 1`
|
||||
)
|
||||
|
||||
func (s *SQLStore) PutAppStateSyncKey(id []byte, key store.AppStateSyncKey) error {
|
||||
@@ -301,6 +302,15 @@ func (s *SQLStore) GetAppStateSyncKey(id []byte) (*store.AppStateSyncKey, error)
|
||||
return &key, err
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetLatestAppStateSyncKeyID() ([]byte, error) {
|
||||
var keyID []byte
|
||||
err := s.db.QueryRow(getLatestAppStateSyncKeyIDQuery, s.JID).Scan(&keyID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return keyID, err
|
||||
}
|
||||
|
||||
const (
|
||||
putAppStateVersionQuery = `
|
||||
INSERT INTO whatsmeow_app_state_version (jid, name, version, hash) VALUES ($1, $2, $3, $4)
|
||||
|
||||
1
vendor/go.mau.fi/whatsmeow/store/store.go
vendored
1
vendor/go.mau.fi/whatsmeow/store/store.go
vendored
@@ -55,6 +55,7 @@ type AppStateSyncKey struct {
|
||||
type AppStateSyncKeyStore interface {
|
||||
PutAppStateSyncKey(id []byte, key AppStateSyncKey) error
|
||||
GetAppStateSyncKey(id []byte) (*AppStateSyncKey, error)
|
||||
GetLatestAppStateSyncKeyID() ([]byte, error)
|
||||
}
|
||||
|
||||
type AppStateMutationMAC struct {
|
||||
|
||||
@@ -19,7 +19,8 @@ type Contact struct {
|
||||
JID types.JID // The contact who was modified.
|
||||
Timestamp time.Time // The time when the modification happened.'
|
||||
|
||||
Action *waProto.ContactAction // The new contact info.
|
||||
Action *waProto.ContactAction // The new contact info.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// PushName is emitted when a message is received with a different push name than the previous value cached for the same user.
|
||||
@@ -43,7 +44,8 @@ type Pin struct {
|
||||
JID types.JID // The chat which was pinned or unpinned.
|
||||
Timestamp time.Time // The time when the (un)pinning happened.
|
||||
|
||||
Action *waProto.PinAction // Whether the chat is now pinned or not.
|
||||
Action *waProto.PinAction // Whether the chat is now pinned or not.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// Star is emitted when a message is starred or unstarred from another device.
|
||||
@@ -54,7 +56,8 @@ type Star struct {
|
||||
MessageID string // The message which was starred or unstarred.
|
||||
Timestamp time.Time // The time when the (un)starring happened.
|
||||
|
||||
Action *waProto.StarAction // Whether the message is now starred or not.
|
||||
Action *waProto.StarAction // Whether the message is now starred or not.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// DeleteForMe is emitted when a message is deleted (for the current user only) from another device.
|
||||
@@ -65,7 +68,8 @@ type DeleteForMe struct {
|
||||
MessageID string // The message which was deleted.
|
||||
Timestamp time.Time // The time when the deletion happened.
|
||||
|
||||
Action *waProto.DeleteMessageForMeAction // Additional information for the deletion.
|
||||
Action *waProto.DeleteMessageForMeAction // Additional information for the deletion.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// Mute is emitted when a chat is muted or unmuted from another device.
|
||||
@@ -73,7 +77,8 @@ type Mute struct {
|
||||
JID types.JID // The chat which was muted or unmuted.
|
||||
Timestamp time.Time // The time when the (un)muting happened.
|
||||
|
||||
Action *waProto.MuteAction // The current mute status of the chat.
|
||||
Action *waProto.MuteAction // The current mute status of the chat.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// Archive is emitted when a chat is archived or unarchived from another device.
|
||||
@@ -81,7 +86,8 @@ type Archive struct {
|
||||
JID types.JID // The chat which was archived or unarchived.
|
||||
Timestamp time.Time // The time when the (un)archiving happened.
|
||||
|
||||
Action *waProto.ArchiveChatAction // The current archival status of the chat.
|
||||
Action *waProto.ArchiveChatAction // The current archival status of the chat.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// MarkChatAsRead is emitted when a whole chat is marked as read or unread from another device.
|
||||
@@ -89,7 +95,17 @@ type MarkChatAsRead struct {
|
||||
JID types.JID // The chat which was marked as read or unread.
|
||||
Timestamp time.Time // The time when the marking happened.
|
||||
|
||||
Action *waProto.MarkChatAsReadAction // Whether the chat was marked as read or unread, and info about the most recent messages.
|
||||
Action *waProto.MarkChatAsReadAction // Whether the chat was marked as read or unread, and info about the most recent messages.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// ClearChat is emitted when a chat is cleared on another device. This is different from DeleteChat.
|
||||
type ClearChat struct {
|
||||
JID types.JID // The chat which was cleared.
|
||||
Timestamp time.Time // The time when the clear happened.
|
||||
|
||||
Action *waProto.ClearChatAction // Information about the clear.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// DeleteChat is emitted when a chat is deleted on another device.
|
||||
@@ -97,21 +113,33 @@ type DeleteChat struct {
|
||||
JID types.JID // The chat which was deleted.
|
||||
Timestamp time.Time // The time when the deletion happened.
|
||||
|
||||
Action *waProto.DeleteChatAction // Information about the deletion.
|
||||
Action *waProto.DeleteChatAction // Information about the deletion.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// PushNameSetting is emitted when the user's push name is changed from another device.
|
||||
type PushNameSetting struct {
|
||||
Timestamp time.Time // The time when the push name was changed.
|
||||
|
||||
Action *waProto.PushNameSetting // The new push name for the user.
|
||||
Action *waProto.PushNameSetting // The new push name for the user.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// UnarchiveChatsSetting is emitted when the user changes the "Keep chats archived" setting from another device.
|
||||
type UnarchiveChatsSetting struct {
|
||||
Timestamp time.Time // The time when the setting was changed.
|
||||
|
||||
Action *waProto.UnarchiveChatsSetting // The new settings.
|
||||
Action *waProto.UnarchiveChatsSetting // The new settings.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// UserStatusMute is emitted when the user mutes or unmutes another user's status updates.
|
||||
type UserStatusMute struct {
|
||||
JID types.JID // The user who was muted or unmuted
|
||||
Timestamp time.Time // The timestamp when the action happened
|
||||
|
||||
Action *waProto.UserStatusMuteAction // The new mute status
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// AppState is emitted directly for new data received from app state syncing.
|
||||
|
||||
@@ -176,8 +176,9 @@ func (cfr ConnectFailureReason) String() string {
|
||||
//
|
||||
// Known reasons are handled internally and emitted as different events (e.g. LoggedOut and TemporaryBan).
|
||||
type ConnectFailure struct {
|
||||
Reason ConnectFailureReason
|
||||
Raw *waBinary.Node
|
||||
Reason ConnectFailureReason
|
||||
Message string
|
||||
Raw *waBinary.Node
|
||||
}
|
||||
|
||||
// ClientOutdated is emitted when the WhatsApp server rejects the connection with the ConnectFailureClientOutdated code.
|
||||
@@ -199,6 +200,13 @@ type HistorySync struct {
|
||||
Data *waProto.HistorySync
|
||||
}
|
||||
|
||||
type DecryptFailMode string
|
||||
|
||||
const (
|
||||
DecryptFailShow DecryptFailMode = ""
|
||||
DecryptFailHide DecryptFailMode = "hide"
|
||||
)
|
||||
|
||||
// UndecryptableMessage is emitted when receiving a new message that failed to decrypt.
|
||||
//
|
||||
// The library will automatically ask the sender to retry. If the sender resends the message,
|
||||
@@ -211,6 +219,8 @@ type UndecryptableMessage struct {
|
||||
// IsUnavailable is true if the recipient device didn't send a ciphertext to this device at all
|
||||
// (as opposed to sending a ciphertext, but the ciphertext not being decryptable).
|
||||
IsUnavailable bool
|
||||
|
||||
DecryptFailMode DecryptFailMode
|
||||
}
|
||||
|
||||
// Message is emitted when receiving a new message.
|
||||
@@ -224,6 +234,13 @@ type Message struct {
|
||||
IsDocumentWithCaption bool // True if the message was unwrapped from a DocumentWithCaptionMessage
|
||||
IsEdit bool // True if the message was unwrapped from an EditedMessage
|
||||
|
||||
// If this event was parsed from a WebMessageInfo (i.e. from a history sync or unavailable message request), the source data is here.
|
||||
SourceWebMsg *waProto.WebMessageInfo
|
||||
// If this event is a response to an unavailable message request, the request ID is here.
|
||||
UnavailableRequestID types.MessageID
|
||||
// If the message was re-requested from the sender, this is the number of retries it took.
|
||||
RetryCount int
|
||||
|
||||
// The raw message struct. This is the raw unmodified data, which means the actual message might
|
||||
// be wrapped in DeviceSentMessage, EphemeralMessage or ViewOnceMessage.
|
||||
RawMessage *waProto.Message
|
||||
|
||||
8
vendor/go.mau.fi/whatsmeow/upload.go
vendored
8
vendor/go.mau.fi/whatsmeow/upload.go
vendored
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
|
||||
"go.mau.fi/whatsmeow/socket"
|
||||
"go.mau.fi/whatsmeow/util/cbcutil"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// UploadResponse contains the data from the attachment upload, which can be put into a message to send the attachment.
|
||||
@@ -62,11 +62,7 @@ type UploadResponse struct {
|
||||
// The same applies to the other message types like DocumentMessage, just replace the struct type and Message field name.
|
||||
func (cli *Client) Upload(ctx context.Context, plaintext []byte, appInfo MediaType) (resp UploadResponse, err error) {
|
||||
resp.FileLength = uint64(len(plaintext))
|
||||
resp.MediaKey = make([]byte, 32)
|
||||
_, err = rand.Read(resp.MediaKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.MediaKey = randbytes.Make(32)
|
||||
|
||||
plaintextSHA256 := sha256.Sum256(plaintext)
|
||||
resp.FileSHA256 = plaintextSHA256[:]
|
||||
|
||||
12
vendor/go.mau.fi/whatsmeow/util/keys/keypair.go
vendored
12
vendor/go.mau.fi/whatsmeow/util/keys/keypair.go
vendored
@@ -8,11 +8,10 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"go.mau.fi/libsignal/ecc"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
type KeyPair struct {
|
||||
@@ -32,12 +31,7 @@ func NewKeyPairFromPrivateKey(priv [32]byte) *KeyPair {
|
||||
}
|
||||
|
||||
func NewKeyPair() *KeyPair {
|
||||
var priv [32]byte
|
||||
|
||||
_, err := rand.Read(priv[:])
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to generate curve25519 private key: %w", err))
|
||||
}
|
||||
priv := *(*[32]byte)(randbytes.Make(32))
|
||||
|
||||
priv[0] &= 248
|
||||
priv[31] &= 127
|
||||
|
||||
15
vendor/go.mau.fi/whatsmeow/util/randbytes/randbytes.go
vendored
Normal file
15
vendor/go.mau.fi/whatsmeow/util/randbytes/randbytes.go
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package randbytes
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Make(length int) []byte {
|
||||
random := make([]byte, length)
|
||||
_, err := rand.Read(random)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to get random bytes: %w", err))
|
||||
}
|
||||
return random
|
||||
}
|
||||
Reference in New Issue
Block a user