summaryrefslogtreecommitdiffstats
path: root/vendor/go.mau.fi/whatsmeow
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow')
-rw-r--r--vendor/go.mau.fi/whatsmeow/README.md9
-rw-r--r--vendor/go.mau.fi/whatsmeow/appstate.go44
-rw-r--r--vendor/go.mau.fi/whatsmeow/appstate/keys.go33
-rw-r--r--vendor/go.mau.fi/whatsmeow/binary/attrs.go21
-rw-r--r--vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go212
-rw-r--r--vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.rawbin61194 -> 61356 bytes
-rw-r--r--vendor/go.mau.fi/whatsmeow/binary/proto/def.proto28
-rw-r--r--vendor/go.mau.fi/whatsmeow/broadcast.go138
-rw-r--r--vendor/go.mau.fi/whatsmeow/call.go4
-rw-r--r--vendor/go.mau.fi/whatsmeow/client.go118
-rw-r--r--vendor/go.mau.fi/whatsmeow/connectionevents.go7
-rw-r--r--vendor/go.mau.fi/whatsmeow/download.go12
-rw-r--r--vendor/go.mau.fi/whatsmeow/errors.go5
-rw-r--r--vendor/go.mau.fi/whatsmeow/group.go11
-rw-r--r--vendor/go.mau.fi/whatsmeow/internals.go8
-rw-r--r--vendor/go.mau.fi/whatsmeow/keepalive.go28
-rw-r--r--vendor/go.mau.fi/whatsmeow/mediaretry.go36
-rw-r--r--vendor/go.mau.fi/whatsmeow/message.go104
-rw-r--r--vendor/go.mau.fi/whatsmeow/notification.go5
-rw-r--r--vendor/go.mau.fi/whatsmeow/pair.go4
-rw-r--r--vendor/go.mau.fi/whatsmeow/presence.go3
-rw-r--r--vendor/go.mau.fi/whatsmeow/receipt.go2
-rw-r--r--vendor/go.mau.fi/whatsmeow/request.go79
-rw-r--r--vendor/go.mau.fi/whatsmeow/retry.go59
-rw-r--r--vendor/go.mau.fi/whatsmeow/send.go240
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/clientpayload.go35
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go10
-rw-r--r--vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go36
-rw-r--r--vendor/go.mau.fi/whatsmeow/types/events/events.go37
-rw-r--r--vendor/go.mau.fi/whatsmeow/types/user.go22
-rw-r--r--vendor/go.mau.fi/whatsmeow/user.go13
31 files changed, 1021 insertions, 342 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/README.md b/vendor/go.mau.fi/whatsmeow/README.md
index bcd65fa2..123239b5 100644
--- a/vendor/go.mau.fi/whatsmeow/README.md
+++ b/vendor/go.mau.fi/whatsmeow/README.md
@@ -6,6 +6,12 @@ whatsmeow is a Go library for the WhatsApp web multidevice API.
## Discussion
Matrix room: [#whatsmeow:maunium.net](https://matrix.to/#/#whatsmeow:maunium.net)
+For questions about the WhatsApp protocol (like how to send a specific type of
+message), you can also use the [WhatsApp protocol Q&A] section on GitHub
+discussions.
+
+[WhatsApp protocol Q&A]: https://github.com/tulir/whatsmeow/discussions/categories/whatsapp-protocol-q-a
+
## Usage
The [godoc](https://pkg.go.dev/go.mau.fi/whatsmeow) includes docs for all methods and event types.
There's also a [simple example](https://godocs.io/go.mau.fi/whatsmeow#example-package) at the top.
@@ -23,9 +29,10 @@ Most core features are already present:
* Sending and receiving delivery and read receipts
* Reading 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 status messages or broadcast list messages (this is not supported on WhatsApp web either)
+* Sending broadcast list messages (this is not supported on WhatsApp web either)
* Calls
diff --git a/vendor/go.mau.fi/whatsmeow/appstate.go b/vendor/go.mau.fi/whatsmeow/appstate.go
index b9b99f26..ad97bdc2 100644
--- a/vendor/go.mau.fi/whatsmeow/appstate.go
+++ b/vendor/go.mau.fi/whatsmeow/appstate.go
@@ -7,6 +7,8 @@
package whatsmeow
import (
+ "encoding/hex"
+ "errors"
"fmt"
"time"
@@ -53,6 +55,9 @@ func (cli *Client) FetchAppState(name appstate.WAPatchName, fullSync, onlyIfNotS
mutations, newState, err := cli.appStateProc.DecodePatches(patches, state, true)
if err != nil {
+ if errors.Is(err, appstate.ErrKeyNotFound) {
+ go cli.requestMissingAppStateKeys(patches)
+ }
return fmt.Errorf("failed to decode app state %s patches: %w", name, err)
}
wasFullSync := state.Version == 0 && patches.Snapshot != nil
@@ -228,3 +233,42 @@ func (cli *Client) fetchAppStatePatches(name appstate.WAPatchName, fromVersion u
}
return appstate.ParsePatchList(resp, cli.downloadExternalAppStateBlob)
}
+
+func (cli *Client) requestMissingAppStateKeys(patches *appstate.PatchList) {
+ cli.appStateKeyRequestsLock.Lock()
+ rawKeyIDs := cli.appStateProc.GetMissingKeyIDs(patches)
+ filteredKeyIDs := make([][]byte, 0, len(rawKeyIDs))
+ now := time.Now()
+ for _, keyID := range rawKeyIDs {
+ stringKeyID := hex.EncodeToString(keyID)
+ lastRequestTime := cli.appStateKeyRequests[stringKeyID]
+ if lastRequestTime.IsZero() || lastRequestTime.Add(24*time.Hour).Before(now) {
+ cli.appStateKeyRequests[stringKeyID] = now
+ filteredKeyIDs = append(filteredKeyIDs, keyID)
+ }
+ }
+ cli.appStateKeyRequestsLock.Unlock()
+ cli.requestAppStateKeys(filteredKeyIDs)
+}
+
+func (cli *Client) requestAppStateKeys(rawKeyIDs [][]byte) {
+ keyIDs := make([]*waProto.AppStateSyncKeyId, len(rawKeyIDs))
+ debugKeyIDs := make([]string, len(rawKeyIDs))
+ for i, keyID := range rawKeyIDs {
+ keyIDs[i] = &waProto.AppStateSyncKeyId{KeyId: keyID}
+ debugKeyIDs[i] = hex.EncodeToString(keyID)
+ }
+ msg := &waProto.Message{
+ ProtocolMessage: &waProto.ProtocolMessage{
+ Type: waProto.ProtocolMessage_APP_STATE_SYNC_KEY_REQUEST.Enum(),
+ AppStateSyncKeyRequest: &waProto.AppStateSyncKeyRequest{
+ KeyIds: keyIDs,
+ },
+ },
+ }
+ cli.Log.Infof("Sending key request for app state keys %+v", debugKeyIDs)
+ _, err := cli.SendMessage(cli.Store.ID.ToNonAD(), "", msg)
+ if err != nil {
+ cli.Log.Warnf("Failed to send app state key request: %v", err)
+ }
+}
diff --git a/vendor/go.mau.fi/whatsmeow/appstate/keys.go b/vendor/go.mau.fi/whatsmeow/appstate/keys.go
index 30ecbcc4..ec19dc26 100644
--- a/vendor/go.mau.fi/whatsmeow/appstate/keys.go
+++ b/vendor/go.mau.fi/whatsmeow/appstate/keys.go
@@ -83,3 +83,36 @@ func (proc *Processor) getAppStateKey(keyID []byte) (keys ExpandedAppStateKeys,
}
return
}
+
+func (proc *Processor) GetMissingKeyIDs(pl *PatchList) [][]byte {
+ cache := make(map[string]bool)
+ var missingKeys [][]byte
+ checkMissing := func(keyID []byte) {
+ if keyID == nil {
+ return
+ }
+ stringKeyID := base64.RawStdEncoding.EncodeToString(keyID)
+ _, alreadyAdded := cache[stringKeyID]
+ if !alreadyAdded {
+ keyData, err := proc.Store.AppStateKeys.GetAppStateSyncKey(keyID)
+ if err != nil {
+ proc.Log.Warnf("Error fetching key %X while checking if it's missing: %v", keyID, err)
+ }
+ missing := keyData == nil && err == nil
+ cache[stringKeyID] = missing
+ if missing {
+ missingKeys = append(missingKeys, keyID)
+ }
+ }
+ }
+ if pl.Snapshot != nil {
+ checkMissing(pl.Snapshot.GetKeyId().GetId())
+ for _, record := range pl.Snapshot.GetRecords() {
+ checkMissing(record.GetKeyId().GetId())
+ }
+ }
+ for _, patch := range pl.Patches {
+ checkMissing(patch.GetKeyId().GetId())
+ }
+ return missingKeys
+}
diff --git a/vendor/go.mau.fi/whatsmeow/binary/attrs.go b/vendor/go.mau.fi/whatsmeow/binary/attrs.go
index 35acd396..d7d43f0a 100644
--- a/vendor/go.mau.fi/whatsmeow/binary/attrs.go
+++ b/vendor/go.mau.fi/whatsmeow/binary/attrs.go
@@ -9,6 +9,7 @@ package binary
import (
"fmt"
"strconv"
+ "time"
"go.mau.fi/whatsmeow/types"
)
@@ -112,6 +113,16 @@ func (au *AttrUtility) GetBool(key string, require bool) (bool, bool) {
}
}
+func (au *AttrUtility) GetUnixTime(key string, require bool) (time.Time, bool) {
+ if intVal, ok := au.GetInt64(key, require); !ok {
+ return time.Time{}, false
+ } else if intVal == 0 {
+ return time.Time{}, true
+ } else {
+ return time.Unix(intVal, 0), true
+ }
+}
+
// OptionalString returns the string under the given key.
func (au *AttrUtility) OptionalString(key string) string {
strVal, _ := au.GetString(key, false)
@@ -155,6 +166,16 @@ func (au *AttrUtility) Bool(key string) bool {
return val
}
+func (au *AttrUtility) OptionalUnixTime(key string) time.Time {
+ val, _ := au.GetUnixTime(key, false)
+ return val
+}
+
+func (au *AttrUtility) UnixTime(key string) time.Time {
+ val, _ := au.GetUnixTime(key, true)
+ return val
+}
+
// OK returns true if there are no errors.
func (au *AttrUtility) OK() bool {
return len(au.Errors) == 0
diff --git a/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go b/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
index b32ecc60..5b91ccbc 100644
--- a/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
+++ b/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
@@ -2739,7 +2739,7 @@ func (x *DNSSource_DNSSourceDNSResolutionMethod) UnmarshalJSON(b []byte) error {
// Deprecated: Use DNSSource_DNSSourceDNSResolutionMethod.Descriptor instead.
func (DNSSource_DNSSourceDNSResolutionMethod) EnumDescriptor() ([]byte, []int) {
- return file_binary_proto_def_proto_rawDescGZIP(), []int{182, 0}
+ return file_binary_proto_def_proto_rawDescGZIP(), []int{183, 0}
}
type WebMessageInfo_WebMessageInfoStatus int32
@@ -10131,7 +10131,6 @@ type ContextInfo struct {
ActionLink *ActionLink `protobuf:"bytes,33,opt,name=actionLink" json:"actionLink,omitempty"`
GroupSubject *string `protobuf:"bytes,34,opt,name=groupSubject" json:"groupSubject,omitempty"`
ParentGroupJid *string `protobuf:"bytes,35,opt,name=parentGroupJid" json:"parentGroupJid,omitempty"`
- MessageSecret []byte `protobuf:"bytes,36,opt,name=messageSecret" json:"messageSecret,omitempty"`
}
func (x *ContextInfo) Reset() {
@@ -10327,13 +10326,6 @@ func (x *ContextInfo) GetParentGroupJid() string {
return ""
}
-func (x *ContextInfo) GetMessageSecret() []byte {
- if x != nil {
- return x.MessageSecret
- }
- return nil
-}
-
type ExternalAdReplyInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -11577,6 +11569,7 @@ type MessageContextInfo struct {
DeviceListMetadata *DeviceListMetadata `protobuf:"bytes,1,opt,name=deviceListMetadata" json:"deviceListMetadata,omitempty"`
DeviceListMetadataVersion *int32 `protobuf:"varint,2,opt,name=deviceListMetadataVersion" json:"deviceListMetadataVersion,omitempty"`
+ MessageSecret []byte `protobuf:"bytes,3,opt,name=messageSecret" json:"messageSecret,omitempty"`
}
func (x *MessageContextInfo) Reset() {
@@ -11625,6 +11618,13 @@ func (x *MessageContextInfo) GetDeviceListMetadataVersion() int32 {
return 0
}
+func (x *MessageContextInfo) GetMessageSecret() []byte {
+ if x != nil {
+ return x.MessageSecret
+ }
+ return nil
+}
+
type VideoMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -13220,6 +13220,8 @@ type GlobalSettings struct {
AutoDownloadRoaming *AutoDownloadSettings `protobuf:"bytes,6,opt,name=autoDownloadRoaming" json:"autoDownloadRoaming,omitempty"`
ShowIndividualNotificationsPreview *bool `protobuf:"varint,7,opt,name=showIndividualNotificationsPreview" json:"showIndividualNotificationsPreview,omitempty"`
ShowGroupNotificationsPreview *bool `protobuf:"varint,8,opt,name=showGroupNotificationsPreview" json:"showGroupNotificationsPreview,omitempty"`
+ DisappearingModeDuration *int32 `protobuf:"varint,9,opt,name=disappearingModeDuration" json:"disappearingModeDuration,omitempty"`
+ DisappearingModeTimestamp *int64 `protobuf:"varint,10,opt,name=disappearingModeTimestamp" json:"disappearingModeTimestamp,omitempty"`
}
func (x *GlobalSettings) Reset() {
@@ -13310,6 +13312,20 @@ func (x *GlobalSettings) GetShowGroupNotificationsPreview() bool {
return false
}
+func (x *GlobalSettings) GetDisappearingModeDuration() int32 {
+ if x != nil && x.DisappearingModeDuration != nil {
+ return *x.DisappearingModeDuration
+ }
+ return 0
+}
+
+func (x *GlobalSettings) GetDisappearingModeTimestamp() int64 {
+ if x != nil && x.DisappearingModeTimestamp != nil {
+ return *x.DisappearingModeTimestamp
+ }
+ return 0
+}
+
type Conversation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -17690,7 +17706,7 @@ type ClientPayload struct {
DnsSource *DNSSource `protobuf:"bytes,15,opt,name=dnsSource" json:"dnsSource,omitempty"`
ConnectAttemptCount *uint32 `protobuf:"varint,16,opt,name=connectAttemptCount" json:"connectAttemptCount,omitempty"`
Device *uint32 `protobuf:"varint,18,opt,name=device" json:"device,omitempty"`
- RegData *CompanionRegData `protobuf:"bytes,19,opt,name=regData" json:"regData,omitempty"`
+ DevicePairingData *DevicePairingRegistrationData `protobuf:"bytes,19,opt,name=devicePairingData" json:"devicePairingData,omitempty"`
Product *ClientPayload_ClientPayloadProduct `protobuf:"varint,20,opt,name=product,enum=proto.ClientPayload_ClientPayloadProduct" json:"product,omitempty"`
FbCat []byte `protobuf:"bytes,21,opt,name=fbCat" json:"fbCat,omitempty"`
FbUserAgent []byte `protobuf:"bytes,22,opt,name=fbUserAgent" json:"fbUserAgent,omitempty"`
@@ -17825,9 +17841,9 @@ func (x *ClientPayload) GetDevice() uint32 {
return 0
}
-func (x *ClientPayload) GetRegData() *CompanionRegData {
+func (x *ClientPayload) GetDevicePairingData() *DevicePairingRegistrationData {
if x != nil {
- return x.RegData
+ return x.DevicePairingData
}
return nil
}
@@ -18236,17 +18252,23 @@ func (x *UserAgent) GetDeviceBoard() string {
return ""
}
-type DNSSource struct {
+type DevicePairingRegistrationData struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- DnsMethod *DNSSource_DNSSourceDNSResolutionMethod `protobuf:"varint,15,opt,name=dnsMethod,enum=proto.DNSSource_DNSSourceDNSResolutionMethod" json:"dnsMethod,omitempty"`
- AppCached *bool `protobuf:"varint,16,opt,name=appCached" json:"appCached,omitempty"`
+ ERegid []byte `protobuf:"bytes,1,opt,name=eRegid" json:"eRegid,omitempty"`
+ EKeytype []byte `protobuf:"bytes,2,opt,name=eKeytype" json:"eKeytype,omitempty"`
+ EIdent []byte `protobuf:"bytes,3,opt,name=eIdent" json:"eIdent,omitempty"`
+ ESkeyId []byte `protobuf:"bytes,4,opt,name=eSkeyId" json:"eSkeyId,omitempty"`
+ ESkeyVal []byte `protobuf:"bytes,5,opt,name=eSkeyVal" json:"eSkeyVal,omitempty"`
+ ESkeySig []byte `protobuf:"bytes,6,opt,name=eSkeySig" json:"eSkeySig,omitempty"`
+ BuildHash []byte `protobuf:"bytes,7,opt,name=buildHash" json:"buildHash,omitempty"`
+ DeviceProps []byte `protobuf:"bytes,8,opt,name=deviceProps" json:"deviceProps,omitempty"`
}
-func (x *DNSSource) Reset() {
- *x = DNSSource{}
+func (x *DevicePairingRegistrationData) Reset() {
+ *x = DevicePairingRegistrationData{}
if protoimpl.UnsafeEnabled {
mi := &file_binary_proto_def_proto_msgTypes[182]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -18254,13 +18276,13 @@ func (x *DNSSource) Reset() {
}
}
-func (x *DNSSource) String() string {
+func (x *DevicePairingRegistrationData) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*DNSSource) ProtoMessage() {}
+func (*DevicePairingRegistrationData) ProtoMessage() {}
-func (x *DNSSource) ProtoReflect() protoreflect.Message {
+func (x *DevicePairingRegistrationData) ProtoReflect() protoreflect.Message {
mi := &file_binary_proto_def_proto_msgTypes[182]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -18272,128 +18294,122 @@ func (x *DNSSource) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use DNSSource.ProtoReflect.Descriptor instead.
-func (*DNSSource) Descriptor() ([]byte, []int) {
+// Deprecated: Use DevicePairingRegistrationData.ProtoReflect.Descriptor instead.
+func (*DevicePairingRegistrationData) Descriptor() ([]byte, []int) {
return file_binary_proto_def_proto_rawDescGZIP(), []int{182}
}
-func (x *DNSSource) GetDnsMethod() DNSSource_DNSSourceDNSResolutionMethod {
- if x != nil && x.DnsMethod != nil {
- return *x.DnsMethod
- }
- return DNSSource_SYSTEM
-}
-
-func (x *DNSSource) GetAppCached() bool {
- if x != nil && x.AppCached != nil {
- return *x.AppCached
- }
- return false
-}
-
-type CompanionRegData struct {
- state protoimpl.MessageState
- sizeCache protoimpl.SizeCache
- unknownFields protoimpl.UnknownFields
-
- ERegid []byte `protobuf:"bytes,1,opt,name=eRegid" json:"eRegid,omitempty"`
- EKeytype []byte `protobuf:"bytes,2,opt,name=eKeytype" json:"eKeytype,omitempty"`
- EIdent []byte `protobuf:"bytes,3,opt,name=eIdent" json:"eIdent,omitempty"`
- ESkeyId []byte `protobuf:"bytes,4,opt,name=eSkeyId" json:"eSkeyId,omitempty"`
- ESkeyVal []byte `protobuf:"bytes,5,opt,name=eSkeyVal" json:"eSkeyVal,omitempty"`
- ESkeySig []byte `protobuf:"bytes,6,opt,name=eSkeySig" json:"eSkeySig,omitempty"`
- BuildHash []byte `protobuf:"bytes,7,opt,name=buildHash" json:"buildHash,omitempty"`
- CompanionProps []byte `protobuf:"bytes,8,opt,name=companionProps" json:"companionProps,omitempty"`
-}
-
-func (x *CompanionRegData) Reset() {
- *x = CompanionRegData{}
- if protoimpl.UnsafeEnabled {
- mi := &file_binary_proto_def_proto_msgTypes[183]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
- }
-}
-
-func (x *CompanionRegData) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CompanionRegData) ProtoMessage() {}
-
-func (x *CompanionRegData) ProtoReflect() protoreflect.Message {
- mi := &file_binary_proto_def_proto_msgTypes[183]
- if protoimpl.UnsafeEnabled && x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use CompanionRegData.ProtoReflect.Descriptor instead.
-func (*CompanionRegData) Descriptor() ([]byte, []int) {
- return file_binary_proto_def_proto_rawDescGZIP(), []int{183}
-}
-
-func (x *CompanionRegData) GetERegid() []byte {
+func (x *DevicePairingRegistrationData) GetERegid() []byte {
if x != nil {
return x.ERegid
}
return nil
}
-func (x *CompanionRegData) GetEKeytype() []byte {
+func (x *DevicePairingRegistrationData) GetEKeytype() []byte {
if x != nil {
return x.EKeytype
}
return nil
}
-func (x *CompanionRegData) GetEIdent() []byte {
+func (x *DevicePairingRegistrationData) GetEIdent() []byte {
if x != nil {
return x.EIdent
}
return nil
}
-func (x *CompanionRegData) GetESkeyId() []byte {
+func (x *DevicePairingRegistrationData) GetESkeyId() []byte {
if x != nil {
return x.ESkeyId
}
return nil
}
-func (x *CompanionRegData) GetESkeyVal() []byte {
+func (x *DevicePairingRegistrationData) GetESkeyVal() []byte {
if x != nil {
return x.ESkeyVal
}
return nil
}
-func (x *CompanionRegData) GetESkeySig() []byte {
+func (x *DevicePairingRegistrationData) GetESkeySig() []byte {
if x != nil {
return x.ESkeySig
}
return nil
}
-func (x *CompanionRegData) GetBuildHash() []byte {
+func (x *DevicePairingRegistrationData) GetBuildHash() []byte {
if x != nil {
return x.BuildHash
}
return nil
}
-func (x *CompanionRegData) GetCompanionProps() []byte {
+func (x *DevicePairingRegistrationData) GetDeviceProps() []byte {
if x != nil {
- return x.CompanionProps
+ return x.DeviceProps
}
return nil
}
+type DNSSource struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ DnsMethod *DNSSource_DNSSourceDNSResolutionMethod `protobuf:"varint,15,opt,name=dnsMethod,enum=proto.DNSSource_DNSSourceDNSResolutionMethod" json:"dnsMethod,omitempty"`
+ AppCached *bool `protobuf:"varint,16,opt,name=appCached" json:"appCached,omitempty"`
+}
+
+func (x *DNSSource) Reset() {
+ *x = DNSSource{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_binary_proto_def_proto_msgTypes[183]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DNSSource) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DNSSource) ProtoMessage() {}
+
+func (x *DNSSource) ProtoReflect() protoreflect.Message {
+ mi := &file_binary_proto_def_proto_msgTypes[183]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DNSSource.ProtoReflect.Descriptor instead.
+func (*DNSSource) Descriptor() ([]byte, []int) {
+ return file_binary_proto_def_proto_rawDescGZIP(), []int{183}
+}
+
+func (x *DNSSource) GetDnsMethod() DNSSource_DNSSourceDNSResolutionMethod {
+ if x != nil && x.DnsMethod != nil {
+ return *x.DnsMethod
+ }
+ return DNSSource_SYSTEM
+}
+
+func (x *DNSSource) GetAppCached() bool {
+ if x != nil && x.AppCached != nil {
+ return *x.AppCached
+ }
+ return false
+}
+
type WebNotificationsInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -20122,8 +20138,8 @@ var file_binary_proto_def_proto_goTypes = []interface{}{
(*WebInfo)(nil), // 229: proto.WebInfo
(*WebdPayload)(nil), // 230: proto.WebdPayload
(*UserAgent)(nil), // 231: proto.UserAgent
- (*DNSSource)(nil), // 232: proto.DNSSource
- (*CompanionRegData)(nil), // 233: proto.CompanionRegData
+ (*DevicePairingRegistrationData)(nil), // 232: proto.DevicePairingRegistrationData
+ (*DNSSource)(nil), // 233: proto.DNSSource
(*WebNotificationsInfo)(nil), // 234: proto.WebNotificationsInfo
(*WebMessageInfo)(nil), // 235: proto.WebMessageInfo
(*WebFeatures)(nil), // 236: proto.WebFeatures
@@ -20417,8 +20433,8 @@ var file_binary_proto_def_proto_depIdxs = []int32{
229, // 276: proto.ClientPayload.webInfo:type_name -> proto.WebInfo
35, // 277: proto.ClientPayload.connectType:type_name -> proto.ClientPayload.ClientPayloadConnectType
36, // 278: proto.ClientPayload.connectReason:type_name -> proto.ClientPayload.ClientPayloadConnectReason
- 232, // 279: proto.ClientPayload.dnsSource:type_name -> proto.DNSSource
- 233, // 280: proto.ClientPayload.regData:type_name -> proto.CompanionRegData
+ 233, // 279: proto.ClientPayload.dnsSource:type_name -> proto.DNSSource
+ 232, // 280: proto.ClientPayload.devicePairingData:type_name -> proto.DevicePairingRegistrationData
37, // 281: proto.ClientPayload.product:type_name -> proto.ClientPayload.ClientPayloadProduct
38, // 282: proto.ClientPayload.iosAppExtension:type_name -> proto.ClientPayload.ClientPayloadIOSAppExtension
230, // 283: proto.WebInfo.webdPayload:type_name -> proto.WebdPayload
@@ -22698,7 +22714,7 @@ func file_binary_proto_def_proto_init() {
}
}
file_binary_proto_def_proto_msgTypes[182].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*DNSSource); i {
+ switch v := v.(*DevicePairingRegistrationData); i {
case 0:
return &v.state
case 1:
@@ -22710,7 +22726,7 @@ func file_binary_proto_def_proto_init() {
}
}
file_binary_proto_def_proto_msgTypes[183].Exporter = func(v interface{}, i int) interface{} {
- switch v := v.(*CompanionRegData); i {
+ switch v := v.(*DNSSource); i {
case 0:
return &v.state
case 1:
diff --git a/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw b/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
index d01d964f..9fb85cdd 100644
--- a/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
+++ b/vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
Binary files differ
diff --git a/vendor/go.mau.fi/whatsmeow/binary/proto/def.proto b/vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
index fd15a61e..93531585 100644
--- a/vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
+++ b/vendor/go.mau.fi/whatsmeow/binary/proto/def.proto
@@ -781,7 +781,6 @@ message ContextInfo {
optional ActionLink actionLink = 33;
optional string groupSubject = 34;
optional string parentGroupJid = 35;
- optional bytes messageSecret = 36;
}
message ExternalAdReplyInfo {
@@ -932,6 +931,7 @@ message Message {
message MessageContextInfo {
optional DeviceListMetadata deviceListMetadata = 1;
optional int32 deviceListMetadataVersion = 2;
+ optional bytes messageSecret = 3;
}
message VideoMessage {
@@ -1123,6 +1123,8 @@ message GlobalSettings {
optional AutoDownloadSettings autoDownloadRoaming = 6;
optional bool showIndividualNotificationsPreview = 7;
optional bool showGroupNotificationsPreview = 8;
+ optional int32 disappearingModeDuration = 9;
+ optional int64 disappearingModeTimestamp = 10;
}
message Conversation {
@@ -1633,7 +1635,7 @@ message ClientPayload {
optional DNSSource dnsSource = 15;
optional uint32 connectAttemptCount = 16;
optional uint32 device = 18;
- optional CompanionRegData regData = 19;
+ optional DevicePairingRegistrationData devicePairingData = 19;
enum ClientPayloadProduct {
WHATSAPP = 0;
MESSENGER = 1;
@@ -1744,6 +1746,17 @@ message UserAgent {
// optional uint32 quinary = 5;
//}
+message DevicePairingRegistrationData {
+ optional bytes eRegid = 1;
+ optional bytes eKeytype = 2;
+ optional bytes eIdent = 3;
+ optional bytes eSkeyId = 4;
+ optional bytes eSkeyVal = 5;
+ optional bytes eSkeySig = 6;
+ optional bytes buildHash = 7;
+ optional bytes deviceProps = 8;
+}
+
message DNSSource {
enum DNSSourceDNSResolutionMethod {
SYSTEM = 0;
@@ -1756,17 +1769,6 @@ message DNSSource {
optional bool appCached = 16;
}
-message CompanionRegData {
- optional bytes eRegid = 1;
- optional bytes eKeytype = 2;
- optional bytes eIdent = 3;
- optional bytes eSkeyId = 4;
- optional bytes eSkeyVal = 5;
- optional bytes eSkeySig = 6;
- optional bytes buildHash = 7;
- optional bytes companionProps = 8;
-}
-
message WebNotificationsInfo {
optional uint64 timestamp = 2;
optional uint32 unreadChats = 3;
diff --git a/vendor/go.mau.fi/whatsmeow/broadcast.go b/vendor/go.mau.fi/whatsmeow/broadcast.go
new file mode 100644
index 00000000..c1260a9c
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/broadcast.go
@@ -0,0 +1,138 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package whatsmeow
+
+import (
+ "errors"
+ "fmt"
+
+ waBinary "go.mau.fi/whatsmeow/binary"
+ "go.mau.fi/whatsmeow/types"
+)
+
+func (cli *Client) getBroadcastListParticipants(jid types.JID) ([]types.JID, error) {
+ var list []types.JID
+ var err error
+ if jid == types.StatusBroadcastJID {
+ list, err = cli.getStatusBroadcastRecipients()
+ } else {
+ return nil, ErrBroadcastListUnsupported
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ var hasSelf bool
+ for _, participant := range list {
+ if participant.User == cli.Store.ID.User {
+ hasSelf = true
+ break
+ }
+ }
+ if !hasSelf {
+ list = append(list, cli.Store.ID.ToNonAD())
+ }
+ return list, nil
+}
+
+func (cli *Client) getStatusBroadcastRecipients() ([]types.JID, error) {
+ statusPrivacyOptions, err := cli.GetStatusPrivacy()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get status privacy: %w", err)
+ }
+ statusPrivacy := statusPrivacyOptions[0]
+ if statusPrivacy.Type == types.StatusPrivacyTypeWhitelist {
+ // Whitelist mode, just return the list
+ return statusPrivacy.List, nil
+ }
+
+ // Blacklist or all contacts mode. Find all contacts from database, then filter them appropriately.
+ contacts, err := cli.Store.Contacts.GetAllContacts()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get contact list from db: %w", err)
+ }
+
+ blacklist := make(map[types.JID]struct{})
+ if statusPrivacy.Type == types.StatusPrivacyTypeBlacklist {
+ for _, jid := range statusPrivacy.List {
+ blacklist[jid] = struct{}{}
+ }
+ }
+
+ var contactsArray []types.JID
+ for jid, contact := range contacts {
+ _, isBlacklisted := blacklist[jid]
+ if isBlacklisted {
+ continue
+ }
+ // TODO should there be a better way to separate contacts and found push names in the db?
+ if len(contact.FullName) > 0 {
+ contactsArray = append(contactsArray, jid)
+ }
+ }
+ return contactsArray, nil
+}
+
+var DefaultStatusPrivacy = []types.StatusPrivacy{{
+ Type: types.StatusPrivacyTypeContacts,
+ IsDefault: true,
+}}
+
+// GetStatusPrivacy gets the user's status privacy settings (who to send status broadcasts to).
+//
+// There can be multiple different stored settings, the first one is always the default.
+func (cli *Client) GetStatusPrivacy() ([]types.StatusPrivacy, error) {
+ resp, err := cli.sendIQ(infoQuery{
+ Namespace: "status",
+ Type: iqGet,
+ To: types.ServerJID,
+ Content: []waBinary.Node{{
+ Tag: "privacy",
+ }},
+ })
+ if err != nil {
+ if errors.Is(err, ErrIQNotFound) {
+ return DefaultStatusPrivacy, nil
+ }
+ return nil, err
+ }
+ privacyLists := resp.GetChildByTag("privacy")
+ var outputs []types.StatusPrivacy
+ for _, list := range privacyLists.GetChildren() {
+ if list.Tag != "list" {
+ continue
+ }
+
+ ag := list.AttrGetter()
+ var out types.StatusPrivacy
+ out.IsDefault = ag.OptionalBool("default")
+ out.Type = types.StatusPrivacyType(ag.String("type"))
+ children := list.GetChildren()
+ if len(children) > 0 {
+ out.List = make([]types.JID, 0, len(children))
+ for _, child := range children {
+ jid, ok := child.Attrs["jid"].(types.JID)
+ if child.Tag == "user" && ok {
+ out.List = append(out.List, jid)
+ }
+ }
+ }
+ outputs = append(outputs, out)
+ if out.IsDefault {
+ // Move default to always be first in the list
+ outputs[len(outputs)-1] = outputs[0]
+ outputs[0] = out
+ }
+ if len(ag.Errors) > 0 {
+ return nil, ag.Error()
+ }
+ }
+ if len(outputs) == 0 {
+ return DefaultStatusPrivacy, nil
+ }
+ return outputs, nil
+}
diff --git a/vendor/go.mau.fi/whatsmeow/call.go b/vendor/go.mau.fi/whatsmeow/call.go
index e48e791c..f4de9bd8 100644
--- a/vendor/go.mau.fi/whatsmeow/call.go
+++ b/vendor/go.mau.fi/whatsmeow/call.go
@@ -7,8 +7,6 @@
package whatsmeow
import (
- "time"
-
waBinary "go.mau.fi/whatsmeow/binary"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
@@ -26,7 +24,7 @@ func (cli *Client) handleCallEvent(node *waBinary.Node) {
cag := child.AttrGetter()
basicMeta := types.BasicCallMeta{
From: ag.JID("from"),
- Timestamp: time.Unix(ag.Int64("t"), 0),
+ Timestamp: ag.UnixTime("t"),
CallCreator: cag.JID("call-creator"),
CallID: cag.String("call-id"),
}
diff --git a/vendor/go.mau.fi/whatsmeow/client.go b/vendor/go.mau.fi/whatsmeow/client.go
index 70c82130..ea3a297c 100644
--- a/vendor/go.mau.fi/whatsmeow/client.go
+++ b/vendor/go.mau.fi/whatsmeow/client.go
@@ -10,7 +10,6 @@ package whatsmeow
import (
"context"
"crypto/rand"
- "encoding/base64"
"encoding/hex"
"errors"
"fmt"
@@ -52,6 +51,7 @@ type Client struct {
socket *socket.NoiseSocket
socketLock sync.RWMutex
+ socketWait chan struct{}
isLoggedIn uint32
expectedDisconnectVal uint32
@@ -88,6 +88,11 @@ type Client struct {
messageRetries map[string]int
messageRetriesLock sync.Mutex
+ appStateKeyRequests map[string]time.Time
+ appStateKeyRequestsLock sync.RWMutex
+
+ messageSendLock sync.Mutex
+
privacySettingsCache atomic.Value
groupParticipantsCache map[types.JID][]types.JID
@@ -99,22 +104,21 @@ type Client struct {
recentMessagesList [recentMessagesSize]recentMessageKey
recentMessagesPtr int
recentMessagesLock sync.RWMutex
+
+ sessionRecreateHistory map[types.JID]time.Time
+ sessionRecreateHistoryLock sync.Mutex
// GetMessageForRetry is used to find the source message for handling retry receipts
// when the message is not found in the recently sent message cache.
- GetMessageForRetry func(to types.JID, id types.MessageID) *waProto.Message
+ GetMessageForRetry func(requester, to types.JID, id types.MessageID) *waProto.Message
// PreRetryCallback is called before a retry receipt is accepted.
// If it returns false, the accepting will be cancelled and the retry receipt will be ignored.
- PreRetryCallback func(receipt *events.Receipt, retryCount int, msg *waProto.Message) bool
+ PreRetryCallback func(receipt *events.Receipt, id types.MessageID, retryCount int, msg *waProto.Message) bool
// Should untrusted identity errors be handled automatically? If true, the stored identity and existing signal
// sessions will be removed on untrusted identity errors, and an events.IdentityChange will be dispatched.
// If false, decrypting a message from untrusted devices will fail.
AutoTrustIdentity bool
- DebugDecodeBeforeSend bool
- OneMessageAtATime bool
- messageSendLock sync.Mutex
-
uniqueID string
idCounter uint32
@@ -162,14 +166,17 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
messageRetries: make(map[string]int),
handlerQueue: make(chan *waBinary.Node, handlerQueueSize),
appStateProc: appstate.NewProcessor(deviceStore, log.Sub("AppState")),
+ socketWait: make(chan struct{}),
historySyncNotifications: make(chan *waProto.HistorySyncNotification, 32),
groupParticipantsCache: make(map[types.JID][]types.JID),
userDevicesCache: make(map[types.JID][]types.JID),
- recentMessagesMap: make(map[recentMessageKey]*waProto.Message, recentMessagesSize),
- GetMessageForRetry: func(to types.JID, id types.MessageID) *waProto.Message { return nil },
+ recentMessagesMap: make(map[recentMessageKey]*waProto.Message, recentMessagesSize),
+ sessionRecreateHistory: make(map[types.JID]time.Time),
+ GetMessageForRetry: func(requester, to types.JID, id types.MessageID) *waProto.Message { return nil },
+ appStateKeyRequests: make(map[string]time.Time),
EnableAutoReconnect: true,
AutoTrustIdentity: true,
@@ -226,6 +233,37 @@ func (cli *Client) SetProxy(proxy socket.Proxy) {
cli.http.Transport.(*http.Transport).Proxy = proxy
}
+func (cli *Client) getSocketWaitChan() <-chan struct{} {
+ cli.socketLock.RLock()
+ ch := cli.socketWait
+ cli.socketLock.RUnlock()
+ return ch
+}
+
+func (cli *Client) closeSocketWaitChan() {
+ cli.socketLock.Lock()
+ close(cli.socketWait)
+ cli.socketWait = make(chan struct{})
+ cli.socketLock.Unlock()
+}
+
+func (cli *Client) WaitForConnection(timeout time.Duration) bool {
+ timeoutChan := time.After(timeout)
+ cli.socketLock.RLock()
+ for cli.socket == nil || !cli.socket.IsConnected() || !cli.IsLoggedIn() {
+ ch := cli.socketWait
+ cli.socketLock.RUnlock()
+ select {
+ case <-ch:
+ case <-timeoutChan:
+ return false
+ }
+ cli.socketLock.RLock()
+ }
+ cli.socketLock.RUnlock()
+ return true
+}
+
// Connect connects the client to the WhatsApp web websocket. After connection, it will either
// authenticate if there's data in the device store, or emit a QREvent to set up a new link.
func (cli *Client) Connect() error {
@@ -322,6 +360,9 @@ func (cli *Client) IsConnected() bool {
}
// Disconnect disconnects from the WhatsApp web websocket.
+//
+// This will not emit any events, the Disconnected event is only used when the
+// connection is closed by the server or a network error.
func (cli *Client) Disconnect() {
if cli.socket == nil {
return
@@ -336,6 +377,7 @@ func (cli *Client) unlockedDisconnect() {
if cli.socket != nil {
cli.socket.Stop(true)
cli.socket = nil
+ cli.clearResponseWaiters(xmlStreamEndNode)
}
}
@@ -343,6 +385,9 @@ func (cli *Client) unlockedDisconnect() {
//
// If the logout request fails, the disconnection and local data deletion will not happen either.
// If an error is returned, but you want to force disconnect/clear data, call Client.Disconnect() and Client.Store.Delete() manually.
+//
+// Note that this will not emit any events. The LoggedOut event is only used for external logouts
+// (triggered by the user from the main device or by WhatsApp servers).
func (cli *Client) Logout() error {
if cli.Store.ID == nil {
return ErrNotLoggedIn
@@ -491,7 +536,7 @@ func (cli *Client) handlerQueueLoop(ctx context.Context) {
}
}
-func (cli *Client) sendNodeDebug(node waBinary.Node) ([]byte, error) {
+func (cli *Client) sendNodeAndGetData(node waBinary.Node) ([]byte, error) {
cli.socketLock.RLock()
sock := cli.socket
cli.socketLock.RUnlock()
@@ -503,22 +548,13 @@ func (cli *Client) sendNodeDebug(node waBinary.Node) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("failed to marshal node: %w", err)
}
- if cli.DebugDecodeBeforeSend {
- var decoded *waBinary.Node
- decoded, err = waBinary.Unmarshal(payload[1:])
- if err != nil {
- cli.Log.Infof("Malformed payload: %s", base64.URLEncoding.EncodeToString(payload))
- return nil, fmt.Errorf("failed to decode the binary we just produced: %w", err)
- }
- node = *decoded
- }
cli.sendLog.Debugf("%s", node.XMLString())
return payload, sock.SendFrame(payload)
}
func (cli *Client) sendNode(node waBinary.Node) error {
- _, err := cli.sendNodeDebug(node)
+ _, err := cli.sendNodeAndGetData(node)
return err
}
@@ -535,3 +571,45 @@ func (cli *Client) dispatchEvent(evt interface{}) {
handler.fn(evt)
}
}
+
+// ParseWebMessage parses a WebMessageInfo object into *events.Message to match what real-time messages have.
+//
+// The chat JID can be found in the Conversation data:
+// chatJID, err := types.ParseJID(conv.GetId())
+// for _, historyMsg := range conv.GetMessages() {
+// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
+// yourNormalEventHandler(evt)
+// }
+func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessageInfo) (*events.Message, error) {
+ info := types.MessageInfo{
+ MessageSource: types.MessageSource{
+ Chat: chatJID,
+ IsFromMe: webMsg.GetKey().GetFromMe(),
+ IsGroup: chatJID.Server == types.GroupServer,
+ },
+ ID: webMsg.GetKey().GetId(),
+ PushName: webMsg.GetPushName(),
+ Timestamp: time.Unix(int64(webMsg.GetMessageTimestamp()), 0),
+ }
+ var err error
+ if info.IsFromMe {
+ info.Sender = cli.Store.ID.ToNonAD()
+ } else if chatJID.Server == types.DefaultUserServer {
+ info.Sender = chatJID
+ } else if webMsg.GetParticipant() != "" {
+ info.Sender, err = types.ParseJID(webMsg.GetParticipant())
+ } else if webMsg.GetKey().GetParticipant() != "" {
+ info.Sender, err = types.ParseJID(webMsg.GetKey().GetParticipant())
+ } else {
+ return nil, fmt.Errorf("couldn't find sender of message %s", info.ID)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse sender of message %s: %v", info.ID, err)
+ }
+ evt := &events.Message{
+ RawMessage: webMsg.GetMessage(),
+ Info: info,
+ }
+ evt.UnwrapRaw()
+ return evt, nil
+}
diff --git a/vendor/go.mau.fi/whatsmeow/connectionevents.go b/vendor/go.mau.fi/whatsmeow/connectionevents.go
index 2c8d27c0..54fafccf 100644
--- a/vendor/go.mau.fi/whatsmeow/connectionevents.go
+++ b/vendor/go.mau.fi/whatsmeow/connectionevents.go
@@ -89,11 +89,7 @@ func (cli *Client) handleConnectFailure(node *waBinary.Node) {
}
} else if reason == events.ConnectFailureTempBanned {
cli.Log.Warnf("Temporary ban connect failure: %s", node.XMLString())
- expiryTimeUnix := ag.Int64("expire")
- var expiryTime time.Time
- if expiryTimeUnix > 0 {
- expiryTime = time.Unix(expiryTimeUnix, 0)
- }
+ expiryTime := ag.UnixTime("expire")
go cli.dispatchEvent(&events.TemporaryBan{
Code: events.TempBanReason(ag.Int("code")),
Expire: expiryTime,
@@ -130,6 +126,7 @@ func (cli *Client) handleConnectSuccess(node *waBinary.Node) {
cli.Log.Warnf("Failed to send post-connect passive IQ: %v", err)
}
cli.dispatchEvent(&events.Connected{})
+ cli.closeSocketWaitChan()
}()
}
diff --git a/vendor/go.mau.fi/whatsmeow/download.go b/vendor/go.mau.fi/whatsmeow/download.go
index 6779478f..e9b8d4f1 100644
--- a/vendor/go.mau.fi/whatsmeow/download.go
+++ b/vendor/go.mau.fi/whatsmeow/download.go
@@ -13,6 +13,7 @@ import (
"fmt"
"io"
"net/http"
+ "strings"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
@@ -183,11 +184,20 @@ func (cli *Client) Download(msg DownloadableMessage) ([]byte, error) {
return nil, fmt.Errorf("%w '%s'", ErrUnknownMediaType, string(msg.ProtoReflect().Descriptor().Name()))
}
urlable, ok := msg.(downloadableMessageWithURL)
- if ok && len(urlable.GetUrl()) > 0 {
+ var url string
+ var isWebWhatsappNetURL bool
+ if ok {
+ url = urlable.GetUrl()
+ isWebWhatsappNetURL = strings.HasPrefix(urlable.GetUrl(), "https://web.whatsapp.net")
+ }
+ if len(url) > 0 && !isWebWhatsappNetURL {
return cli.downloadAndDecrypt(urlable.GetUrl(), msg.GetMediaKey(), mediaType, getSize(msg), msg.GetFileEncSha256(), msg.GetFileSha256())
} else if len(msg.GetDirectPath()) > 0 {
return cli.DownloadMediaWithPath(msg.GetDirectPath(), msg.GetFileEncSha256(), msg.GetFileSha256(), msg.GetMediaKey(), getSize(msg), mediaType, mediaTypeToMMSType[mediaType])
} else {
+ if isWebWhatsappNetURL {
+ cli.Log.Warnf("Got a media message with a web.whatsapp.net URL (%s) and no direct path", url)
+ }
return nil, ErrNoURLPresent
}
}
diff --git a/vendor/go.mau.fi/whatsmeow/errors.go b/vendor/go.mau.fi/whatsmeow/errors.go
index d11cbb37..5cdbd1d0 100644
--- a/vendor/go.mau.fi/whatsmeow/errors.go
+++ b/vendor/go.mau.fi/whatsmeow/errors.go
@@ -50,11 +50,13 @@ var (
ErrMediaNotAvailableOnPhone = errors.New("media no longer available on phone")
// ErrUnknownMediaRetryError is returned by DecryptMediaRetryNotification if the given event contains an unknown error code.
ErrUnknownMediaRetryError = errors.New("unknown media retry error")
+ // ErrInvalidDisappearingTimer is returned by SetDisappearingTimer if the given timer is not one of the allowed values.
+ ErrInvalidDisappearingTimer = errors.New("invalid disappearing timer provided")
)
// Some errors that Client.SendMessage can return
var (
- ErrBroadcastListUnsupported = errors.New("sending to broadcast lists is not yet supported")
+ ErrBroadcastListUnsupported = errors.New("sending to non-status broadcast lists is not yet supported")
ErrUnknownServer = errors.New("can't send message to unknown server")
ErrRecipientADJID = errors.New("message recipient must be normal (non-AD) JID")
)
@@ -104,6 +106,7 @@ type IQError struct {
// Common errors returned by info queries for use with errors.Is
var (
+ ErrIQBadRequest error = &IQError{Code: 400, Text: "bad-request"}
ErrIQNotAuthorized error = &IQError{Code: 401, Text: "not-authorized"}
ErrIQForbidden error = &IQError{Code: 403, Text: "forbidden"}
ErrIQNotFound error = &IQError{Code: 404, Text: "item-not-found"}
diff --git a/vendor/go.mau.fi/whatsmeow/group.go b/vendor/go.mau.fi/whatsmeow/group.go
index 34e3603f..e19a90a3 100644
--- a/vendor/go.mau.fi/whatsmeow/group.go
+++ b/vendor/go.mau.fi/whatsmeow/group.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"strings"
- "time"
waBinary "go.mau.fi/whatsmeow/binary"
"go.mau.fi/whatsmeow/types"
@@ -397,10 +396,10 @@ func (cli *Client) parseGroupNode(groupNode *waBinary.Node) (*types.GroupInfo, e
group.OwnerJID = ag.OptionalJIDOrEmpty("creator")
group.Name = ag.String("subject")
- group.NameSetAt = time.Unix(ag.Int64("s_t"), 0)
+ group.NameSetAt = ag.UnixTime("s_t")
group.NameSetBy = ag.OptionalJIDOrEmpty("s_o")
- group.GroupCreated = time.Unix(ag.Int64("creation"), 0)
+ group.GroupCreated = ag.UnixTime("creation")
group.AnnounceVersionID = ag.OptionalString("a_v_id")
group.ParticipantVersionID = ag.OptionalString("p_v_id")
@@ -423,7 +422,7 @@ func (cli *Client) parseGroupNode(groupNode *waBinary.Node) (*types.GroupInfo, e
group.Topic = string(topicBytes)
group.TopicID = childAG.String("id")
group.TopicSetBy = childAG.OptionalJIDOrEmpty("participant")
- group.TopicSetAt = time.Unix(childAG.Int64("t"), 0)
+ group.TopicSetAt = childAG.UnixTime("t")
}
case "announcement":
group.IsAnnounce = true
@@ -477,7 +476,7 @@ func (cli *Client) parseGroupChange(node *waBinary.Node) (*events.GroupInfo, err
evt.JID = ag.JID("from")
evt.Notify = ag.OptionalString("notify")
evt.Sender = ag.OptionalJID("participant")
- evt.Timestamp = time.Unix(ag.Int64("t"), 0)
+ evt.Timestamp = ag.UnixTime("t")
if !ag.OK() {
return nil, fmt.Errorf("group change doesn't contain required attributes: %w", ag.Error())
}
@@ -505,7 +504,7 @@ func (cli *Client) parseGroupChange(node *waBinary.Node) (*events.GroupInfo, err
case "subject":
evt.Name = &types.GroupName{
Name: cag.String("subject"),
- NameSetAt: time.Unix(cag.Int64("s_t"), 0),
+ NameSetAt: cag.UnixTime("s_t"),
NameSetBy: cag.OptionalJIDOrEmpty("s_o"),
}
case "description":
diff --git a/vendor/go.mau.fi/whatsmeow/internals.go b/vendor/go.mau.fi/whatsmeow/internals.go
index 64a0b354..7a8d50b5 100644
--- a/vendor/go.mau.fi/whatsmeow/internals.go
+++ b/vendor/go.mau.fi/whatsmeow/internals.go
@@ -53,3 +53,11 @@ func (int *DangerousInternalClient) RefreshMediaConn(force bool) (*MediaConn, er
func (int *DangerousInternalClient) GetServerPreKeyCount() (int, error) {
return int.c.getServerPreKeyCount()
}
+
+func (int *DangerousInternalClient) RequestAppStateKeys(keyIDs [][]byte) {
+ int.c.requestAppStateKeys(keyIDs)
+}
+
+func (int *DangerousInternalClient) SendRetryReceipt(node *waBinary.Node, forceIncludeIdentity bool) {
+ int.c.sendRetryReceipt(node, forceIncludeIdentity)
+}
diff --git a/vendor/go.mau.fi/whatsmeow/keepalive.go b/vendor/go.mau.fi/whatsmeow/keepalive.go
index ec05df1d..d5e40286 100644
--- a/vendor/go.mau.fi/whatsmeow/keepalive.go
+++ b/vendor/go.mau.fi/whatsmeow/keepalive.go
@@ -13,6 +13,7 @@ import (
waBinary "go.mau.fi/whatsmeow/binary"
"go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/types/events"
)
var (
@@ -25,12 +26,27 @@ var (
)
func (cli *Client) keepAliveLoop(ctx context.Context) {
+ var lastSuccess time.Time
+ var errorCount int
for {
interval := rand.Int63n(KeepAliveIntervalMax.Milliseconds()-KeepAliveIntervalMin.Milliseconds()) + KeepAliveIntervalMin.Milliseconds()
select {
case <-time.After(time.Duration(interval) * time.Millisecond):
- if !cli.sendKeepAlive(ctx) {
+ isSuccess, shouldContinue := cli.sendKeepAlive(ctx)
+ if !shouldContinue {
return
+ } else if !isSuccess {
+ errorCount++
+ go cli.dispatchEvent(&events.KeepAliveTimeout{
+ ErrorCount: errorCount,
+ LastSuccess: lastSuccess,
+ })
+ } else {
+ if errorCount > 0 {
+ errorCount = 0
+ go cli.dispatchEvent(&events.KeepAliveRestored{})
+ }
+ lastSuccess = time.Now()
}
case <-ctx.Done():
return
@@ -38,7 +54,7 @@ func (cli *Client) keepAliveLoop(ctx context.Context) {
}
}
-func (cli *Client) sendKeepAlive(ctx context.Context) bool {
+func (cli *Client) sendKeepAlive(ctx context.Context) (isSuccess, shouldContinue bool) {
respCh, err := cli.sendIQAsync(infoQuery{
Namespace: "w:p",
Type: "get",
@@ -47,16 +63,16 @@ func (cli *Client) sendKeepAlive(ctx context.Context) bool {
})
if err != nil {
cli.Log.Warnf("Failed to send keepalive: %v", err)
- return true
+ return false, true
}
select {
case <-respCh:
// All good
+ return true, true
case <-time.After(KeepAliveResponseDeadline):
- // TODO disconnect websocket?
cli.Log.Warnf("Keepalive timed out")
+ return false, true
case <-ctx.Done():
- return false
+ return false, false
}
- return true
}
diff --git a/vendor/go.mau.fi/whatsmeow/mediaretry.go b/vendor/go.mau.fi/whatsmeow/mediaretry.go
index ddee228e..52b04608 100644
--- a/vendor/go.mau.fi/whatsmeow/mediaretry.go
+++ b/vendor/go.mau.fi/whatsmeow/mediaretry.go
@@ -11,7 +11,6 @@ import (
"crypto/cipher"
"crypto/rand"
"fmt"
- "time"
"google.golang.org/protobuf/proto"
@@ -64,8 +63,38 @@ func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphe
// SendMediaRetryReceipt sends a request to the phone to re-upload the media in a message.
//
+// This is mostly relevant when handling history syncs and getting a 404 or 410 error downloading media.
+// Rough example on how to use it (will not work out of the box, you must adjust it depending on what you need exactly):
+//
+// var mediaRetryCache map[types.MessageID]*waProto.ImageMessage
+//
+// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
+// imageMsg := evt.Message.GetImageMessage() // replace this with the part of the message you want to download
+// data, err := cli.Download(imageMsg)
+// if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
+// err = cli.SendMediaRetryReceipt(&evt.Info, imageMsg.GetMediaKey())
+// // You need to store the event data somewhere as it's necessary for handling the retry response.
+// mediaRetryCache[evt.Info.ID] = imageMsg
+// }
+//
// The response will come as an *events.MediaRetry. The response will then have to be decrypted
-// using DecryptMediaRetryNotification and the same media key passed here.
+// using DecryptMediaRetryNotification and the same media key passed here. If the media retry was successful,
+// the decrypted notification should contain an updated DirectPath, which can be used to download the file.
+//
+// func eventHandler(rawEvt interface{}) {
+// switch evt := rawEvt.(type) {
+// case *events.MediaRetry:
+// imageMsg := mediaRetryCache[evt.MessageID]
+// retryData, err := whatsmeow.DecryptMediaRetryNotification(evt, imageMsg.GetMediaKey())
+// if err != nil || retryData.GetResult != waProto.MediaRetryNotification_SUCCESS {
+// return
+// }
+// // Use the new path to download the attachment
+// imageMsg.DirectPath = retryData.DirectPath
+// data, err := cli.Download(imageMsg)
+// // Alternatively, you can use cli.DownloadMediaWithPath and provide the individual fields manually.
+// }
+// }
func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey []byte) error {
ciphertext, iv, err := encryptMediaRetryReceipt(message.ID, mediaKey)
if err != nil {
@@ -104,6 +133,7 @@ func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey []
}
// DecryptMediaRetryNotification decrypts a media retry notification using the media key.
+// See Client.SendMediaRetryReceipt for more info on how to use this.
func DecryptMediaRetryNotification(evt *events.MediaRetry, mediaKey []byte) (*waProto.MediaRetryNotification, error) {
var notif waProto.MediaRetryNotification
var plaintext []byte
@@ -126,7 +156,7 @@ func DecryptMediaRetryNotification(evt *events.MediaRetry, mediaKey []byte) (*wa
func parseMediaRetryNotification(node *waBinary.Node) (*events.MediaRetry, error) {
ag := node.AttrGetter()
var evt events.MediaRetry
- evt.Timestamp = time.Unix(ag.Int64("t"), 0)
+ evt.Timestamp = ag.UnixTime("t")
evt.MessageID = types.MessageID(ag.String("id"))
if !ag.OK() {
return nil, ag.Error()
diff --git a/vendor/go.mau.fi/whatsmeow/message.go b/vendor/go.mau.fi/whatsmeow/message.go
index 36ccfcad..24c1c6c6 100644
--- a/vendor/go.mau.fi/whatsmeow/message.go
+++ b/vendor/go.mau.fi/whatsmeow/message.go
@@ -10,11 +10,11 @@ import (
"bytes"
"compress/zlib"
"crypto/rand"
+ "encoding/hex"
"errors"
"fmt"
"io"
"runtime/debug"
- "strconv"
"sync/atomic"
"time"
@@ -48,67 +48,50 @@ func (cli *Client) handleEncryptedMessage(node *waBinary.Node) {
}
func (cli *Client) parseMessageSource(node *waBinary.Node) (source types.MessageSource, err error) {
- from, ok := node.Attrs["from"].(types.JID)
- if !ok {
- err = fmt.Errorf("didn't find valid `from` attribute in message")
- } else if from.Server == types.GroupServer || from.Server == types.BroadcastServer {
+ ag := node.AttrGetter()
+ from := ag.JID("from")
+ if from.Server == types.GroupServer || from.Server == types.BroadcastServer {
source.IsGroup = true
source.Chat = from
- sender, ok := node.Attrs["participant"].(types.JID)
- if !ok {
- err = fmt.Errorf("didn't find valid `participant` attribute in group message")
- } else {
- source.Sender = sender
- if source.Sender.User == cli.Store.ID.User {
- source.IsFromMe = true
- }
+ source.Sender = ag.JID("participant")
+ if source.Sender.User == cli.Store.ID.User {
+ source.IsFromMe = true
}
if from.Server == types.BroadcastServer {
- recipient, ok := node.Attrs["recipient"].(types.JID)
- if ok {
- source.BroadcastListOwner = recipient
- }
+ source.BroadcastListOwner = ag.OptionalJIDOrEmpty("recipient")
}
} else if from.User == cli.Store.ID.User {
source.IsFromMe = true
source.Sender = from
- recipient, ok := node.Attrs["recipient"].(types.JID)
- if !ok {
- source.Chat = from.ToNonAD()
+ recipient := ag.OptionalJID("recipient")
+ if recipient != nil {
+ source.Chat = *recipient
} else {
- source.Chat = recipient
+ source.Chat = from.ToNonAD()
}
} else {
source.Chat = from.ToNonAD()
source.Sender = from
}
+ err = ag.Error()
return
}
func (cli *Client) parseMessageInfo(node *waBinary.Node) (*types.MessageInfo, error) {
var info types.MessageInfo
var err error
- var ok bool
info.MessageSource, err = cli.parseMessageSource(node)
if err != nil {
return nil, err
}
- info.ID, ok = node.Attrs["id"].(string)
- if !ok {
- return nil, fmt.Errorf("didn't find valid `id` attribute in message")
- }
- ts, ok := node.Attrs["t"].(string)
- if !ok {
- return nil, fmt.Errorf("didn't find valid `t` (timestamp) attribute in message")
+ ag := node.AttrGetter()
+ info.ID = types.MessageID(ag.String("id"))
+ info.Timestamp = ag.UnixTime("t")
+ info.PushName = ag.OptionalString("notify")
+ info.Category = ag.OptionalString("category")
+ if !ag.OK() {
+ return nil, ag.Error()
}
- tsInt, err := strconv.ParseInt(ts, 10, 64)
- if err != nil {
- return nil, fmt.Errorf("didn't find valid `t` (timestamp) attribute in message: %w", err)
- }
- info.Timestamp = time.Unix(tsInt, 0)
-
- info.PushName, _ = node.Attrs["notify"].(string)
- info.Category, _ = node.Attrs["category"].(string)
for _, child := range node.GetChildren() {
if child.Tag == "multicast" {
@@ -132,6 +115,7 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
children := node.GetChildren()
cli.Log.Debugf("Decrypting %d messages from %s", len(children), info.SourceString())
handled := false
+ containsDirectMsg := false
for _, child := range children {
if child.Tag != "enc" {
continue
@@ -144,6 +128,7 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
var err error
if encType == "pkmsg" || encType == "msg" {
decrypted, err = cli.decryptDM(&child, info.Sender, encType == "pkmsg")
+ containsDirectMsg = true
} else if info.IsGroup && encType == "skmsg" {
decrypted, err = cli.decryptGroupMsg(&child, info.Sender, info.Chat)
} else {
@@ -152,8 +137,9 @@ 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)
- go cli.sendRetryReceipt(node, false)
- cli.dispatchEvent(&events.UndecryptableMessage{Info: *info, IsUnavailable: false})
+ isUnavailable := encType == "skmsg" && !containsDirectMsg && errors.Is(err, signalerror.ErrNoSenderKeyForUser)
+ go cli.sendRetryReceipt(node, isUnavailable)
+ cli.dispatchEvent(&events.UndecryptableMessage{Info: *info, IsUnavailable: isUnavailable})
return
}
@@ -317,27 +303,35 @@ func (cli *Client) handleHistorySyncNotification(notif *waProto.HistorySyncNotif
}
func (cli *Client) handleAppStateSyncKeyShare(keys *waProto.AppStateSyncKeyShare) {
+ onlyResyncIfNotSynced := true
+
cli.Log.Debugf("Got %d new app state keys", len(keys.GetKeys()))
+ cli.appStateKeyRequestsLock.RLock()
for _, key := range keys.GetKeys() {
marshaledFingerprint, err := proto.Marshal(key.GetKeyData().GetFingerprint())
if err != nil {
cli.Log.Errorf("Failed to marshal fingerprint of app state sync key %X", key.GetKeyId().GetKeyId())
continue
}
+ _, isReRequest := cli.appStateKeyRequests[hex.EncodeToString(key.GetKeyId().GetKeyId())]
+ if isReRequest {
+ onlyResyncIfNotSynced = false
+ }
err = cli.Store.AppStateKeys.PutAppStateSyncKey(key.GetKeyId().GetKeyId(), store.AppStateSyncKey{
Data: key.GetKeyData().GetKeyData(),
Fingerprint: marshaledFingerprint,
Timestamp: key.GetKeyData().GetTimestamp(),
})
if err != nil {
- cli.Log.Errorf("Failed to store app state sync key %X", key.GetKeyId().GetKeyId())
+ cli.Log.Errorf("Failed to store app state sync key %X: %v", key.GetKeyId().GetKeyId(), err)
continue
}
cli.Log.Debugf("Received app state sync key %X", key.GetKeyId().GetKeyId())
}
+ cli.appStateKeyRequestsLock.RUnlock()
for _, name := range appstate.AllPatchNames {
- err := cli.FetchAppState(name, false, true)
+ err := cli.FetchAppState(name, false, onlyResyncIfNotSynced)
if err != nil {
cli.Log.Errorf("Failed to do initial fetch of app state %s: %v", name, err)
}
@@ -364,18 +358,11 @@ func (cli *Client) handleProtocolMessage(info *types.MessageInfo, msg *waProto.M
}
}
-func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.Message) {
- evt := &events.Message{Info: *info, RawMessage: msg}
-
- // First unwrap device sent messages
+func (cli *Client) processProtocolParts(info *types.MessageInfo, msg *waProto.Message) {
+ // Hopefully sender key distribution messages and protocol messages can't be inside ephemeral messages
if msg.GetDeviceSentMessage().GetMessage() != nil {
msg = msg.GetDeviceSentMessage().GetMessage()
- evt.Info.DeviceSentMeta = &types.DeviceSentMeta{
- DestinationJID: msg.GetDeviceSentMessage().GetDestinationJid(),
- Phash: msg.GetDeviceSentMessage().GetPhash(),
- }
}
-
if msg.GetSenderKeyDistributionMessage() != nil {
if !info.IsGroup {
cli.Log.Warnf("Got sender key distribution message in non-group chat from", info.Sender)
@@ -387,19 +374,12 @@ func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.
cli.handleProtocolMessage(info, msg)
}
- // Unwrap ephemeral and view-once messages
- // Hopefully sender key distribution messages and protocol messages can't be inside ephemeral messages
- if msg.GetEphemeralMessage().GetMessage() != nil {
- msg = msg.GetEphemeralMessage().GetMessage()
- evt.IsEphemeral = true
- }
- if msg.GetViewOnceMessage().GetMessage() != nil {
- msg = msg.GetViewOnceMessage().GetMessage()
- evt.IsViewOnce = true
- }
- evt.Message = msg
+}
- cli.dispatchEvent(evt)
+func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.Message) {
+ cli.processProtocolParts(info, msg)
+ evt := &events.Message{Info: *info, RawMessage: msg}
+ cli.dispatchEvent(evt.UnwrapRaw())
}
func (cli *Client) sendProtocolMessageReceipt(id, msgType string) {
diff --git a/vendor/go.mau.fi/whatsmeow/notification.go b/vendor/go.mau.fi/whatsmeow/notification.go
index c05fd4cb..c55764d1 100644
--- a/vendor/go.mau.fi/whatsmeow/notification.go
+++ b/vendor/go.mau.fi/whatsmeow/notification.go
@@ -8,7 +8,6 @@ package whatsmeow
import (
"errors"
- "time"
"go.mau.fi/whatsmeow/appstate"
waBinary "go.mau.fi/whatsmeow/binary"
@@ -40,7 +39,7 @@ func (cli *Client) handleEncryptNotification(node *waBinary.Node) {
if err != nil {
cli.Log.Warnf("Failed to delete all sessions of %s from store after identity change: %v", from, err)
}
- ts := time.Unix(node.AttrGetter().Int64("t"), 0)
+ ts := node.AttrGetter().UnixTime("t")
cli.dispatchEvent(&events.IdentityChange{JID: from, Timestamp: ts})
} else {
cli.Log.Debugf("Got unknown encryption notification from server: %s", node.XMLString())
@@ -65,7 +64,7 @@ func (cli *Client) handleAppStateNotification(node *waBinary.Node) {
}
func (cli *Client) handlePictureNotification(node *waBinary.Node) {
- ts := time.Unix(node.AttrGetter().Int64("t"), 0)
+ ts := node.AttrGetter().UnixTime("t")
for _, child := range node.GetChildren() {
ag := child.AttrGetter()
var evt events.Picture
diff --git a/vendor/go.mau.fi/whatsmeow/pair.go b/vendor/go.mau.fi/whatsmeow/pair.go
index 389d6f52..41dfa086 100644
--- a/vendor/go.mau.fi/whatsmeow/pair.go
+++ b/vendor/go.mau.fi/whatsmeow/pair.go
@@ -138,13 +138,13 @@ func (cli *Client) handlePair(deviceIdentityBytes []byte, reqID, businessName, p
return fmt.Errorf("failed to parse device identity details in pair success message: %w", err)
}
+ cli.Store.Account = proto.Clone(&deviceIdentity).(*waProto.ADVSignedDeviceIdentity)
+
mainDeviceJID := jid
mainDeviceJID.Device = 0
mainDeviceIdentity := *(*[32]byte)(deviceIdentity.AccountSignatureKey)
deviceIdentity.AccountSignatureKey = nil
- cli.Store.Account = proto.Clone(&deviceIdentity).(*waProto.ADVSignedDeviceIdentity)
-
selfSignedDeviceIdentity, err := proto.Marshal(&deviceIdentity)
if err != nil {
cli.sendIQError(reqID, 500, "internal-error")
diff --git a/vendor/go.mau.fi/whatsmeow/presence.go b/vendor/go.mau.fi/whatsmeow/presence.go
index 8de1969a..2943c7da 100644
--- a/vendor/go.mau.fi/whatsmeow/presence.go
+++ b/vendor/go.mau.fi/whatsmeow/presence.go
@@ -8,7 +8,6 @@ package whatsmeow
import (
"sync/atomic"
- "time"
waBinary "go.mau.fi/whatsmeow/binary"
"go.mau.fi/whatsmeow/types"
@@ -48,7 +47,7 @@ func (cli *Client) handlePresence(node *waBinary.Node) {
}
lastSeen := ag.OptionalString("last")
if lastSeen != "" && lastSeen != "deny" {
- evt.LastSeen = time.Unix(ag.Int64("last"), 0)
+ evt.LastSeen = ag.UnixTime("last")
}
if !ag.OK() {
cli.Log.Warnf("Error parsing presence event: %+v", ag.Errors)
diff --git a/vendor/go.mau.fi/whatsmeow/receipt.go b/vendor/go.mau.fi/whatsmeow/receipt.go
index 0f74926b..3335583c 100644
--- a/vendor/go.mau.fi/whatsmeow/receipt.go
+++ b/vendor/go.mau.fi/whatsmeow/receipt.go
@@ -42,7 +42,7 @@ func (cli *Client) parseReceipt(node *waBinary.Node) (*events.Receipt, error) {
}
receipt := events.Receipt{
MessageSource: source,
- Timestamp: time.Unix(ag.Int64("t"), 0),
+ Timestamp: ag.UnixTime("t"),
Type: events.ReceiptType(ag.OptionalString("type")),
}
mainMessageID := ag.String("id")
diff --git a/vendor/go.mau.fi/whatsmeow/request.go b/vendor/go.mau.fi/whatsmeow/request.go
index 444ae1ad..e070126b 100644
--- a/vendor/go.mau.fi/whatsmeow/request.go
+++ b/vendor/go.mau.fi/whatsmeow/request.go
@@ -8,7 +8,7 @@ package whatsmeow
import (
"context"
- "encoding/base64"
+ "fmt"
"strconv"
"sync/atomic"
"time"
@@ -27,6 +27,20 @@ func isDisconnectNode(node *waBinary.Node) bool {
return node == xmlStreamEndNode || node.Tag == "stream:error"
}
+// isAuthErrorDisconnect checks if the given disconnect node is an error that shouldn't cause retrying.
+func isAuthErrorDisconnect(node *waBinary.Node) bool {
+ if node.Tag != "stream:error" {
+ return false
+ }
+ code, _ := node.Attrs["code"].(string)
+ conflict, _ := node.GetOptionalChildByTag("conflict")
+ conflictType := conflict.AttrGetter().OptionalString("type")
+ if code == "401" || conflictType == "replaced" || conflictType == "device_removed" {
+ return true
+ }
+ return false
+}
+
func (cli *Client) clearResponseWaiters(node *waBinary.Node) {
cli.responseWaitersLock.Lock()
for _, waiter := range cli.responseWaiters {
@@ -88,10 +102,11 @@ type infoQuery struct {
Content interface{}
Timeout time.Duration
+ NoRetry bool
Context context.Context
}
-func (cli *Client) sendIQAsyncDebug(query infoQuery) (<-chan *waBinary.Node, []byte, error) {
+func (cli *Client) sendIQAsyncAndGetData(query *infoQuery) (<-chan *waBinary.Node, []byte, error) {
if len(query.ID) == 0 {
query.ID = cli.generateRequestID()
}
@@ -107,7 +122,7 @@ func (cli *Client) sendIQAsyncDebug(query infoQuery) (<-chan *waBinary.Node, []b
if !query.Target.IsEmpty() {
attrs["target"] = query.Target
}
- data, err := cli.sendNodeDebug(waBinary.Node{
+ data, err := cli.sendNodeAndGetData(waBinary.Node{
Tag: "iq",
Attrs: attrs,
Content: query.Content,
@@ -120,12 +135,12 @@ func (cli *Client) sendIQAsyncDebug(query infoQuery) (<-chan *waBinary.Node, []b
}
func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) {
- ch, _, err := cli.sendIQAsyncDebug(query)
+ ch, _, err := cli.sendIQAsyncAndGetData(&query)
return ch, err
}
func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
- resChan, data, err := cli.sendIQAsyncDebug(query)
+ resChan, data, err := cli.sendIQAsyncAndGetData(&query)
if err != nil {
return nil, err
}
@@ -138,10 +153,13 @@ func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
select {
case res := <-resChan:
if isDisconnectNode(res) {
- if cli.DebugDecodeBeforeSend && res.Tag == "stream:error" && res.GetChildByTag("xml-not-well-formed").Tag != "" {
- cli.Log.Debugf("Info query that was interrupted by xml-not-well-formed: %s", base64.URLEncoding.EncodeToString(data))
+ if query.NoRetry {
+ return nil, &DisconnectedError{Action: "info query", Node: res}
+ }
+ res, err = cli.retryFrame("info query", query.ID, data, res, query.Context, query.Timeout)
+ if err != nil {
+ return nil, err
}
- return nil, &DisconnectedError{Action: "info query", Node: res}
}
resType, _ := res.Attrs["type"].(string)
if res.Tag != "iq" || (resType != "result" && resType != "error") {
@@ -156,3 +174,48 @@ func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
return nil, ErrIQTimedOut
}
}
+
+func (cli *Client) retryFrame(reqType, id string, data []byte, origResp *waBinary.Node, ctx context.Context, timeout time.Duration) (*waBinary.Node, error) {
+ if isAuthErrorDisconnect(origResp) {
+ cli.Log.Debugf("%s (%s) was interrupted by websocket disconnection (%s), not retrying as it looks like an auth error", id, reqType, origResp.XMLString())
+ return nil, &DisconnectedError{Action: reqType, Node: origResp}
+ }
+
+ cli.Log.Debugf("%s (%s) was interrupted by websocket disconnection (%s), waiting for reconnect to retry...", id, reqType, origResp.XMLString())
+ if !cli.WaitForConnection(5 * time.Second) {
+ cli.Log.Debugf("Websocket didn't reconnect within 5 seconds of failed %s (%s)", reqType, id)
+ return nil, &DisconnectedError{Action: reqType, Node: origResp}
+ }
+
+ cli.socketLock.RLock()
+ sock := cli.socket
+ cli.socketLock.RUnlock()
+ if sock == nil {
+ return nil, ErrNotConnected
+ }
+
+ respChan := cli.waitResponse(id)
+ err := sock.SendFrame(data)
+ if err != nil {
+ cli.cancelResponse(id, respChan)
+ return nil, err
+ }
+ var resp *waBinary.Node
+ if ctx != nil && timeout > 0 {
+ select {
+ case resp = <-respChan:
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-time.After(timeout):
+ // FIXME this error isn't technically correct (but works for now - the ctx and timeout params are only used from sendIQ)
+ return nil, ErrIQTimedOut
+ }
+ } else {
+ resp = <-respChan
+ }
+ if isDisconnectNode(resp) {
+ cli.Log.Debugf("Retrying %s %s was interrupted by websocket disconnection (%v), not retrying anymore", reqType, id, resp.XMLString())
+ return nil, &DisconnectedError{Action: fmt.Sprintf("%s (retry)", reqType), Node: resp}
+ }
+ return resp, nil
+}
diff --git a/vendor/go.mau.fi/whatsmeow/retry.go b/vendor/go.mau.fi/whatsmeow/retry.go
index a5a56d8f..24be9c7a 100644
--- a/vendor/go.mau.fi/whatsmeow/retry.go
+++ b/vendor/go.mau.fi/whatsmeow/retry.go
@@ -62,7 +62,7 @@ func (cli *Client) getRecentMessage(to types.JID, id types.MessageID) *waProto.M
func (cli *Client) getMessageForRetry(receipt *events.Receipt, messageID types.MessageID) (*waProto.Message, error) {
msg := cli.getRecentMessage(receipt.Chat, messageID)
if msg == nil {
- msg = cli.GetMessageForRetry(receipt.Chat, messageID)
+ msg = cli.GetMessageForRetry(receipt.Sender, receipt.Chat, messageID)
if msg == nil {
return nil, fmt.Errorf("couldn't find message %s", messageID)
} else {
@@ -74,6 +74,25 @@ func (cli *Client) getMessageForRetry(receipt *events.Receipt, messageID types.M
return proto.Clone(msg).(*waProto.Message), nil
}
+const recreateSessionTimeout = 1 * time.Hour
+
+func (cli *Client) shouldRecreateSession(retryCount int, jid types.JID) (reason string, recreate bool) {
+ cli.sessionRecreateHistoryLock.Lock()
+ defer cli.sessionRecreateHistoryLock.Unlock()
+ if !cli.Store.ContainsSession(jid.SignalAddress()) {
+ cli.sessionRecreateHistory[jid] = time.Now()
+ return "we don't have a Signal session with them", true
+ } else if retryCount < 2 {
+ return "", false
+ }
+ prevTime, ok := cli.sessionRecreateHistory[jid]
+ if !ok || prevTime.Add(recreateSessionTimeout).Before(time.Now()) {
+ cli.sessionRecreateHistory[jid] = time.Now()
+ return "retry count > 1 and over an hour since last recreation", true
+ }
+ return "", false
+}
+
// handleRetryReceipt handles an incoming retry receipt for an outgoing message.
func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.Node) error {
retryChild, ok := node.GetOptionalChildByTag("retry")
@@ -82,7 +101,7 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
}
ag := retryChild.AttrGetter()
messageID := ag.String("id")
- timestamp := time.Unix(ag.Int64("t"), 0)
+ timestamp := ag.UnixTime("t")
retryCount := ag.Int("count")
if !ag.OK() {
return ag.Error()
@@ -113,7 +132,7 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
}
}
- if cli.PreRetryCallback != nil && !cli.PreRetryCallback(receipt, retryCount, msg) {
+ if cli.PreRetryCallback != nil && !cli.PreRetryCallback(receipt, messageID, retryCount, msg) {
cli.Log.Debugf("Cancelled retry receipt in PreRetryCallback")
return nil
}
@@ -129,12 +148,8 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
if err != nil {
return fmt.Errorf("failed to read prekey bundle in retry receipt: %w", err)
}
- } else if retryCount >= 2 || !cli.Store.ContainsSession(receipt.Sender.SignalAddress()) {
- if retryCount >= 2 {
- cli.Log.Debugf("Fetching prekeys for %s due to retry receipt with count>1 but no prekey bundle", receipt.Sender)
- } else {
- cli.Log.Debugf("Fetching prekeys for %s for handling retry receipt because we don't have a Signal session with them", receipt.Sender)
- }
+ } else if reason, recreate := cli.shouldRecreateSession(retryCount, receipt.Sender); recreate {
+ cli.Log.Debugf("Fetching prekeys for %s for handling retry receipt with no prekey bundle because %s", receipt.Sender, reason)
var keys map[types.JID]preKeyResp
keys, err = cli.fetchPreKeys([]types.JID{receipt.Sender})
if err != nil {
@@ -148,13 +163,6 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
} else if bundle == nil {
return fmt.Errorf("didn't get prekey bundle for %s (response size: %d)", senderAD, len(keys))
}
- if retryCount > 3 {
- cli.Log.Debugf("Erasing existing session for %s due to retry receipt with count>3", receipt.Sender)
- err = cli.Store.Sessions.DeleteSession(receipt.Sender.SignalAddress().String())
- if err != nil {
- return fmt.Errorf("failed to delete session for %s: %w", senderAD, err)
- }
- }
}
encrypted, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, receipt.Sender, bundle)
if err != nil {
@@ -164,7 +172,7 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
attrs := waBinary.Attrs{
"to": node.Attrs["from"],
- "type": "text",
+ "type": getTypeFromMessage(msg),
"id": messageID,
"t": timestamp.Unix(),
}
@@ -180,18 +188,15 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
if edit, ok := node.Attrs["edit"]; ok {
attrs["edit"] = edit
}
- req := waBinary.Node{
- Tag: "message",
- Attrs: attrs,
- Content: []waBinary.Node{*encrypted},
- }
+ content := []waBinary.Node{*encrypted}
if includeDeviceIdentity {
- err = cli.appendDeviceIdentityNode(&req)
- if err != nil {
- return fmt.Errorf("failed to add device identity to retry message: %w", err)
- }
+ content = append(content, cli.makeDeviceIdentityNode())
}
- err = cli.sendNode(req)
+ err = cli.sendNode(waBinary.Node{
+ Tag: "message",
+ Attrs: attrs,
+ Content: content,
+ })
if err != nil {
return fmt.Errorf("failed to send retry message: %w", err)
}
diff --git a/vendor/go.mau.fi/whatsmeow/send.go b/vendor/go.mau.fi/whatsmeow/send.go
index 9e520fb0..7ef6649f 100644
--- a/vendor/go.mau.fi/whatsmeow/send.go
+++ b/vendor/go.mau.fi/whatsmeow/send.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Tulir Asokan
+// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"sort"
+ "strconv"
"strings"
"time"
@@ -65,7 +66,8 @@ func GenerateMessageID() types.MessageID {
// For other message types, you'll have to figure it out yourself. Looking at the protobuf schema
// in binary/proto/def.proto may be useful to find out all the allowed fields.
func (cli *Client) SendMessage(to types.JID, id types.MessageID, message *waProto.Message) (time.Time, error) {
- if to.AD {
+ isPeerMessage := to.User == cli.Store.ID.User
+ if to.AD && !isPeerMessage {
return time.Time{}, ErrRecipientADJID
}
@@ -73,23 +75,27 @@ func (cli *Client) SendMessage(to types.JID, id types.MessageID, message *waProt
id = GenerateMessageID()
}
- if cli.OneMessageAtATime {
- cli.messageSendLock.Lock()
- defer cli.messageSendLock.Unlock()
- }
+ // Sending multiple messages at a time can cause weird issues and makes it harder to retry safely
+ cli.messageSendLock.Lock()
+ defer cli.messageSendLock.Unlock()
- cli.addRecentMessage(to, id, message)
respChan := cli.waitResponse(id)
+ // Peer message retries aren't implemented yet
+ if !isPeerMessage {
+ cli.addRecentMessage(to, id, message)
+ }
var err error
var phash string
var data []byte
switch to.Server {
- case types.GroupServer:
+ case types.GroupServer, types.BroadcastServer:
phash, data, err = cli.sendGroup(to, id, message)
case types.DefaultUserServer:
- data, err = cli.sendDM(to, id, message)
- case types.BroadcastServer:
- err = ErrBroadcastListUnsupported
+ if isPeerMessage {
+ data, err = cli.sendPeerMessage(to, id, message)
+ } else {
+ data, err = cli.sendDM(to, id, message)
+ }
default:
err = fmt.Errorf("%w %s", ErrUnknownServer, to.Server)
}
@@ -99,13 +105,13 @@ func (cli *Client) SendMessage(to types.JID, id types.MessageID, message *waProt
}
resp := <-respChan
if isDisconnectNode(resp) {
- if cli.DebugDecodeBeforeSend && resp.Tag == "stream:error" && resp.GetChildByTag("xml-not-well-formed").Tag != "" {
- cli.Log.Debugf("Message that was interrupted by xml-not-well-formed: %s", base64.URLEncoding.EncodeToString(data))
+ resp, err = cli.retryFrame("message send", id, data, resp, nil, 0)
+ if err != nil {
+ return time.Time{}, err
}
- return time.Time{}, &DisconnectedError{Action: "message send", Node: resp}
}
ag := resp.AttrGetter()
- ts := time.Unix(ag.Int64("t"), 0)
+ ts := ag.UnixTime("t")
expectedPHash := ag.OptionalString("phash")
if len(expectedPHash) > 0 && phash != expectedPHash {
cli.Log.Warnf("Server returned different participant list hash when sending to %s. Some devices may not have received the message.", to)
@@ -135,6 +141,66 @@ func (cli *Client) RevokeMessage(chat types.JID, id types.MessageID) (time.Time,
})
}
+const (
+ DisappearingTimerOff = time.Duration(0)
+ DisappearingTimer24Hours = 24 * time.Hour
+ DisappearingTimer7Days = 7 * 24 * time.Hour
+ DisappearingTimer90Days = 90 * 24 * time.Hour
+)
+
+// ParseDisappearingTimerString parses common human-readable disappearing message timer strings into Duration values.
+// If the string doesn't look like one of the allowed values (0, 24h, 7d, 90d), the second return value is false.
+func ParseDisappearingTimerString(val string) (time.Duration, bool) {
+ switch strings.ReplaceAll(strings.ToLower(val), " ", "") {
+ case "0d", "0h", "0s", "0", "off":
+ return DisappearingTimerOff, true
+ case "1day", "day", "1d", "1", "24h", "24", "86400s", "86400":
+ return DisappearingTimer24Hours, true
+ case "1week", "week", "7d", "7", "168h", "168", "604800s", "604800":
+ return DisappearingTimer7Days, true
+ case "3months", "3m", "3mo", "90d", "90", "2160h", "2160", "7776000s", "7776000":
+ return DisappearingTimer90Days, true
+ default:
+ return 0, false
+ }
+}
+
+// SetDisappearingTimer sets the disappearing timer in a chat. Both private chats and groups are supported, but they're
+// set with different methods.
+//
+// Note that while this function allows passing non-standard durations, official WhatsApp apps will ignore those,
+// and in groups the server will just reject the change. You can use the DisappearingTimer<Duration> constants for convenience.
+//
+// In groups, the server will echo the change as a notification, so it'll show up as a *events.GroupInfo update.
+func (cli *Client) SetDisappearingTimer(chat types.JID, timer time.Duration) (err error) {
+ switch chat.Server {
+ case types.DefaultUserServer:
+ _, err = cli.SendMessage(chat, "", &waProto.Message{
+ ProtocolMessage: &waProto.ProtocolMessage{
+ Type: waProto.ProtocolMessage_EPHEMERAL_SETTING.Enum(),
+ EphemeralExpiration: proto.Uint32(uint32(timer.Seconds())),
+ },
+ })
+ case types.GroupServer:
+ if timer == 0 {
+ _, err = cli.sendGroupIQ(iqSet, chat, waBinary.Node{Tag: "not_ephemeral"})
+ } else {
+ _, err = cli.sendGroupIQ(iqSet, chat, waBinary.Node{
+ Tag: "ephemeral",
+ Attrs: waBinary.Attrs{
+ "expiration": strconv.Itoa(int(timer.Seconds())),
+ },
+ })
+ if errors.Is(err, ErrIQBadRequest) {
+ err = wrapIQError(ErrInvalidDisappearingTimer, err)
+ }
+ }
+ default:
+ err = fmt.Errorf("can't set disappearing time in a %s chat", chat.Server)
+ }
+ return
+}
+
func participantListHashV2(participants []types.JID) string {
participantsStrings := make([]string, len(participants))
for i, part := range participants {
@@ -147,9 +213,18 @@ func participantListHashV2(participants []types.JID) string {
}
func (cli *Client) sendGroup(to types.JID, id types.MessageID, message *waProto.Message) (string, []byte, error) {
- participants, err := cli.getGroupMembers(to)
- if err != nil {
- return "", nil, fmt.Errorf("failed to get group members: %w", err)
+ var participants []types.JID
+ var err error
+ if to.Server == types.GroupServer {
+ participants, err = cli.getGroupMembers(to)
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to get group members: %w", err)
+ }
+ } else {
+ participants, err = cli.getBroadcastListParticipants(to)
+ if err != nil {
+ return "", nil, fmt.Errorf("failed to get broadcast list members: %w", err)
+ }
}
plaintext, _, err := marshalMessage(to, message)
@@ -194,13 +269,25 @@ func (cli *Client) sendGroup(to types.JID, id types.MessageID, message *waProto.
Attrs: waBinary.Attrs{"v": "2", "type": "skmsg"},
})
- data, err := cli.sendNodeDebug(*node)
+ data, err := cli.sendNodeAndGetData(*node)
if err != nil {
return "", nil, fmt.Errorf("failed to send message node: %w", err)
}
return phash, data, nil
}
+func (cli *Client) sendPeerMessage(to types.JID, id types.MessageID, message *waProto.Message) ([]byte, error) {
+ node, err := cli.preparePeerMessageNode(to, id, message)
+ if err != nil {
+ return nil, err
+ }
+ data, err := cli.sendNodeAndGetData(*node)
+ if err != nil {
+ return nil, fmt.Errorf("failed to send message node: %w", err)
+ }
+ return data, nil
+}
+
func (cli *Client) sendDM(to types.JID, id types.MessageID, message *waProto.Message) ([]byte, error) {
messagePlaintext, deviceSentMessagePlaintext, err := marshalMessage(to, message)
if err != nil {
@@ -211,46 +298,102 @@ func (cli *Client) sendDM(to types.JID, id types.MessageID, message *waProto.Mes
if err != nil {
return nil, err
}
- data, err := cli.sendNodeDebug(*node)
+ data, err := cli.sendNodeAndGetData(*node)
if err != nil {
return nil, fmt.Errorf("failed to send message node: %w", err)
}
return data, nil
}
+func getTypeFromMessage(msg *waProto.Message) string {
+ switch {
+ case msg.ViewOnceMessage != nil:
+ return getTypeFromMessage(msg.ViewOnceMessage.Message)
+ case msg.EphemeralMessage != nil:
+ return getTypeFromMessage(msg.EphemeralMessage.Message)
+ case msg.ReactionMessage != nil:
+ return "reaction"
+ 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 getEditAttribute(msg *waProto.Message) string {
+ if msg.ProtocolMessage != nil && msg.GetProtocolMessage().GetType() == waProto.ProtocolMessage_REVOKE && msg.GetProtocolMessage().GetKey() != nil {
+ if msg.GetProtocolMessage().GetKey().GetFromMe() {
+ return "7"
+ } else {
+ return "8"
+ }
+ } else if msg.ReactionMessage != nil && msg.ReactionMessage.GetText() == "" {
+ return "7"
+ }
+ return ""
+}
+
+func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, message *waProto.Message) (*waBinary.Node, error) {
+ attrs := waBinary.Attrs{
+ "id": id,
+ "type": "text",
+ "category": "peer",
+ "to": to,
+ }
+ if message.GetProtocolMessage().GetType() == waProto.ProtocolMessage_APP_STATE_SYNC_KEY_REQUEST {
+ attrs["push_priority"] = "high"
+ }
+ plaintext, err := proto.Marshal(message)
+ if err != nil {
+ err = fmt.Errorf("failed to marshal message: %w", err)
+ return nil, err
+ }
+ encrypted, isPreKey, err := cli.encryptMessageForDevice(plaintext, to, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to encrypt peer message for %s: %v", to, err)
+ }
+ content := []waBinary.Node{*encrypted}
+ if isPreKey {
+ content = append(content, cli.makeDeviceIdentityNode())
+ }
+ return &waBinary.Node{
+ Tag: "message",
+ Attrs: attrs,
+ Content: content,
+ }, nil
+}
+
func (cli *Client) prepareMessageNode(to types.JID, id types.MessageID, message *waProto.Message, participants []types.JID, plaintext, dsmPlaintext []byte) (*waBinary.Node, []types.JID, error) {
allDevices, err := cli.GetUserDevices(participants)
if err != nil {
return nil, nil, fmt.Errorf("failed to get device list: %w", err)
}
- participantNodes, includeIdentity := cli.encryptMessageForDevices(allDevices, id, plaintext, dsmPlaintext)
- node := waBinary.Node{
- Tag: "message",
- Attrs: waBinary.Attrs{
- "id": id,
- "type": "text",
- "to": to,
- },
- Content: []waBinary.Node{{
- Tag: "participants",
- Content: participantNodes,
- }},
- }
- if message.ProtocolMessage != nil && message.GetProtocolMessage().GetType() == waProto.ProtocolMessage_REVOKE && message.GetProtocolMessage().GetKey() != nil {
- if message.GetProtocolMessage().GetKey().GetFromMe() {
- node.Attrs["edit"] = "7"
- } else {
- node.Attrs["edit"] = "8"
- }
+ attrs := waBinary.Attrs{
+ "id": id,
+ "type": getTypeFromMessage(message),
+ "to": to,
}
+ if editAttr := getEditAttribute(message); editAttr != "" {
+ attrs["edit"] = editAttr
+ }
+
+ participantNodes, includeIdentity := cli.encryptMessageForDevices(allDevices, id, plaintext, dsmPlaintext)
+ content := []waBinary.Node{{
+ Tag: "participants",
+ Content: participantNodes,
+ }}
if includeIdentity {
- err := cli.appendDeviceIdentityNode(&node)
- if err != nil {
- return nil, nil, err
- }
+ content = append(content, cli.makeDeviceIdentityNode())
}
- return &node, allDevices, nil
+ return &waBinary.Node{
+ Tag: "message",
+ Attrs: attrs,
+ Content: content,
+ }, allDevices, nil
}
func marshalMessage(to types.JID, message *waProto.Message) (plaintext, dsmPlaintext []byte, err error) {
@@ -276,16 +419,15 @@ func marshalMessage(to types.JID, message *waProto.Message) (plaintext, dsmPlain
return
}
-func (cli *Client) appendDeviceIdentityNode(node *waBinary.Node) error {
+func (cli *Client) makeDeviceIdentityNode() waBinary.Node {
deviceIdentity, err := proto.Marshal(cli.Store.Account)
if err != nil {
- return fmt.Errorf("failed to marshal device identity: %w", err)
+ panic(fmt.Errorf("failed to marshal device identity: %w", err))
}
- node.Content = append(node.GetChildren(), waBinary.Node{
+ return waBinary.Node{
Tag: "device-identity",
Content: deviceIdentity,
- })
- return nil
+ }
}
func (cli *Client) encryptMessageForDevices(allDevices []types.JID, id string, msgPlaintext, dsmPlaintext []byte) ([]waBinary.Node, bool) {
diff --git a/vendor/go.mau.fi/whatsmeow/store/clientpayload.go b/vendor/go.mau.fi/whatsmeow/store/clientpayload.go
index 9a65f515..6ebf5d99 100644
--- a/vendor/go.mau.fi/whatsmeow/store/clientpayload.go
+++ b/vendor/go.mau.fi/whatsmeow/store/clientpayload.go
@@ -74,7 +74,7 @@ func (vc WAVersionContainer) ProtoAppVersion() *waProto.AppVersion {
}
// waVersion is the WhatsApp web client version
-var waVersion = WAVersionContainer{2, 2214, 12}
+var waVersion = WAVersionContainer{2, 2218, 8}
// waVersionHash is the md5 hash of a dot-separated waVersion
var waVersionHash [16]byte
@@ -122,7 +122,10 @@ var BaseClientPayload = &waProto.ClientPayload{
ConnectReason: waProto.ClientPayload_USER_ACTIVATED.Enum(),
}
-var CompanionProps = &waProto.CompanionProps{
+// Deprecated: renamed to DeviceProps
+var CompanionProps = DeviceProps
+
+var DeviceProps = &waProto.CompanionProps{
Os: proto.String("whatsmeow"),
Version: &waProto.AppVersion{
Primary: proto.Uint32(0),
@@ -134,10 +137,10 @@ var CompanionProps = &waProto.CompanionProps{
}
func SetOSInfo(name string, version [3]uint32) {
- CompanionProps.Os = &name
- CompanionProps.Version.Primary = &version[0]
- CompanionProps.Version.Secondary = &version[1]
- CompanionProps.Version.Tertiary = &version[2]
+ DeviceProps.Os = &name
+ DeviceProps.Version.Primary = &version[0]
+ DeviceProps.Version.Secondary = &version[1]
+ DeviceProps.Version.Tertiary = &version[2]
BaseClientPayload.UserAgent.OsVersion = proto.String(fmt.Sprintf("%d.%d.%d", version[0], version[1], version[2]))
BaseClientPayload.UserAgent.OsBuildNumber = BaseClientPayload.UserAgent.OsVersion
}
@@ -148,16 +151,16 @@ func (device *Device) getRegistrationPayload() *waProto.ClientPayload {
binary.BigEndian.PutUint32(regID, device.RegistrationID)
preKeyID := make([]byte, 4)
binary.BigEndian.PutUint32(preKeyID, device.SignedPreKey.KeyID)
- companionProps, _ := proto.Marshal(CompanionProps)
- payload.RegData = &waProto.CompanionRegData{
- ERegid: regID,
- EKeytype: []byte{ecc.DjbType},
- EIdent: device.IdentityKey.Pub[:],
- ESkeyId: preKeyID[1:],
- ESkeyVal: device.SignedPreKey.Pub[:],
- ESkeySig: device.SignedPreKey.Signature[:],
- BuildHash: waVersionHash[:],
- CompanionProps: companionProps,
+ deviceProps, _ := proto.Marshal(DeviceProps)
+ payload.DevicePairingData = &waProto.DevicePairingRegistrationData{
+ ERegid: regID,
+ EKeytype: []byte{ecc.DjbType},
+ EIdent: device.IdentityKey.Pub[:],
+ ESkeyId: preKeyID[1:],
+ ESkeyVal: device.SignedPreKey.Pub[:],
+ ESkeySig: device.SignedPreKey.Signature[:],
+ BuildHash: waVersionHash[:],
+ DeviceProps: deviceProps,
}
payload.Passive = proto.Bool(false)
return payload
diff --git a/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go b/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go
index 3150cfec..b7c0a7c4 100644
--- a/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go
+++ b/vendor/go.mau.fi/whatsmeow/store/sqlstore/container.go
@@ -78,7 +78,7 @@ func NewWithDB(db *sql.DB, dialect string, log waLog.Logger) *Container {
const getAllDevicesQuery = `
SELECT jid, registration_id, noise_key, identity_key,
signed_pre_key, signed_pre_key_id, signed_pre_key_sig,
- adv_key, adv_details, adv_account_sig, adv_device_sig,
+ adv_key, adv_details, adv_account_sig, adv_account_sig_key, adv_device_sig,
platform, business_name, push_name
FROM whatsmeow_device
`
@@ -100,7 +100,7 @@ func (c *Container) scanDevice(row scannable) (*store.Device, error) {
err := row.Scan(
&device.ID, &device.RegistrationID, &noisePriv, &identityPriv,
&preKeyPriv, &device.SignedPreKey.KeyID, &preKeySig,
- &device.AdvSecretKey, &account.Details, &account.AccountSignature, &account.DeviceSignature,
+ &device.AdvSecretKey, &account.Details, &account.AccountSignature, &account.AccountSignatureKey, &account.DeviceSignature,
&device.Platform, &device.BusinessName, &device.PushName)
if err != nil {
return nil, fmt.Errorf("failed to scan session: %w", err)
@@ -178,9 +178,9 @@ const (
insertDeviceQuery = `
INSERT INTO whatsmeow_device (jid, registration_id, noise_key, identity_key,
signed_pre_key, signed_pre_key_id, signed_pre_key_sig,
- adv_key, adv_details, adv_account_sig, adv_device_sig,
+ adv_key, adv_details, adv_account_sig, adv_account_sig_key, adv_device_sig,
platform, business_name, push_name)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
ON CONFLICT (jid) DO UPDATE SET platform=$12, business_name=$13, push_name=$14
`
deleteDeviceQuery = `DELETE FROM whatsmeow_device WHERE jid=$1`
@@ -222,7 +222,7 @@ func (c *Container) PutDevice(device *store.Device) error {
_, err := c.db.Exec(insertDeviceQuery,
device.ID.String(), device.RegistrationID, device.NoiseKey.Priv[:], device.IdentityKey.Priv[:],
device.SignedPreKey.Priv[:], device.SignedPreKey.KeyID, device.SignedPreKey.Signature[:],
- device.AdvSecretKey, device.Account.Details, device.Account.AccountSignature, device.Account.DeviceSignature,
+ device.AdvSecretKey, device.Account.Details, device.Account.AccountSignature, device.Account.AccountSignatureKey, device.Account.DeviceSignature,
device.Platform, device.BusinessName, device.PushName)
if !device.Initialized {
diff --git a/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go b/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go
index b98f2d61..37bd7c29 100644
--- a/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go
+++ b/vendor/go.mau.fi/whatsmeow/store/sqlstore/upgrade.go
@@ -16,7 +16,7 @@ type upgradeFunc func(*sql.Tx, *Container) error
//
// This may be of use if you want to manage the database fully manually, but in most cases you
// should just call Container.Upgrade to let the library handle everything.
-var Upgrades = [...]upgradeFunc{upgradeV1}
+var Upgrades = [...]upgradeFunc{upgradeV1, upgradeV2}
func (c *Container) getVersion() (int, error) {
_, err := c.db.Exec("CREATE TABLE IF NOT EXISTS whatsmeow_version (version INTEGER)")
@@ -56,6 +56,7 @@ func (c *Container) Upgrade() error {
}
migrateFunc := Upgrades[version]
+ c.log.Infof("Upgrading database to v%d", version+1)
err = migrateFunc(tx, c)
if err != nil {
_ = tx.Rollback()
@@ -212,3 +213,36 @@ func upgradeV1(tx *sql.Tx, _ *Container) error {
}
return nil
}
+
+const fillSigKeyPostgres = `
+UPDATE whatsmeow_device SET adv_account_sig_key=(
+ SELECT identity
+ FROM whatsmeow_identity_keys
+ WHERE our_jid=whatsmeow_device.jid
+ AND their_id=concat(split_part(whatsmeow_device.jid, '.', 1), ':0')
+);
+DELETE FROM whatsmeow_device WHERE adv_account_sig_key IS NULL;
+ALTER TABLE whatsmeow_device ALTER COLUMN adv_account_sig_key SET NOT NULL;
+`
+
+const fillSigKeySQLite = `
+UPDATE whatsmeow_device SET adv_account_sig_key=(
+ SELECT identity
+ FROM whatsmeow_identity_keys
+ WHERE our_jid=whatsmeow_device.jid
+ AND their_id=substr(whatsmeow_device.jid, 0, instr(whatsmeow_device.jid, '.')) || ':0'
+)
+`
+
+func upgradeV2(tx *sql.Tx, container *Container) error {
+ _, err := tx.Exec("ALTER TABLE whatsmeow_device ADD COLUMN adv_account_sig_key bytea CHECK ( length(adv_account_sig_key) = 32 )")
+ if err != nil {
+ return err
+ }
+ if container.dialect == "postgres" {
+ _, err = tx.Exec(fillSigKeyPostgres)
+ } else {
+ _, err = tx.Exec(fillSigKeySQLite)
+ }
+ return err
+}
diff --git a/vendor/go.mau.fi/whatsmeow/types/events/events.go b/vendor/go.mau.fi/whatsmeow/types/events/events.go
index 8b9e9a4f..92ae035f 100644
--- a/vendor/go.mau.fi/whatsmeow/types/events/events.go
+++ b/vendor/go.mau.fi/whatsmeow/types/events/events.go
@@ -55,9 +55,25 @@ type QRScannedWithoutMultidevice struct{}
// at this point, which is why this event doesn't contain any data.
type Connected struct{}
+// KeepAliveTimeout is emitted when the keepalive ping request to WhatsApp web servers times out.
+//
+// Currently, there's no automatic handling for these, but it's expected that the TCP connection will
+// either start working again or notice it's dead on its own eventually. Clients may use this event to
+// decide to force a disconnect+reconnect faster.
+type KeepAliveTimeout struct {
+ ErrorCount int
+ LastSuccess time.Time
+}
+
+// KeepAliveRestored is emitted if the keepalive pings start working again after some KeepAliveTimeout events.
+// Note that if the websocket disconnects before the pings start working, this event will not be emitted.
+type KeepAliveRestored struct{}
+
// LoggedOut is emitted when the client has been unpaired from the phone.
//
// This can happen while connected (stream:error messages) or right after connecting (connect failure messages).
+//
+// This will not be emitted when the logout is initiated by this client (using Client.LogOut()).
type LoggedOut struct {
// OnConnect is true if the event was triggered by a connect failure message.
// If it's false, the event was triggered by a stream:error message.
@@ -205,6 +221,27 @@ type Message struct {
RawMessage *waProto.Message
}
+// UnwrapRaw fills the Message, IsEphemeral and IsViewOnce fields based on the raw message in the RawMessage field.
+func (evt *Message) UnwrapRaw() *Message {
+ evt.Message = evt.RawMessage
+ if evt.Message.GetDeviceSentMessage().GetMessage() != nil {
+ evt.Info.DeviceSentMeta = &types.DeviceSentMeta{
+ DestinationJID: evt.Message.GetDeviceSentMessage().GetDestinationJid(),
+ Phash: evt.Message.GetDeviceSentMessage().GetPhash(),
+ }
+ evt.Message = evt.Message.GetDeviceSentMessage().GetMessage()
+ }
+ if evt.Message.GetEphemeralMessage().GetMessage() != nil {
+ evt.Message = evt.Message.GetEphemeralMessage().GetMessage()
+ evt.IsEphemeral = true
+ }
+ if evt.Message.GetViewOnceMessage().GetMessage() != nil {
+ evt.Message = evt.Message.GetViewOnceMessage().GetMessage()
+ evt.IsViewOnce = true
+ }
+ return evt
+}
+
// ReceiptType represents the type of a Receipt event.
type ReceiptType string
diff --git a/vendor/go.mau.fi/whatsmeow/types/user.go b/vendor/go.mau.fi/whatsmeow/types/user.go
index 523d70d1..1aff91e8 100644
--- a/vendor/go.mau.fi/whatsmeow/types/user.go
+++ b/vendor/go.mau.fi/whatsmeow/types/user.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Tulir Asokan
+// Copyright (c) 2022 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -94,3 +94,23 @@ type PrivacySettings struct {
Profile PrivacySetting
ReadReceipts PrivacySetting
}
+
+// StatusPrivacyType is the type of list in StatusPrivacy.
+type StatusPrivacyType string
+
+const (
+ // StatusPrivacyTypeContacts means statuses are sent to all contacts.
+ StatusPrivacyTypeContacts StatusPrivacyType = "contacts"
+ // StatusPrivacyTypeBlacklist means statuses are sent to all contacts, except the ones on the list.
+ StatusPrivacyTypeBlacklist StatusPrivacyType = "blacklist"
+ // StatusPrivacyTypeWhitelist means statuses are only sent to users on the list.
+ StatusPrivacyTypeWhitelist StatusPrivacyType = "whitelist"
+)
+
+// StatusPrivacy contains the settings for who to send status messages to by default.
+type StatusPrivacy struct {
+ Type StatusPrivacyType
+ List []JID
+
+ IsDefault bool
+}
diff --git a/vendor/go.mau.fi/whatsmeow/user.go b/vendor/go.mau.fi/whatsmeow/user.go
index fea80521..bbddd405 100644
--- a/vendor/go.mau.fi/whatsmeow/user.go
+++ b/vendor/go.mau.fi/whatsmeow/user.go
@@ -123,22 +123,19 @@ func (cli *Client) GetUserInfo(jids []types.JID) (map[types.JID]types.UserInfo,
if child.Tag != "user" || !jidOK {
continue
}
+ var info types.UserInfo
verifiedName, err := parseVerifiedName(child.GetChildByTag("business"))
if err != nil {
cli.Log.Warnf("Failed to parse %s's verified name details: %v", jid, err)
}
status, _ := child.GetChildByTag("status").Content.([]byte)
- pictureID, _ := child.GetChildByTag("picture").Attrs["id"].(string)
- devices := parseDeviceList(jid.User, child.GetChildByTag("devices"))
- respData[jid] = types.UserInfo{
- VerifiedName: verifiedName,
- Status: string(status),
- PictureID: pictureID,
- Devices: devices,
- }
+ info.Status = string(status)
+ info.PictureID, _ = child.GetChildByTag("picture").Attrs["id"].(string)
+ info.Devices = parseDeviceList(jid.User, child.GetChildByTag("devices"))
if verifiedName != nil {
cli.updateBusinessName(jid, verifiedName.Details.GetVerifiedName())
}
+ respData[jid] = info
}
return respData, nil
}