mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-07-03 07:17:44 +00:00
Update dependencies (#1841)
This commit is contained in:
9
vendor/go.mau.fi/whatsmeow/README.md
vendored
9
vendor/go.mau.fi/whatsmeow/README.md
vendored
@ -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
|
||||
|
44
vendor/go.mau.fi/whatsmeow/appstate.go
vendored
44
vendor/go.mau.fi/whatsmeow/appstate.go
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
|
33
vendor/go.mau.fi/whatsmeow/appstate/keys.go
vendored
33
vendor/go.mau.fi/whatsmeow/appstate/keys.go
vendored
@ -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
|
||||
}
|
||||
|
21
vendor/go.mau.fi/whatsmeow/binary/attrs.go
vendored
21
vendor/go.mau.fi/whatsmeow/binary/attrs.go
vendored
@ -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
|
||||
|
264
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
vendored
264
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.go
vendored
@ -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,6 +18252,109 @@ func (x *UserAgent) GetDeviceBoard() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type DevicePairingRegistrationData 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"`
|
||||
DeviceProps []byte `protobuf:"bytes,8,opt,name=deviceProps" json:"deviceProps,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) Reset() {
|
||||
*x = DevicePairingRegistrationData{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_binary_proto_def_proto_msgTypes[182]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DevicePairingRegistrationData) ProtoMessage() {}
|
||||
|
||||
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))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DevicePairingRegistrationData.ProtoReflect.Descriptor instead.
|
||||
func (*DevicePairingRegistrationData) Descriptor() ([]byte, []int) {
|
||||
return file_binary_proto_def_proto_rawDescGZIP(), []int{182}
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetERegid() []byte {
|
||||
if x != nil {
|
||||
return x.ERegid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetEKeytype() []byte {
|
||||
if x != nil {
|
||||
return x.EKeytype
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetEIdent() []byte {
|
||||
if x != nil {
|
||||
return x.EIdent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetESkeyId() []byte {
|
||||
if x != nil {
|
||||
return x.ESkeyId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetESkeyVal() []byte {
|
||||
if x != nil {
|
||||
return x.ESkeyVal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetESkeySig() []byte {
|
||||
if x != nil {
|
||||
return x.ESkeySig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetBuildHash() []byte {
|
||||
if x != nil {
|
||||
return x.BuildHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DevicePairingRegistrationData) GetDeviceProps() []byte {
|
||||
if x != nil {
|
||||
return x.DeviceProps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DNSSource struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -18248,7 +18367,7 @@ type DNSSource struct {
|
||||
func (x *DNSSource) Reset() {
|
||||
*x = DNSSource{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_binary_proto_def_proto_msgTypes[182]
|
||||
mi := &file_binary_proto_def_proto_msgTypes[183]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@ -18261,7 +18380,7 @@ func (x *DNSSource) String() string {
|
||||
func (*DNSSource) ProtoMessage() {}
|
||||
|
||||
func (x *DNSSource) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_binary_proto_def_proto_msgTypes[182]
|
||||
mi := &file_binary_proto_def_proto_msgTypes[183]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@ -18274,7 +18393,7 @@ func (x *DNSSource) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DNSSource.ProtoReflect.Descriptor instead.
|
||||
func (*DNSSource) Descriptor() ([]byte, []int) {
|
||||
return file_binary_proto_def_proto_rawDescGZIP(), []int{182}
|
||||
return file_binary_proto_def_proto_rawDescGZIP(), []int{183}
|
||||
}
|
||||
|
||||
func (x *DNSSource) GetDnsMethod() DNSSource_DNSSourceDNSResolutionMethod {
|
||||
@ -18291,109 +18410,6 @@ func (x *DNSSource) GetAppCached() bool {
|
||||
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 {
|
||||
if x != nil {
|
||||
return x.ERegid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetEKeytype() []byte {
|
||||
if x != nil {
|
||||
return x.EKeytype
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetEIdent() []byte {
|
||||
if x != nil {
|
||||
return x.EIdent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetESkeyId() []byte {
|
||||
if x != nil {
|
||||
return x.ESkeyId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetESkeyVal() []byte {
|
||||
if x != nil {
|
||||
return x.ESkeyVal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetESkeySig() []byte {
|
||||
if x != nil {
|
||||
return x.ESkeySig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetBuildHash() []byte {
|
||||
if x != nil {
|
||||
return x.BuildHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *CompanionRegData) GetCompanionProps() []byte {
|
||||
if x != nil {
|
||||
return x.CompanionProps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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:
|
||||
|
BIN
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
vendored
BIN
vendor/go.mau.fi/whatsmeow/binary/proto/def.pb.raw
vendored
Binary file not shown.
@ -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;
|
||||
|
138
vendor/go.mau.fi/whatsmeow/broadcast.go
vendored
Normal file
138
vendor/go.mau.fi/whatsmeow/broadcast.go
vendored
Normal file
@ -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
|
||||
}
|
4
vendor/go.mau.fi/whatsmeow/call.go
vendored
4
vendor/go.mau.fi/whatsmeow/call.go
vendored
@ -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"),
|
||||
}
|
||||
|
118
vendor/go.mau.fi/whatsmeow/client.go
vendored
118
vendor/go.mau.fi/whatsmeow/client.go
vendored
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}()
|
||||
}
|
||||
|
||||
|
12
vendor/go.mau.fi/whatsmeow/download.go
vendored
12
vendor/go.mau.fi/whatsmeow/download.go
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
5
vendor/go.mau.fi/whatsmeow/errors.go
vendored
5
vendor/go.mau.fi/whatsmeow/errors.go
vendored
@ -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"}
|
||||
|
11
vendor/go.mau.fi/whatsmeow/group.go
vendored
11
vendor/go.mau.fi/whatsmeow/group.go
vendored
@ -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":
|
||||
|
8
vendor/go.mau.fi/whatsmeow/internals.go
vendored
8
vendor/go.mau.fi/whatsmeow/internals.go
vendored
@ -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)
|
||||
}
|
||||
|
28
vendor/go.mau.fi/whatsmeow/keepalive.go
vendored
28
vendor/go.mau.fi/whatsmeow/keepalive.go
vendored
@ -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
|
||||
}
|
||||
|
36
vendor/go.mau.fi/whatsmeow/mediaretry.go
vendored
36
vendor/go.mau.fi/whatsmeow/mediaretry.go
vendored
@ -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()
|
||||
|
104
vendor/go.mau.fi/whatsmeow/message.go
vendored
104
vendor/go.mau.fi/whatsmeow/message.go
vendored
@ -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")
|
||||
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()
|
||||
}
|
||||
ts, ok := node.Attrs["t"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("didn't find valid `t` (timestamp) attribute in message")
|
||||
}
|
||||
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) {
|
||||
|
5
vendor/go.mau.fi/whatsmeow/notification.go
vendored
5
vendor/go.mau.fi/whatsmeow/notification.go
vendored
@ -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
|
||||
|
4
vendor/go.mau.fi/whatsmeow/pair.go
vendored
4
vendor/go.mau.fi/whatsmeow/pair.go
vendored
@ -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")
|
||||
|
3
vendor/go.mau.fi/whatsmeow/presence.go
vendored
3
vendor/go.mau.fi/whatsmeow/presence.go
vendored
@ -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)
|
||||
|
2
vendor/go.mau.fi/whatsmeow/receipt.go
vendored
2
vendor/go.mau.fi/whatsmeow/receipt.go
vendored
@ -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")
|
||||
|
79
vendor/go.mau.fi/whatsmeow/request.go
vendored
79
vendor/go.mau.fi/whatsmeow/request.go
vendored
@ -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
|
||||
}
|
||||
|
59
vendor/go.mau.fi/whatsmeow/retry.go
vendored
59
vendor/go.mau.fi/whatsmeow/retry.go
vendored
@ -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{
|
||||
content := []waBinary.Node{*encrypted}
|
||||
if includeDeviceIdentity {
|
||||
content = append(content, cli.makeDeviceIdentityNode())
|
||||
}
|
||||
err = cli.sendNode(waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
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)
|
||||
}
|
||||
}
|
||||
err = cli.sendNode(req)
|
||||
Content: content,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send retry message: %w", err)
|
||||
}
|
||||
|
238
vendor/go.mau.fi/whatsmeow/send.go
vendored
238
vendor/go.mau.fi/whatsmeow/send.go
vendored
@ -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,
|
||||
}},
|
||||
attrs := waBinary.Attrs{
|
||||
"id": id,
|
||||
"type": getTypeFromMessage(message),
|
||||
"to": to,
|
||||
}
|
||||
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"
|
||||
}
|
||||
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) {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
22
vendor/go.mau.fi/whatsmeow/types/user.go
vendored
22
vendor/go.mau.fi/whatsmeow/types/user.go
vendored
@ -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
|
||||
}
|
||||
|
13
vendor/go.mau.fi/whatsmeow/user.go
vendored
13
vendor/go.mau.fi/whatsmeow/user.go
vendored
@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user