forked from lug/matterbridge
Update dependencies and build to go1.22 (#2113)
* Update dependencies and build to go1.22 * Fix api changes wrt to dependencies * Update golangci config
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
*.pb.go linguist-generated=true
|
||||
*.pb.raw binary linguist-generated=true
|
||||
@@ -0,0 +1,18 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
exclude_types: [markdown]
|
||||
exclude: LICENSE|def.proto
|
||||
- id: end-of-file-fixer
|
||||
exclude: LICENSE|def.proto
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-imports-repo
|
||||
args: ["-w"]
|
||||
- id: go-vet-repo-mod
|
||||
+35
@@ -223,6 +223,41 @@ func (cli *Client) dispatchAppState(mutation appstate.Mutation, fullSync bool, e
|
||||
Action: mutation.Action.GetUserStatusMuteAction(),
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
case appstate.IndexLabelEdit:
|
||||
act := mutation.Action.GetLabelEditAction()
|
||||
eventToDispatch = &events.LabelEdit{
|
||||
Timestamp: ts,
|
||||
LabelID: mutation.Index[1],
|
||||
Action: act,
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
case appstate.IndexLabelAssociationChat:
|
||||
if len(mutation.Index) < 3 {
|
||||
return
|
||||
}
|
||||
jid, _ = types.ParseJID(mutation.Index[2])
|
||||
act := mutation.Action.GetLabelAssociationAction()
|
||||
eventToDispatch = &events.LabelAssociationChat{
|
||||
JID: jid,
|
||||
Timestamp: ts,
|
||||
LabelID: mutation.Index[1],
|
||||
Action: act,
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
case appstate.IndexLabelAssociationMessage:
|
||||
if len(mutation.Index) < 6 {
|
||||
return
|
||||
}
|
||||
jid, _ = types.ParseJID(mutation.Index[2])
|
||||
act := mutation.Action.GetLabelAssociationAction()
|
||||
eventToDispatch = &events.LabelAssociationMessage{
|
||||
JID: jid,
|
||||
Timestamp: ts,
|
||||
LabelID: mutation.Index[1],
|
||||
MessageID: mutation.Index[3],
|
||||
Action: act,
|
||||
FromFullSync: fullSync,
|
||||
}
|
||||
}
|
||||
if storeUpdateError != nil {
|
||||
cli.Log.Errorf("Failed to update device store after app state mutation: %v", storeUpdateError)
|
||||
|
||||
+90
@@ -123,6 +123,96 @@ func BuildArchive(target types.JID, archive bool, lastMessageTimestamp time.Time
|
||||
return result
|
||||
}
|
||||
|
||||
func newLabelChatMutation(target types.JID, labelID string, labeled bool) MutationInfo {
|
||||
return MutationInfo{
|
||||
Index: []string{IndexLabelAssociationChat, labelID, target.String()},
|
||||
Version: 3,
|
||||
Value: &waProto.SyncActionValue{
|
||||
LabelAssociationAction: &waProto.LabelAssociationAction{
|
||||
Labeled: &labeled,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildLabelChat builds an app state patch for labeling or un(labeling) a chat.
|
||||
func BuildLabelChat(target types.JID, labelID string, labeled bool) PatchInfo {
|
||||
return PatchInfo{
|
||||
Type: WAPatchRegular,
|
||||
Mutations: []MutationInfo{
|
||||
newLabelChatMutation(target, labelID, labeled),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newLabelMessageMutation(target types.JID, labelID, messageID string, labeled bool) MutationInfo {
|
||||
return MutationInfo{
|
||||
Index: []string{IndexLabelAssociationMessage, labelID, target.String(), messageID, "0", "0"},
|
||||
Version: 3,
|
||||
Value: &waProto.SyncActionValue{
|
||||
LabelAssociationAction: &waProto.LabelAssociationAction{
|
||||
Labeled: &labeled,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildLabelMessage builds an app state patch for labeling or un(labeling) a message.
|
||||
func BuildLabelMessage(target types.JID, labelID, messageID string, labeled bool) PatchInfo {
|
||||
return PatchInfo{
|
||||
Type: WAPatchRegular,
|
||||
Mutations: []MutationInfo{
|
||||
newLabelMessageMutation(target, labelID, messageID, labeled),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newLabelEditMutation(labelID string, labelName string, labelColor int32, deleted bool) MutationInfo {
|
||||
return MutationInfo{
|
||||
Index: []string{IndexLabelEdit, labelID},
|
||||
Version: 3,
|
||||
Value: &waProto.SyncActionValue{
|
||||
LabelEditAction: &waProto.LabelEditAction{
|
||||
Name: &labelName,
|
||||
Color: &labelColor,
|
||||
Deleted: &deleted,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildLabelEdit builds an app state patch for editing a label.
|
||||
func BuildLabelEdit(labelID string, labelName string, labelColor int32, deleted bool) PatchInfo {
|
||||
return PatchInfo{
|
||||
Type: WAPatchRegular,
|
||||
Mutations: []MutationInfo{
|
||||
newLabelEditMutation(labelID, labelName, labelColor, deleted),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSettingPushNameMutation(pushName string) MutationInfo {
|
||||
return MutationInfo{
|
||||
Index: []string{IndexSettingPushName},
|
||||
Version: 1,
|
||||
Value: &waProto.SyncActionValue{
|
||||
PushNameSetting: &waProto.PushNameSetting{
|
||||
Name: &pushName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildSettingPushName builds an app state patch for setting the push name.
|
||||
func BuildSettingPushName(pushName string) PatchInfo {
|
||||
return PatchInfo{
|
||||
Type: WAPatchCriticalBlock,
|
||||
Mutations: []MutationInfo{
|
||||
newSettingPushNameMutation(pushName),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (proc *Processor) EncodePatch(keyID []byte, state HashState, patchInfo PatchInfo) ([]byte, error) {
|
||||
keys, err := proc.getAppStateKey(keyID)
|
||||
if err != nil {
|
||||
|
||||
+15
-12
@@ -37,18 +37,21 @@ var AllPatchNames = [...]WAPatchName{WAPatchCriticalBlock, WAPatchCriticalUnbloc
|
||||
|
||||
// Constants for the first part of app state indexes.
|
||||
const (
|
||||
IndexMute = "mute"
|
||||
IndexPin = "pin_v1"
|
||||
IndexArchive = "archive"
|
||||
IndexContact = "contact"
|
||||
IndexClearChat = "clearChat"
|
||||
IndexDeleteChat = "deleteChat"
|
||||
IndexStar = "star"
|
||||
IndexDeleteMessageForMe = "deleteMessageForMe"
|
||||
IndexMarkChatAsRead = "markChatAsRead"
|
||||
IndexSettingPushName = "setting_pushName"
|
||||
IndexSettingUnarchiveChats = "setting_unarchiveChats"
|
||||
IndexUserStatusMute = "userStatusMute"
|
||||
IndexMute = "mute"
|
||||
IndexPin = "pin_v1"
|
||||
IndexArchive = "archive"
|
||||
IndexContact = "contact"
|
||||
IndexClearChat = "clearChat"
|
||||
IndexDeleteChat = "deleteChat"
|
||||
IndexStar = "star"
|
||||
IndexDeleteMessageForMe = "deleteMessageForMe"
|
||||
IndexMarkChatAsRead = "markChatAsRead"
|
||||
IndexSettingPushName = "setting_pushName"
|
||||
IndexSettingUnarchiveChats = "setting_unarchiveChats"
|
||||
IndexUserStatusMute = "userStatusMute"
|
||||
IndexLabelEdit = "label_edit"
|
||||
IndexLabelAssociationChat = "label_jid"
|
||||
IndexLabelAssociationMessage = "label_message"
|
||||
)
|
||||
|
||||
type Processor struct {
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2024 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 (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/whatsmeow/binary/armadillo"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgTransport"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
|
||||
func (cli *Client) handleDecryptedArmadillo(info *types.MessageInfo, decrypted []byte, retryCount int) bool {
|
||||
dec, err := decodeArmadillo(decrypted)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to decode armadillo message from %s: %v", info.SourceString(), err)
|
||||
return false
|
||||
}
|
||||
dec.Info = *info
|
||||
dec.RetryCount = retryCount
|
||||
if dec.Transport.GetProtocol().GetAncillary().GetSkdm() != nil {
|
||||
if !info.IsGroup {
|
||||
cli.Log.Warnf("Got sender key distribution message in non-group chat from %s", info.Sender)
|
||||
} else {
|
||||
skdm := dec.Transport.GetProtocol().GetAncillary().GetSkdm()
|
||||
cli.handleSenderKeyDistributionMessage(info.Chat, info.Sender, skdm.AxolotlSenderKeyDistributionMessage)
|
||||
}
|
||||
}
|
||||
if dec.Message != nil {
|
||||
cli.dispatchEvent(&dec)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeArmadillo(data []byte) (dec events.FBMessage, err error) {
|
||||
var transport waMsgTransport.MessageTransport
|
||||
err = proto.Unmarshal(data, &transport)
|
||||
if err != nil {
|
||||
return dec, fmt.Errorf("failed to unmarshal transport: %w", err)
|
||||
}
|
||||
dec.Transport = &transport
|
||||
if transport.GetPayload() == nil {
|
||||
return
|
||||
}
|
||||
application, err := transport.GetPayload().Decode()
|
||||
if err != nil {
|
||||
return dec, fmt.Errorf("failed to unmarshal application: %w", err)
|
||||
}
|
||||
dec.Application = application
|
||||
if application.GetPayload() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch typedContent := application.GetPayload().GetContent().(type) {
|
||||
case *waMsgApplication.MessageApplication_Payload_CoreContent:
|
||||
err = fmt.Errorf("unsupported core content payload")
|
||||
case *waMsgApplication.MessageApplication_Payload_Signal:
|
||||
err = fmt.Errorf("unsupported signal payload")
|
||||
case *waMsgApplication.MessageApplication_Payload_ApplicationData:
|
||||
err = fmt.Errorf("unsupported application data payload")
|
||||
case *waMsgApplication.MessageApplication_Payload_SubProtocol:
|
||||
var protoMsg proto.Message
|
||||
var subData *waCommon.SubProtocol
|
||||
switch subProtocol := typedContent.SubProtocol.GetSubProtocol().(type) {
|
||||
case *waMsgApplication.MessageApplication_SubProtocolPayload_ConsumerMessage:
|
||||
dec.Message, err = subProtocol.Decode()
|
||||
case *waMsgApplication.MessageApplication_SubProtocolPayload_BusinessMessage:
|
||||
dec.Message = (*armadillo.Unsupported_BusinessApplication)(subProtocol.BusinessMessage)
|
||||
case *waMsgApplication.MessageApplication_SubProtocolPayload_PaymentMessage:
|
||||
dec.Message = (*armadillo.Unsupported_PaymentApplication)(subProtocol.PaymentMessage)
|
||||
case *waMsgApplication.MessageApplication_SubProtocolPayload_MultiDevice:
|
||||
dec.Message, err = subProtocol.Decode()
|
||||
case *waMsgApplication.MessageApplication_SubProtocolPayload_Voip:
|
||||
dec.Message = (*armadillo.Unsupported_Voip)(subProtocol.Voip)
|
||||
case *waMsgApplication.MessageApplication_SubProtocolPayload_Armadillo:
|
||||
dec.Message, err = subProtocol.Decode()
|
||||
default:
|
||||
return dec, fmt.Errorf("unsupported subprotocol type: %T", subProtocol)
|
||||
}
|
||||
if protoMsg != nil {
|
||||
err = proto.Unmarshal(subData.GetPayload(), protoMsg)
|
||||
if err != nil {
|
||||
return dec, fmt.Errorf("failed to unmarshal application subprotocol payload (%T v%d): %w", protoMsg, subData.GetVersion(), err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("unsupported application payload content type: %T", typedContent)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
e2ee.js
|
||||
@@ -0,0 +1,32 @@
|
||||
package armadilloutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
)
|
||||
|
||||
var ErrUnsupportedVersion = errors.New("unsupported subprotocol version")
|
||||
|
||||
func Unmarshal[T proto.Message](into T, msg *waCommon.SubProtocol, expectedVersion int32) (T, error) {
|
||||
if msg.GetVersion() != expectedVersion {
|
||||
return into, fmt.Errorf("%w %d in %T (expected %d)", ErrUnsupportedVersion, msg.GetVersion(), into, expectedVersion)
|
||||
}
|
||||
|
||||
err := proto.Unmarshal(msg.GetPayload(), into)
|
||||
return into, err
|
||||
}
|
||||
|
||||
func Marshal[T proto.Message](msg T, version int32) (*waCommon.SubProtocol, error) {
|
||||
payload, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &waCommon.SubProtocol{
|
||||
Payload: payload,
|
||||
Version: version,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package armadillo
|
||||
|
||||
import (
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waArmadilloApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waConsumerApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMultiDevice"
|
||||
)
|
||||
|
||||
type MessageApplicationSub interface {
|
||||
IsMessageApplicationSub()
|
||||
}
|
||||
|
||||
type Unsupported_BusinessApplication waCommon.SubProtocol
|
||||
type Unsupported_PaymentApplication waCommon.SubProtocol
|
||||
type Unsupported_Voip waCommon.SubProtocol
|
||||
|
||||
var (
|
||||
_ MessageApplicationSub = (*waConsumerApplication.ConsumerApplication)(nil) // 2
|
||||
_ MessageApplicationSub = (*Unsupported_BusinessApplication)(nil) // 3
|
||||
_ MessageApplicationSub = (*Unsupported_PaymentApplication)(nil) // 4
|
||||
_ MessageApplicationSub = (*waMultiDevice.MultiDevice)(nil) // 5
|
||||
_ MessageApplicationSub = (*Unsupported_Voip)(nil) // 6
|
||||
_ MessageApplicationSub = (*waArmadilloApplication.Armadillo)(nil) // 7
|
||||
)
|
||||
|
||||
func (*Unsupported_BusinessApplication) IsMessageApplicationSub() {}
|
||||
func (*Unsupported_PaymentApplication) IsMessageApplicationSub() {}
|
||||
func (*Unsupported_Voip) IsMessageApplicationSub() {}
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
cd $(dirname $0)
|
||||
set -euo pipefail
|
||||
if [[ ! -f "e2ee.js" ]]; then
|
||||
echo "Please download the encryption javascript file and save it to e2ee.js first"
|
||||
exit 1
|
||||
fi
|
||||
node parse-proto.js
|
||||
protoc --go_out=. --go_opt=paths=source_relative --go_opt=embed_raw=true */*.proto
|
||||
@@ -0,0 +1,351 @@
|
||||
///////////////////
|
||||
// JS EVALUATION //
|
||||
///////////////////
|
||||
|
||||
const protos = []
|
||||
const modules = {
|
||||
"$InternalEnum": {
|
||||
exports: {
|
||||
exports: function (data) {
|
||||
data.__enum__ = true
|
||||
return data
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function requireModule(name) {
|
||||
if (!modules[name]) {
|
||||
throw new Error(`Unknown requirement ${name}`)
|
||||
}
|
||||
return modules[name].exports
|
||||
}
|
||||
|
||||
function requireDefault(name) {
|
||||
return requireModule(name).exports
|
||||
}
|
||||
|
||||
function ignoreModule(name) {
|
||||
if (name === "WAProtoConst") {
|
||||
return false
|
||||
} else if (!name.endsWith(".pb")) {
|
||||
// Ignore any non-protobuf modules, except WAProtoConst above
|
||||
return true
|
||||
} else if (name.startsWith("MAWArmadillo") && (name.endsWith("TableSchema.pb") || name.endsWith("TablesSchema.pb"))) {
|
||||
// Ignore internal table schemas
|
||||
return true
|
||||
} else if (name === "WASignalLocalStorageProtocol.pb" || name === "WASignalWhisperTextProtocol.pb") {
|
||||
// Ignore standard signal protocol stuff
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function defineModule(name, dependencies, callback, unknownIntOrNull) {
|
||||
if (ignoreModule(name)) {
|
||||
return
|
||||
}
|
||||
const exports = {}
|
||||
if (dependencies.length > 0) {
|
||||
callback(null, requireDefault, null, requireModule, null, null, exports)
|
||||
} else {
|
||||
callback(null, requireDefault, null, requireModule, exports, exports)
|
||||
}
|
||||
modules[name] = {exports, dependencies}
|
||||
}
|
||||
|
||||
global.self = global
|
||||
global.__d = defineModule
|
||||
|
||||
require("./e2ee.js")
|
||||
|
||||
function dereference(obj, module, currentPath, next, ...remainder) {
|
||||
if (!next) {
|
||||
return obj
|
||||
}
|
||||
if (!obj.messages[next]) {
|
||||
obj.messages[next] = {messages: {}, enums: {}, __module__: module, __path__: currentPath, __name__: next}
|
||||
}
|
||||
return dereference(obj.messages[next], module, currentPath.concat([next]), ...remainder)
|
||||
}
|
||||
|
||||
function dereferenceSnake(obj, currentPath, path) {
|
||||
let next = path[0]
|
||||
path = path.slice(1)
|
||||
while (!obj.messages[next]) {
|
||||
if (path.length === 0) {
|
||||
return [obj, currentPath, next]
|
||||
}
|
||||
next += path[0]
|
||||
path = path.slice(1)
|
||||
}
|
||||
return dereferenceSnake(obj.messages[next], currentPath.concat([next]), path)
|
||||
}
|
||||
|
||||
function renameModule(name) {
|
||||
return name.replace(".pb", "")
|
||||
}
|
||||
|
||||
function renameDependencies(dependencies) {
|
||||
return dependencies
|
||||
.filter(name => name.endsWith(".pb"))
|
||||
.map(renameModule)
|
||||
.map(name => name === "WAProtocol" ? "WACommon" : name)
|
||||
}
|
||||
|
||||
function renameType(protoName, fieldName, field) {
|
||||
return fieldName
|
||||
}
|
||||
|
||||
for (const [name, module] of Object.entries(modules)) {
|
||||
if (!name.endsWith(".pb")) {
|
||||
continue
|
||||
} else if (!module.exports) {
|
||||
console.warn(name, "has no exports")
|
||||
continue
|
||||
}
|
||||
// Slightly hacky way to get rid of WAProtocol.pb and just use the MessageKey in WACommon
|
||||
if (name === "WAProtocol.pb") {
|
||||
if (Object.entries(module.exports).length > 1) {
|
||||
console.warn("WAProtocol.pb has more than one export")
|
||||
}
|
||||
module.exports["MessageKeySpec"].__name__ = "MessageKey"
|
||||
module.exports["MessageKeySpec"].__module__ = "WACommon"
|
||||
module.exports["MessageKeySpec"].__path__ = []
|
||||
continue
|
||||
}
|
||||
const proto = {
|
||||
__protobuf__: true,
|
||||
messages: {},
|
||||
enums: {},
|
||||
__name__: renameModule(name),
|
||||
dependencies: renameDependencies(module.dependencies),
|
||||
}
|
||||
const upperSnakeEnums = []
|
||||
for (const [name, field] of Object.entries(module.exports)) {
|
||||
const namePath = name.replace(/Spec$/, "").split("$")
|
||||
field.__name__ = renameType(proto.__name__, namePath[namePath.length - 1], field)
|
||||
namePath[namePath.length - 1] = field.__name__
|
||||
field.__path__ = namePath.slice(0, -1)
|
||||
field.__module__ = proto.__name__
|
||||
if (field.internalSpec) {
|
||||
dereference(proto, proto.__name__, [], ...namePath).message = field.internalSpec
|
||||
} else if (namePath.length === 1 && name.toUpperCase() === name) {
|
||||
upperSnakeEnums.push(field)
|
||||
} else {
|
||||
dereference(proto, proto.__name__, [], ...namePath.slice(0, -1)).enums[field.__name__] = field
|
||||
}
|
||||
}
|
||||
// Some enums have uppercase names, instead of capital case with $ separators.
|
||||
// For those, we need to find the right nesting location.
|
||||
for (const field of upperSnakeEnums) {
|
||||
field.__enum__ = true
|
||||
const [obj, path, name] = dereferenceSnake(proto, [], field.__name__.split("_").map(part => part[0] + part.slice(1).toLowerCase()))
|
||||
field.__path__ = path
|
||||
field.__name__ = name
|
||||
field.__module__ = proto.__name__
|
||||
obj.enums[name] = field
|
||||
}
|
||||
protos.push(proto)
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// PROTOBUF SCHEMA GENERATION //
|
||||
////////////////////////////////
|
||||
|
||||
function indent(lines, indent = "\t") {
|
||||
return lines.map(line => line ? `${indent}${line}` : "")
|
||||
}
|
||||
|
||||
function flattenWithBlankLines(...items) {
|
||||
return items
|
||||
.flatMap(item => item.length > 0 ? [item, [""]] : [])
|
||||
.slice(0, -1)
|
||||
.flatMap(item => item)
|
||||
}
|
||||
|
||||
function protoifyChildren(container) {
|
||||
return flattenWithBlankLines(
|
||||
...Object.values(container.enums).map(protoifyEnum),
|
||||
...Object.values(container.messages).map(protoifyMessage),
|
||||
)
|
||||
}
|
||||
|
||||
function protoifyEnum(enumDef) {
|
||||
const values = []
|
||||
const names = Object.fromEntries(Object.entries(enumDef).map(([name, value]) => [value, name]))
|
||||
if (!names["0"]) {
|
||||
if (names["-1"]) {
|
||||
enumDef[names["-1"]] = 0
|
||||
} else {
|
||||
// TODO add snake case
|
||||
values.push(`${enumDef.__name__.toUpperCase()}_UNKNOWN = 0;`)
|
||||
}
|
||||
}
|
||||
for (const [name, value] of Object.entries(enumDef)) {
|
||||
if (name.startsWith("__") && name.endsWith("__")) {
|
||||
continue
|
||||
}
|
||||
values.push(`${name} = ${value};`)
|
||||
}
|
||||
return [`enum ${enumDef.__name__} ` + "{", ...indent(values), "}"]
|
||||
}
|
||||
|
||||
const {TYPES, TYPE_MASK, FLAGS} = requireModule("WAProtoConst")
|
||||
|
||||
function fieldTypeName(typeID, typeRef, parentModule, parentPath) {
|
||||
switch (typeID) {
|
||||
case TYPES.INT32:
|
||||
return "int32"
|
||||
case TYPES.INT64:
|
||||
return "int64"
|
||||
case TYPES.UINT32:
|
||||
return "uint32"
|
||||
case TYPES.UINT64:
|
||||
return "uint64"
|
||||
case TYPES.SINT32:
|
||||
return "sint32"
|
||||
case TYPES.SINT64:
|
||||
return "sint64"
|
||||
case TYPES.BOOL:
|
||||
return "bool"
|
||||
case TYPES.ENUM:
|
||||
case TYPES.MESSAGE:
|
||||
let pathStartIndex = 0
|
||||
for (let i = 0; i < parentPath.length && i < typeRef.__path__.length; i++) {
|
||||
if (typeRef.__path__[i] === parentPath[i]) {
|
||||
pathStartIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
const namePath = []
|
||||
if (typeRef.__module__ !== parentModule) {
|
||||
namePath.push(typeRef.__module__)
|
||||
pathStartIndex = 0
|
||||
}
|
||||
namePath.push(...typeRef.__path__.slice(pathStartIndex))
|
||||
namePath.push(typeRef.__name__)
|
||||
return namePath.join(".")
|
||||
case TYPES.FIXED64:
|
||||
return "fixed64"
|
||||
case TYPES.SFIXED64:
|
||||
return "sfixed64"
|
||||
case TYPES.DOUBLE:
|
||||
return "double"
|
||||
case TYPES.STRING:
|
||||
return "string"
|
||||
case TYPES.BYTES:
|
||||
return "bytes"
|
||||
case TYPES.FIXED32:
|
||||
return "fixed32"
|
||||
case TYPES.SFIXED32:
|
||||
return "sfixed32"
|
||||
case TYPES.FLOAT:
|
||||
return "float"
|
||||
}
|
||||
}
|
||||
|
||||
const staticRenames = {
|
||||
id: "ID",
|
||||
jid: "JID",
|
||||
encIv: "encIV",
|
||||
iv: "IV",
|
||||
ptt: "PTT",
|
||||
hmac: "HMAC",
|
||||
url: "URL",
|
||||
fbid: "FBID",
|
||||
jpegThumbnail: "JPEGThumbnail",
|
||||
dsm: "DSM",
|
||||
}
|
||||
|
||||
function fixFieldName(name) {
|
||||
if (name === "id") {
|
||||
return "ID"
|
||||
} else if (name === "encIv") {
|
||||
return "encIV"
|
||||
}
|
||||
return staticRenames[name] ?? name
|
||||
.replace(/Id([A-Zs]|$)/, "ID$1")
|
||||
.replace("Jid", "JID")
|
||||
.replace(/Ms([A-Z]|$)/, "MS$1")
|
||||
.replace(/Ts([A-Z]|$)/, "TS$1")
|
||||
.replace(/Mac([A-Z]|$)/, "MAC$1")
|
||||
.replace("Url", "URL")
|
||||
.replace("Cdn", "CDN")
|
||||
.replace("Json", "JSON")
|
||||
.replace("Jpeg", "JPEG")
|
||||
.replace("Sha256", "SHA256")
|
||||
}
|
||||
|
||||
function protoifyField(name, [index, flags, typeRef], parentModule, parentPath) {
|
||||
const preflags = []
|
||||
const postflags = [""]
|
||||
if ((flags & FLAGS.REPEATED) !== 0) {
|
||||
preflags.push("repeated")
|
||||
}
|
||||
// if ((flags & FLAGS.REQUIRED) === 0) {
|
||||
// preflags.push("optional")
|
||||
// } else {
|
||||
// preflags.push("required")
|
||||
// }
|
||||
preflags.push(fieldTypeName(flags & TYPE_MASK, typeRef, parentModule, parentPath))
|
||||
if ((flags & FLAGS.PACKED) !== 0) {
|
||||
postflags.push(`[packed=true]`)
|
||||
}
|
||||
return `${preflags.join(" ")} ${fixFieldName(name)} = ${index}${postflags.join(" ")};`
|
||||
}
|
||||
|
||||
function protoifyFields(fields, parentModule, parentPath) {
|
||||
return Object.entries(fields).map(([name, definition]) => protoifyField(name, definition, parentModule, parentPath))
|
||||
}
|
||||
|
||||
function protoifyMessage(message) {
|
||||
const sections = [protoifyChildren(message)]
|
||||
const spec = message.message
|
||||
const fullMessagePath = message.__path__.concat([message.__name__])
|
||||
for (const [name, fieldNames] of Object.entries(spec.__oneofs__ ?? {})) {
|
||||
const fields = Object.fromEntries(fieldNames.map(fieldName => {
|
||||
const def = spec[fieldName]
|
||||
delete spec[fieldName]
|
||||
return [fieldName, def]
|
||||
}))
|
||||
sections.push([`oneof ${name} ` + "{", ...indent(protoifyFields(fields, message.__module__, fullMessagePath)), "}"])
|
||||
}
|
||||
if (spec.__reserved__) {
|
||||
console.warn("Found reserved keys:", message.__name__, spec.__reserved__)
|
||||
}
|
||||
delete spec.__oneofs__
|
||||
delete spec.__reserved__
|
||||
sections.push(protoifyFields(spec, message.__module__, fullMessagePath))
|
||||
return [`message ${message.__name__} ` + "{", ...indent(flattenWithBlankLines(...sections)), "}"]
|
||||
}
|
||||
|
||||
function goPackageName(name) {
|
||||
return name.replace(/^WA/, "wa")
|
||||
}
|
||||
|
||||
function protoifyModule(module) {
|
||||
const output = []
|
||||
output.push(`syntax = "proto3";`)
|
||||
output.push(`package ${module.__name__};`)
|
||||
output.push(`option go_package = "go.mau.fi/whatsmeow/binary/armadillo/${goPackageName(module.__name__)}";`)
|
||||
output.push("")
|
||||
if (module.dependencies.length > 0) {
|
||||
for (const dependency of module.dependencies) {
|
||||
output.push(`import "${goPackageName(dependency)}/${dependency}.proto";`)
|
||||
}
|
||||
output.push("")
|
||||
}
|
||||
const children = protoifyChildren(module)
|
||||
children.push("")
|
||||
return output.concat(children)
|
||||
}
|
||||
|
||||
const fs = require("fs")
|
||||
|
||||
for (const proto of protos) {
|
||||
fs.mkdirSync(goPackageName(proto.__name__), {recursive: true})
|
||||
fs.writeFileSync(`${goPackageName(proto.__name__)}/${proto.__name__}.proto`, protoifyModule(proto).join("\n"))
|
||||
}
|
||||
Generated
Vendored
+2927
File diff suppressed because it is too large
Load Diff
Generated
Vendored
BIN
Binary file not shown.
Vendored
+245
@@ -0,0 +1,245 @@
|
||||
syntax = "proto3";
|
||||
package WAArmadilloApplication;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waArmadilloApplication";
|
||||
|
||||
import "waArmadilloXMA/WAArmadilloXMA.proto";
|
||||
import "waCommon/WACommon.proto";
|
||||
|
||||
message Armadillo {
|
||||
message Metadata {
|
||||
}
|
||||
|
||||
message Payload {
|
||||
oneof payload {
|
||||
Content content = 1;
|
||||
ApplicationData applicationData = 2;
|
||||
Signal signal = 3;
|
||||
SubProtocolPayload subProtocol = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message SubProtocolPayload {
|
||||
WACommon.FutureProofBehavior futureProof = 1;
|
||||
}
|
||||
|
||||
message Signal {
|
||||
message EncryptedBackupsSecrets {
|
||||
message Epoch {
|
||||
enum EpochStatus {
|
||||
EPOCHSTATUS_UNKNOWN = 0;
|
||||
ES_OPEN = 1;
|
||||
ES_CLOSE = 2;
|
||||
}
|
||||
|
||||
uint64 ID = 1;
|
||||
bytes anonID = 2;
|
||||
bytes rootKey = 3;
|
||||
EpochStatus status = 4;
|
||||
}
|
||||
|
||||
uint64 backupID = 1;
|
||||
uint64 serverDataID = 2;
|
||||
repeated Epoch epoch = 3;
|
||||
bytes tempOcmfClientState = 4;
|
||||
bytes mailboxRootKey = 5;
|
||||
bytes obliviousValidationToken = 6;
|
||||
}
|
||||
|
||||
oneof signal {
|
||||
EncryptedBackupsSecrets encryptedBackupsSecrets = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ApplicationData {
|
||||
message AIBotResponseMessage {
|
||||
string summonToken = 1;
|
||||
string messageText = 2;
|
||||
string serializedExtras = 3;
|
||||
}
|
||||
|
||||
message MetadataSyncAction {
|
||||
message SyncMessageAction {
|
||||
message ActionMessageDelete {
|
||||
}
|
||||
|
||||
oneof action {
|
||||
ActionMessageDelete messageDelete = 101;
|
||||
}
|
||||
|
||||
WACommon.MessageKey key = 1;
|
||||
}
|
||||
|
||||
message SyncChatAction {
|
||||
message ActionChatRead {
|
||||
SyncActionMessageRange messageRange = 1;
|
||||
bool read = 2;
|
||||
}
|
||||
|
||||
message ActionChatDelete {
|
||||
SyncActionMessageRange messageRange = 1;
|
||||
}
|
||||
|
||||
message ActionChatArchive {
|
||||
SyncActionMessageRange messageRange = 1;
|
||||
bool archived = 2;
|
||||
}
|
||||
|
||||
oneof action {
|
||||
ActionChatArchive chatArchive = 101;
|
||||
ActionChatDelete chatDelete = 102;
|
||||
ActionChatRead chatRead = 103;
|
||||
}
|
||||
|
||||
string chatID = 1;
|
||||
}
|
||||
|
||||
message SyncActionMessage {
|
||||
WACommon.MessageKey key = 1;
|
||||
int64 timestamp = 2;
|
||||
}
|
||||
|
||||
message SyncActionMessageRange {
|
||||
int64 lastMessageTimestamp = 1;
|
||||
int64 lastSystemMessageTimestamp = 2;
|
||||
repeated SyncActionMessage messages = 3;
|
||||
}
|
||||
|
||||
oneof actionType {
|
||||
SyncChatAction chatAction = 101;
|
||||
SyncMessageAction messageAction = 102;
|
||||
}
|
||||
|
||||
int64 actionTimestamp = 1;
|
||||
}
|
||||
|
||||
message MetadataSyncNotification {
|
||||
repeated MetadataSyncAction actions = 2;
|
||||
}
|
||||
|
||||
oneof applicationData {
|
||||
MetadataSyncNotification metadataSync = 1;
|
||||
AIBotResponseMessage aiBotResponse = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Content {
|
||||
message PaymentsTransactionMessage {
|
||||
enum PaymentStatus {
|
||||
PAYMENT_UNKNOWN = 0;
|
||||
REQUEST_INITED = 4;
|
||||
REQUEST_DECLINED = 5;
|
||||
REQUEST_TRANSFER_INITED = 6;
|
||||
REQUEST_TRANSFER_COMPLETED = 7;
|
||||
REQUEST_TRANSFER_FAILED = 8;
|
||||
REQUEST_CANCELED = 9;
|
||||
REQUEST_EXPIRED = 10;
|
||||
TRANSFER_INITED = 11;
|
||||
TRANSFER_PENDING = 12;
|
||||
TRANSFER_PENDING_RECIPIENT_VERIFICATION = 13;
|
||||
TRANSFER_CANCELED = 14;
|
||||
TRANSFER_COMPLETED = 15;
|
||||
TRANSFER_NO_RECEIVER_CREDENTIAL_NO_RTS_PENDING_CANCELED = 16;
|
||||
TRANSFER_NO_RECEIVER_CREDENTIAL_NO_RTS_PENDING_OTHER = 17;
|
||||
TRANSFER_REFUNDED = 18;
|
||||
TRANSFER_PARTIAL_REFUND = 19;
|
||||
TRANSFER_CHARGED_BACK = 20;
|
||||
TRANSFER_EXPIRED = 21;
|
||||
TRANSFER_DECLINED = 22;
|
||||
TRANSFER_UNAVAILABLE = 23;
|
||||
}
|
||||
|
||||
uint64 transactionID = 1;
|
||||
string amount = 2;
|
||||
string currency = 3;
|
||||
PaymentStatus paymentStatus = 4;
|
||||
WAArmadilloXMA.ExtendedContentMessage extendedContentMessage = 5;
|
||||
}
|
||||
|
||||
message NoteReplyMessage {
|
||||
string noteID = 1;
|
||||
WACommon.MessageText noteText = 2;
|
||||
int64 noteTimestampMS = 3;
|
||||
WACommon.MessageText noteReplyText = 4;
|
||||
}
|
||||
|
||||
message BumpExistingMessage {
|
||||
WACommon.MessageKey key = 1;
|
||||
}
|
||||
|
||||
message ImageGalleryMessage {
|
||||
repeated WACommon.SubProtocol images = 1;
|
||||
}
|
||||
|
||||
message ScreenshotAction {
|
||||
enum ScreenshotType {
|
||||
SCREENSHOTTYPE_UNKNOWN = 0;
|
||||
SCREENSHOT_IMAGE = 1;
|
||||
SCREEN_RECORDING = 2;
|
||||
}
|
||||
|
||||
ScreenshotType screenshotType = 1;
|
||||
}
|
||||
|
||||
message ExtendedContentMessageWithSear {
|
||||
string searID = 1;
|
||||
bytes payload = 2;
|
||||
string nativeURL = 3;
|
||||
WACommon.SubProtocol searAssociatedMessage = 4;
|
||||
string searSentWithMessageID = 5;
|
||||
}
|
||||
|
||||
message RavenActionNotifMessage {
|
||||
enum ActionType {
|
||||
PLAYED = 0;
|
||||
SCREENSHOT = 1;
|
||||
FORCE_DISABLE = 2;
|
||||
}
|
||||
|
||||
WACommon.MessageKey key = 1;
|
||||
int64 actionTimestamp = 2;
|
||||
ActionType actionType = 3;
|
||||
}
|
||||
|
||||
message RavenMessage {
|
||||
enum EphemeralType {
|
||||
VIEW_ONCE = 0;
|
||||
ALLOW_REPLAY = 1;
|
||||
KEEP_IN_CHAT = 2;
|
||||
}
|
||||
|
||||
oneof mediaContent {
|
||||
WACommon.SubProtocol imageMessage = 2;
|
||||
WACommon.SubProtocol videoMessage = 3;
|
||||
}
|
||||
|
||||
EphemeralType ephemeralType = 1;
|
||||
}
|
||||
|
||||
message CommonSticker {
|
||||
enum StickerType {
|
||||
STICKERTYPE_UNKNOWN = 0;
|
||||
SMALL_LIKE = 1;
|
||||
MEDIUM_LIKE = 2;
|
||||
LARGE_LIKE = 3;
|
||||
}
|
||||
|
||||
StickerType stickerType = 1;
|
||||
}
|
||||
|
||||
oneof content {
|
||||
CommonSticker commonSticker = 1;
|
||||
ScreenshotAction screenshotAction = 3;
|
||||
WAArmadilloXMA.ExtendedContentMessage extendedContentMessage = 4;
|
||||
RavenMessage ravenMessage = 5;
|
||||
RavenActionNotifMessage ravenActionNotifMessage = 6;
|
||||
ExtendedContentMessageWithSear extendedMessageContentWithSear = 7;
|
||||
ImageGalleryMessage imageGalleryMessage = 8;
|
||||
PaymentsTransactionMessage paymentsTransactionMessage = 10;
|
||||
BumpExistingMessage bumpExistingMessage = 11;
|
||||
NoteReplyMessage noteReplyMessage = 13;
|
||||
}
|
||||
}
|
||||
|
||||
Payload payload = 1;
|
||||
Metadata metadata = 2;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package waArmadilloApplication
|
||||
|
||||
func (*Armadillo) IsMessageApplicationSub() {}
|
||||
+785
@@ -0,0 +1,785 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v3.21.12
|
||||
// source: waArmadilloXMA/WAArmadilloXMA.proto
|
||||
|
||||
package waArmadilloXMA
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
waCommon "go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type ExtendedContentMessage_OverlayIconGlyph int32
|
||||
|
||||
const (
|
||||
ExtendedContentMessage_INFO ExtendedContentMessage_OverlayIconGlyph = 0
|
||||
ExtendedContentMessage_EYE_OFF ExtendedContentMessage_OverlayIconGlyph = 1
|
||||
ExtendedContentMessage_NEWS_OFF ExtendedContentMessage_OverlayIconGlyph = 2
|
||||
ExtendedContentMessage_WARNING ExtendedContentMessage_OverlayIconGlyph = 3
|
||||
ExtendedContentMessage_PRIVATE ExtendedContentMessage_OverlayIconGlyph = 4
|
||||
ExtendedContentMessage_NONE ExtendedContentMessage_OverlayIconGlyph = 5
|
||||
ExtendedContentMessage_MEDIA_LABEL ExtendedContentMessage_OverlayIconGlyph = 6
|
||||
ExtendedContentMessage_POST_COVER ExtendedContentMessage_OverlayIconGlyph = 7
|
||||
ExtendedContentMessage_POST_LABEL ExtendedContentMessage_OverlayIconGlyph = 8
|
||||
ExtendedContentMessage_WARNING_SCREENS ExtendedContentMessage_OverlayIconGlyph = 9
|
||||
)
|
||||
|
||||
// Enum value maps for ExtendedContentMessage_OverlayIconGlyph.
|
||||
var (
|
||||
ExtendedContentMessage_OverlayIconGlyph_name = map[int32]string{
|
||||
0: "INFO",
|
||||
1: "EYE_OFF",
|
||||
2: "NEWS_OFF",
|
||||
3: "WARNING",
|
||||
4: "PRIVATE",
|
||||
5: "NONE",
|
||||
6: "MEDIA_LABEL",
|
||||
7: "POST_COVER",
|
||||
8: "POST_LABEL",
|
||||
9: "WARNING_SCREENS",
|
||||
}
|
||||
ExtendedContentMessage_OverlayIconGlyph_value = map[string]int32{
|
||||
"INFO": 0,
|
||||
"EYE_OFF": 1,
|
||||
"NEWS_OFF": 2,
|
||||
"WARNING": 3,
|
||||
"PRIVATE": 4,
|
||||
"NONE": 5,
|
||||
"MEDIA_LABEL": 6,
|
||||
"POST_COVER": 7,
|
||||
"POST_LABEL": 8,
|
||||
"WARNING_SCREENS": 9,
|
||||
}
|
||||
)
|
||||
|
||||
func (x ExtendedContentMessage_OverlayIconGlyph) Enum() *ExtendedContentMessage_OverlayIconGlyph {
|
||||
p := new(ExtendedContentMessage_OverlayIconGlyph)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_OverlayIconGlyph) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_OverlayIconGlyph) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_OverlayIconGlyph) Type() protoreflect.EnumType {
|
||||
return &file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_OverlayIconGlyph) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExtendedContentMessage_OverlayIconGlyph.Descriptor instead.
|
||||
func (ExtendedContentMessage_OverlayIconGlyph) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type ExtendedContentMessage_CtaButtonType int32
|
||||
|
||||
const (
|
||||
ExtendedContentMessage_CTABUTTONTYPE_UNKNOWN ExtendedContentMessage_CtaButtonType = 0
|
||||
ExtendedContentMessage_OPEN_NATIVE ExtendedContentMessage_CtaButtonType = 11
|
||||
)
|
||||
|
||||
// Enum value maps for ExtendedContentMessage_CtaButtonType.
|
||||
var (
|
||||
ExtendedContentMessage_CtaButtonType_name = map[int32]string{
|
||||
0: "CTABUTTONTYPE_UNKNOWN",
|
||||
11: "OPEN_NATIVE",
|
||||
}
|
||||
ExtendedContentMessage_CtaButtonType_value = map[string]int32{
|
||||
"CTABUTTONTYPE_UNKNOWN": 0,
|
||||
"OPEN_NATIVE": 11,
|
||||
}
|
||||
)
|
||||
|
||||
func (x ExtendedContentMessage_CtaButtonType) Enum() *ExtendedContentMessage_CtaButtonType {
|
||||
p := new(ExtendedContentMessage_CtaButtonType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_CtaButtonType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_CtaButtonType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_CtaButtonType) Type() protoreflect.EnumType {
|
||||
return &file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_CtaButtonType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExtendedContentMessage_CtaButtonType.Descriptor instead.
|
||||
func (ExtendedContentMessage_CtaButtonType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP(), []int{0, 1}
|
||||
}
|
||||
|
||||
type ExtendedContentMessage_XmaLayoutType int32
|
||||
|
||||
const (
|
||||
ExtendedContentMessage_SINGLE ExtendedContentMessage_XmaLayoutType = 0
|
||||
ExtendedContentMessage_PORTRAIT ExtendedContentMessage_XmaLayoutType = 3
|
||||
ExtendedContentMessage_STANDARD_DXMA ExtendedContentMessage_XmaLayoutType = 12
|
||||
ExtendedContentMessage_LIST_DXMA ExtendedContentMessage_XmaLayoutType = 15
|
||||
)
|
||||
|
||||
// Enum value maps for ExtendedContentMessage_XmaLayoutType.
|
||||
var (
|
||||
ExtendedContentMessage_XmaLayoutType_name = map[int32]string{
|
||||
0: "SINGLE",
|
||||
3: "PORTRAIT",
|
||||
12: "STANDARD_DXMA",
|
||||
15: "LIST_DXMA",
|
||||
}
|
||||
ExtendedContentMessage_XmaLayoutType_value = map[string]int32{
|
||||
"SINGLE": 0,
|
||||
"PORTRAIT": 3,
|
||||
"STANDARD_DXMA": 12,
|
||||
"LIST_DXMA": 15,
|
||||
}
|
||||
)
|
||||
|
||||
func (x ExtendedContentMessage_XmaLayoutType) Enum() *ExtendedContentMessage_XmaLayoutType {
|
||||
p := new(ExtendedContentMessage_XmaLayoutType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_XmaLayoutType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_XmaLayoutType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[2].Descriptor()
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_XmaLayoutType) Type() protoreflect.EnumType {
|
||||
return &file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[2]
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_XmaLayoutType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExtendedContentMessage_XmaLayoutType.Descriptor instead.
|
||||
func (ExtendedContentMessage_XmaLayoutType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP(), []int{0, 2}
|
||||
}
|
||||
|
||||
type ExtendedContentMessage_ExtendedContentType int32
|
||||
|
||||
const (
|
||||
ExtendedContentMessage_EXTENDEDCONTENTTYPE_UNKNOWN ExtendedContentMessage_ExtendedContentType = 0
|
||||
ExtendedContentMessage_IG_STORY_PHOTO_MENTION ExtendedContentMessage_ExtendedContentType = 4
|
||||
ExtendedContentMessage_IG_SINGLE_IMAGE_POST_SHARE ExtendedContentMessage_ExtendedContentType = 9
|
||||
ExtendedContentMessage_IG_MULTIPOST_SHARE ExtendedContentMessage_ExtendedContentType = 10
|
||||
ExtendedContentMessage_IG_SINGLE_VIDEO_POST_SHARE ExtendedContentMessage_ExtendedContentType = 11
|
||||
ExtendedContentMessage_IG_STORY_PHOTO_SHARE ExtendedContentMessage_ExtendedContentType = 12
|
||||
ExtendedContentMessage_IG_STORY_VIDEO_SHARE ExtendedContentMessage_ExtendedContentType = 13
|
||||
ExtendedContentMessage_IG_CLIPS_SHARE ExtendedContentMessage_ExtendedContentType = 14
|
||||
ExtendedContentMessage_IG_IGTV_SHARE ExtendedContentMessage_ExtendedContentType = 15
|
||||
ExtendedContentMessage_IG_SHOP_SHARE ExtendedContentMessage_ExtendedContentType = 16
|
||||
ExtendedContentMessage_IG_PROFILE_SHARE ExtendedContentMessage_ExtendedContentType = 19
|
||||
ExtendedContentMessage_IG_STORY_PHOTO_HIGHLIGHT_SHARE ExtendedContentMessage_ExtendedContentType = 20
|
||||
ExtendedContentMessage_IG_STORY_VIDEO_HIGHLIGHT_SHARE ExtendedContentMessage_ExtendedContentType = 21
|
||||
ExtendedContentMessage_IG_STORY_REPLY ExtendedContentMessage_ExtendedContentType = 22
|
||||
ExtendedContentMessage_IG_STORY_REACTION ExtendedContentMessage_ExtendedContentType = 23
|
||||
ExtendedContentMessage_IG_STORY_VIDEO_MENTION ExtendedContentMessage_ExtendedContentType = 24
|
||||
ExtendedContentMessage_IG_STORY_HIGHLIGHT_REPLY ExtendedContentMessage_ExtendedContentType = 25
|
||||
ExtendedContentMessage_IG_STORY_HIGHLIGHT_REACTION ExtendedContentMessage_ExtendedContentType = 26
|
||||
ExtendedContentMessage_IG_EXTERNAL_LINK ExtendedContentMessage_ExtendedContentType = 27
|
||||
ExtendedContentMessage_IG_RECEIVER_FETCH ExtendedContentMessage_ExtendedContentType = 28
|
||||
ExtendedContentMessage_FB_FEED_SHARE ExtendedContentMessage_ExtendedContentType = 1000
|
||||
ExtendedContentMessage_FB_STORY_REPLY ExtendedContentMessage_ExtendedContentType = 1001
|
||||
ExtendedContentMessage_FB_STORY_SHARE ExtendedContentMessage_ExtendedContentType = 1002
|
||||
ExtendedContentMessage_FB_STORY_MENTION ExtendedContentMessage_ExtendedContentType = 1003
|
||||
ExtendedContentMessage_FB_FEED_VIDEO_SHARE ExtendedContentMessage_ExtendedContentType = 1004
|
||||
ExtendedContentMessage_FB_GAMING_CUSTOM_UPDATE ExtendedContentMessage_ExtendedContentType = 1005
|
||||
ExtendedContentMessage_FB_PRODUCER_STORY_REPLY ExtendedContentMessage_ExtendedContentType = 1006
|
||||
ExtendedContentMessage_FB_EVENT ExtendedContentMessage_ExtendedContentType = 1007
|
||||
ExtendedContentMessage_FB_FEED_POST_PRIVATE_REPLY ExtendedContentMessage_ExtendedContentType = 1008
|
||||
ExtendedContentMessage_FB_SHORT ExtendedContentMessage_ExtendedContentType = 1009
|
||||
ExtendedContentMessage_FB_COMMENT_MENTION_SHARE ExtendedContentMessage_ExtendedContentType = 1010
|
||||
ExtendedContentMessage_MSG_EXTERNAL_LINK_SHARE ExtendedContentMessage_ExtendedContentType = 2000
|
||||
ExtendedContentMessage_MSG_P2P_PAYMENT ExtendedContentMessage_ExtendedContentType = 2001
|
||||
ExtendedContentMessage_MSG_LOCATION_SHARING ExtendedContentMessage_ExtendedContentType = 2002
|
||||
ExtendedContentMessage_MSG_LOCATION_SHARING_V2 ExtendedContentMessage_ExtendedContentType = 2003
|
||||
ExtendedContentMessage_MSG_HIGHLIGHTS_TAB_FRIEND_UPDATES_REPLY ExtendedContentMessage_ExtendedContentType = 2004
|
||||
ExtendedContentMessage_MSG_HIGHLIGHTS_TAB_LOCAL_EVENT_REPLY ExtendedContentMessage_ExtendedContentType = 2005
|
||||
ExtendedContentMessage_MSG_RECEIVER_FETCH ExtendedContentMessage_ExtendedContentType = 2006
|
||||
ExtendedContentMessage_MSG_IG_MEDIA_SHARE ExtendedContentMessage_ExtendedContentType = 2007
|
||||
ExtendedContentMessage_MSG_GEN_AI_SEARCH_PLUGIN_RESPONSE ExtendedContentMessage_ExtendedContentType = 2008
|
||||
ExtendedContentMessage_MSG_REELS_LIST ExtendedContentMessage_ExtendedContentType = 2009
|
||||
ExtendedContentMessage_MSG_CONTACT ExtendedContentMessage_ExtendedContentType = 2010
|
||||
ExtendedContentMessage_RTC_AUDIO_CALL ExtendedContentMessage_ExtendedContentType = 3000
|
||||
ExtendedContentMessage_RTC_VIDEO_CALL ExtendedContentMessage_ExtendedContentType = 3001
|
||||
ExtendedContentMessage_RTC_MISSED_AUDIO_CALL ExtendedContentMessage_ExtendedContentType = 3002
|
||||
ExtendedContentMessage_RTC_MISSED_VIDEO_CALL ExtendedContentMessage_ExtendedContentType = 3003
|
||||
ExtendedContentMessage_RTC_GROUP_AUDIO_CALL ExtendedContentMessage_ExtendedContentType = 3004
|
||||
ExtendedContentMessage_RTC_GROUP_VIDEO_CALL ExtendedContentMessage_ExtendedContentType = 3005
|
||||
ExtendedContentMessage_RTC_MISSED_GROUP_AUDIO_CALL ExtendedContentMessage_ExtendedContentType = 3006
|
||||
ExtendedContentMessage_RTC_MISSED_GROUP_VIDEO_CALL ExtendedContentMessage_ExtendedContentType = 3007
|
||||
ExtendedContentMessage_DATACLASS_SENDER_COPY ExtendedContentMessage_ExtendedContentType = 4000
|
||||
)
|
||||
|
||||
// Enum value maps for ExtendedContentMessage_ExtendedContentType.
|
||||
var (
|
||||
ExtendedContentMessage_ExtendedContentType_name = map[int32]string{
|
||||
0: "EXTENDEDCONTENTTYPE_UNKNOWN",
|
||||
4: "IG_STORY_PHOTO_MENTION",
|
||||
9: "IG_SINGLE_IMAGE_POST_SHARE",
|
||||
10: "IG_MULTIPOST_SHARE",
|
||||
11: "IG_SINGLE_VIDEO_POST_SHARE",
|
||||
12: "IG_STORY_PHOTO_SHARE",
|
||||
13: "IG_STORY_VIDEO_SHARE",
|
||||
14: "IG_CLIPS_SHARE",
|
||||
15: "IG_IGTV_SHARE",
|
||||
16: "IG_SHOP_SHARE",
|
||||
19: "IG_PROFILE_SHARE",
|
||||
20: "IG_STORY_PHOTO_HIGHLIGHT_SHARE",
|
||||
21: "IG_STORY_VIDEO_HIGHLIGHT_SHARE",
|
||||
22: "IG_STORY_REPLY",
|
||||
23: "IG_STORY_REACTION",
|
||||
24: "IG_STORY_VIDEO_MENTION",
|
||||
25: "IG_STORY_HIGHLIGHT_REPLY",
|
||||
26: "IG_STORY_HIGHLIGHT_REACTION",
|
||||
27: "IG_EXTERNAL_LINK",
|
||||
28: "IG_RECEIVER_FETCH",
|
||||
1000: "FB_FEED_SHARE",
|
||||
1001: "FB_STORY_REPLY",
|
||||
1002: "FB_STORY_SHARE",
|
||||
1003: "FB_STORY_MENTION",
|
||||
1004: "FB_FEED_VIDEO_SHARE",
|
||||
1005: "FB_GAMING_CUSTOM_UPDATE",
|
||||
1006: "FB_PRODUCER_STORY_REPLY",
|
||||
1007: "FB_EVENT",
|
||||
1008: "FB_FEED_POST_PRIVATE_REPLY",
|
||||
1009: "FB_SHORT",
|
||||
1010: "FB_COMMENT_MENTION_SHARE",
|
||||
2000: "MSG_EXTERNAL_LINK_SHARE",
|
||||
2001: "MSG_P2P_PAYMENT",
|
||||
2002: "MSG_LOCATION_SHARING",
|
||||
2003: "MSG_LOCATION_SHARING_V2",
|
||||
2004: "MSG_HIGHLIGHTS_TAB_FRIEND_UPDATES_REPLY",
|
||||
2005: "MSG_HIGHLIGHTS_TAB_LOCAL_EVENT_REPLY",
|
||||
2006: "MSG_RECEIVER_FETCH",
|
||||
2007: "MSG_IG_MEDIA_SHARE",
|
||||
2008: "MSG_GEN_AI_SEARCH_PLUGIN_RESPONSE",
|
||||
2009: "MSG_REELS_LIST",
|
||||
2010: "MSG_CONTACT",
|
||||
3000: "RTC_AUDIO_CALL",
|
||||
3001: "RTC_VIDEO_CALL",
|
||||
3002: "RTC_MISSED_AUDIO_CALL",
|
||||
3003: "RTC_MISSED_VIDEO_CALL",
|
||||
3004: "RTC_GROUP_AUDIO_CALL",
|
||||
3005: "RTC_GROUP_VIDEO_CALL",
|
||||
3006: "RTC_MISSED_GROUP_AUDIO_CALL",
|
||||
3007: "RTC_MISSED_GROUP_VIDEO_CALL",
|
||||
4000: "DATACLASS_SENDER_COPY",
|
||||
}
|
||||
ExtendedContentMessage_ExtendedContentType_value = map[string]int32{
|
||||
"EXTENDEDCONTENTTYPE_UNKNOWN": 0,
|
||||
"IG_STORY_PHOTO_MENTION": 4,
|
||||
"IG_SINGLE_IMAGE_POST_SHARE": 9,
|
||||
"IG_MULTIPOST_SHARE": 10,
|
||||
"IG_SINGLE_VIDEO_POST_SHARE": 11,
|
||||
"IG_STORY_PHOTO_SHARE": 12,
|
||||
"IG_STORY_VIDEO_SHARE": 13,
|
||||
"IG_CLIPS_SHARE": 14,
|
||||
"IG_IGTV_SHARE": 15,
|
||||
"IG_SHOP_SHARE": 16,
|
||||
"IG_PROFILE_SHARE": 19,
|
||||
"IG_STORY_PHOTO_HIGHLIGHT_SHARE": 20,
|
||||
"IG_STORY_VIDEO_HIGHLIGHT_SHARE": 21,
|
||||
"IG_STORY_REPLY": 22,
|
||||
"IG_STORY_REACTION": 23,
|
||||
"IG_STORY_VIDEO_MENTION": 24,
|
||||
"IG_STORY_HIGHLIGHT_REPLY": 25,
|
||||
"IG_STORY_HIGHLIGHT_REACTION": 26,
|
||||
"IG_EXTERNAL_LINK": 27,
|
||||
"IG_RECEIVER_FETCH": 28,
|
||||
"FB_FEED_SHARE": 1000,
|
||||
"FB_STORY_REPLY": 1001,
|
||||
"FB_STORY_SHARE": 1002,
|
||||
"FB_STORY_MENTION": 1003,
|
||||
"FB_FEED_VIDEO_SHARE": 1004,
|
||||
"FB_GAMING_CUSTOM_UPDATE": 1005,
|
||||
"FB_PRODUCER_STORY_REPLY": 1006,
|
||||
"FB_EVENT": 1007,
|
||||
"FB_FEED_POST_PRIVATE_REPLY": 1008,
|
||||
"FB_SHORT": 1009,
|
||||
"FB_COMMENT_MENTION_SHARE": 1010,
|
||||
"MSG_EXTERNAL_LINK_SHARE": 2000,
|
||||
"MSG_P2P_PAYMENT": 2001,
|
||||
"MSG_LOCATION_SHARING": 2002,
|
||||
"MSG_LOCATION_SHARING_V2": 2003,
|
||||
"MSG_HIGHLIGHTS_TAB_FRIEND_UPDATES_REPLY": 2004,
|
||||
"MSG_HIGHLIGHTS_TAB_LOCAL_EVENT_REPLY": 2005,
|
||||
"MSG_RECEIVER_FETCH": 2006,
|
||||
"MSG_IG_MEDIA_SHARE": 2007,
|
||||
"MSG_GEN_AI_SEARCH_PLUGIN_RESPONSE": 2008,
|
||||
"MSG_REELS_LIST": 2009,
|
||||
"MSG_CONTACT": 2010,
|
||||
"RTC_AUDIO_CALL": 3000,
|
||||
"RTC_VIDEO_CALL": 3001,
|
||||
"RTC_MISSED_AUDIO_CALL": 3002,
|
||||
"RTC_MISSED_VIDEO_CALL": 3003,
|
||||
"RTC_GROUP_AUDIO_CALL": 3004,
|
||||
"RTC_GROUP_VIDEO_CALL": 3005,
|
||||
"RTC_MISSED_GROUP_AUDIO_CALL": 3006,
|
||||
"RTC_MISSED_GROUP_VIDEO_CALL": 3007,
|
||||
"DATACLASS_SENDER_COPY": 4000,
|
||||
}
|
||||
)
|
||||
|
||||
func (x ExtendedContentMessage_ExtendedContentType) Enum() *ExtendedContentMessage_ExtendedContentType {
|
||||
p := new(ExtendedContentMessage_ExtendedContentType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_ExtendedContentType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_ExtendedContentType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (ExtendedContentMessage_ExtendedContentType) Type() protoreflect.EnumType {
|
||||
return &file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x ExtendedContentMessage_ExtendedContentType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExtendedContentMessage_ExtendedContentType.Descriptor instead.
|
||||
func (ExtendedContentMessage_ExtendedContentType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP(), []int{0, 3}
|
||||
}
|
||||
|
||||
type ExtendedContentMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
AssociatedMessage *waCommon.SubProtocol `protobuf:"bytes,1,opt,name=associatedMessage,proto3" json:"associatedMessage,omitempty"`
|
||||
TargetType ExtendedContentMessage_ExtendedContentType `protobuf:"varint,2,opt,name=targetType,proto3,enum=WAArmadilloXMA.ExtendedContentMessage_ExtendedContentType" json:"targetType,omitempty"`
|
||||
TargetUsername string `protobuf:"bytes,3,opt,name=targetUsername,proto3" json:"targetUsername,omitempty"`
|
||||
TargetID string `protobuf:"bytes,4,opt,name=targetID,proto3" json:"targetID,omitempty"`
|
||||
TargetExpiringAtSec int64 `protobuf:"varint,5,opt,name=targetExpiringAtSec,proto3" json:"targetExpiringAtSec,omitempty"`
|
||||
XmaLayoutType ExtendedContentMessage_XmaLayoutType `protobuf:"varint,6,opt,name=xmaLayoutType,proto3,enum=WAArmadilloXMA.ExtendedContentMessage_XmaLayoutType" json:"xmaLayoutType,omitempty"`
|
||||
Ctas []*ExtendedContentMessage_CTA `protobuf:"bytes,7,rep,name=ctas,proto3" json:"ctas,omitempty"`
|
||||
Previews []*waCommon.SubProtocol `protobuf:"bytes,8,rep,name=previews,proto3" json:"previews,omitempty"`
|
||||
TitleText string `protobuf:"bytes,9,opt,name=titleText,proto3" json:"titleText,omitempty"`
|
||||
SubtitleText string `protobuf:"bytes,10,opt,name=subtitleText,proto3" json:"subtitleText,omitempty"`
|
||||
MaxTitleNumOfLines uint32 `protobuf:"varint,11,opt,name=maxTitleNumOfLines,proto3" json:"maxTitleNumOfLines,omitempty"`
|
||||
MaxSubtitleNumOfLines uint32 `protobuf:"varint,12,opt,name=maxSubtitleNumOfLines,proto3" json:"maxSubtitleNumOfLines,omitempty"`
|
||||
Favicon *waCommon.SubProtocol `protobuf:"bytes,13,opt,name=favicon,proto3" json:"favicon,omitempty"`
|
||||
HeaderImage *waCommon.SubProtocol `protobuf:"bytes,14,opt,name=headerImage,proto3" json:"headerImage,omitempty"`
|
||||
HeaderTitle string `protobuf:"bytes,15,opt,name=headerTitle,proto3" json:"headerTitle,omitempty"`
|
||||
OverlayIconGlyph ExtendedContentMessage_OverlayIconGlyph `protobuf:"varint,16,opt,name=overlayIconGlyph,proto3,enum=WAArmadilloXMA.ExtendedContentMessage_OverlayIconGlyph" json:"overlayIconGlyph,omitempty"`
|
||||
OverlayTitle string `protobuf:"bytes,17,opt,name=overlayTitle,proto3" json:"overlayTitle,omitempty"`
|
||||
OverlayDescription string `protobuf:"bytes,18,opt,name=overlayDescription,proto3" json:"overlayDescription,omitempty"`
|
||||
SentWithMessageID string `protobuf:"bytes,19,opt,name=sentWithMessageID,proto3" json:"sentWithMessageID,omitempty"`
|
||||
MessageText string `protobuf:"bytes,20,opt,name=messageText,proto3" json:"messageText,omitempty"`
|
||||
HeaderSubtitle string `protobuf:"bytes,21,opt,name=headerSubtitle,proto3" json:"headerSubtitle,omitempty"`
|
||||
XmaDataclass string `protobuf:"bytes,22,opt,name=xmaDataclass,proto3" json:"xmaDataclass,omitempty"`
|
||||
ContentRef string `protobuf:"bytes,23,opt,name=contentRef,proto3" json:"contentRef,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) Reset() {
|
||||
*x = ExtendedContentMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExtendedContentMessage) ProtoMessage() {}
|
||||
|
||||
func (x *ExtendedContentMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes[0]
|
||||
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 ExtendedContentMessage.ProtoReflect.Descriptor instead.
|
||||
func (*ExtendedContentMessage) Descriptor() ([]byte, []int) {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetAssociatedMessage() *waCommon.SubProtocol {
|
||||
if x != nil {
|
||||
return x.AssociatedMessage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetTargetType() ExtendedContentMessage_ExtendedContentType {
|
||||
if x != nil {
|
||||
return x.TargetType
|
||||
}
|
||||
return ExtendedContentMessage_EXTENDEDCONTENTTYPE_UNKNOWN
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetTargetUsername() string {
|
||||
if x != nil {
|
||||
return x.TargetUsername
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetTargetID() string {
|
||||
if x != nil {
|
||||
return x.TargetID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetTargetExpiringAtSec() int64 {
|
||||
if x != nil {
|
||||
return x.TargetExpiringAtSec
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetXmaLayoutType() ExtendedContentMessage_XmaLayoutType {
|
||||
if x != nil {
|
||||
return x.XmaLayoutType
|
||||
}
|
||||
return ExtendedContentMessage_SINGLE
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetCtas() []*ExtendedContentMessage_CTA {
|
||||
if x != nil {
|
||||
return x.Ctas
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetPreviews() []*waCommon.SubProtocol {
|
||||
if x != nil {
|
||||
return x.Previews
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetTitleText() string {
|
||||
if x != nil {
|
||||
return x.TitleText
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetSubtitleText() string {
|
||||
if x != nil {
|
||||
return x.SubtitleText
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetMaxTitleNumOfLines() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxTitleNumOfLines
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetMaxSubtitleNumOfLines() uint32 {
|
||||
if x != nil {
|
||||
return x.MaxSubtitleNumOfLines
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetFavicon() *waCommon.SubProtocol {
|
||||
if x != nil {
|
||||
return x.Favicon
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetHeaderImage() *waCommon.SubProtocol {
|
||||
if x != nil {
|
||||
return x.HeaderImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetHeaderTitle() string {
|
||||
if x != nil {
|
||||
return x.HeaderTitle
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetOverlayIconGlyph() ExtendedContentMessage_OverlayIconGlyph {
|
||||
if x != nil {
|
||||
return x.OverlayIconGlyph
|
||||
}
|
||||
return ExtendedContentMessage_INFO
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetOverlayTitle() string {
|
||||
if x != nil {
|
||||
return x.OverlayTitle
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetOverlayDescription() string {
|
||||
if x != nil {
|
||||
return x.OverlayDescription
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetSentWithMessageID() string {
|
||||
if x != nil {
|
||||
return x.SentWithMessageID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetMessageText() string {
|
||||
if x != nil {
|
||||
return x.MessageText
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetHeaderSubtitle() string {
|
||||
if x != nil {
|
||||
return x.HeaderSubtitle
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetXmaDataclass() string {
|
||||
if x != nil {
|
||||
return x.XmaDataclass
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage) GetContentRef() string {
|
||||
if x != nil {
|
||||
return x.ContentRef
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ExtendedContentMessage_CTA struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ButtonType ExtendedContentMessage_CtaButtonType `protobuf:"varint,1,opt,name=buttonType,proto3,enum=WAArmadilloXMA.ExtendedContentMessage_CtaButtonType" json:"buttonType,omitempty"`
|
||||
Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
|
||||
ActionURL string `protobuf:"bytes,3,opt,name=actionURL,proto3" json:"actionURL,omitempty"`
|
||||
NativeURL string `protobuf:"bytes,4,opt,name=nativeURL,proto3" json:"nativeURL,omitempty"`
|
||||
CtaType string `protobuf:"bytes,5,opt,name=ctaType,proto3" json:"ctaType,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) Reset() {
|
||||
*x = ExtendedContentMessage_CTA{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExtendedContentMessage_CTA) ProtoMessage() {}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes[1]
|
||||
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 ExtendedContentMessage_CTA.ProtoReflect.Descriptor instead.
|
||||
func (*ExtendedContentMessage_CTA) Descriptor() ([]byte, []int) {
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) GetButtonType() ExtendedContentMessage_CtaButtonType {
|
||||
if x != nil {
|
||||
return x.ButtonType
|
||||
}
|
||||
return ExtendedContentMessage_CTABUTTONTYPE_UNKNOWN
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) GetTitle() string {
|
||||
if x != nil {
|
||||
return x.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) GetActionURL() string {
|
||||
if x != nil {
|
||||
return x.ActionURL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) GetNativeURL() string {
|
||||
if x != nil {
|
||||
return x.NativeURL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExtendedContentMessage_CTA) GetCtaType() string {
|
||||
if x != nil {
|
||||
return x.CtaType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_waArmadilloXMA_WAArmadilloXMA_proto protoreflect.FileDescriptor
|
||||
|
||||
//go:embed WAArmadilloXMA.pb.raw
|
||||
var file_waArmadilloXMA_WAArmadilloXMA_proto_rawDesc []byte
|
||||
|
||||
var (
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescOnce sync.Once
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescData = file_waArmadilloXMA_WAArmadilloXMA_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescGZIP() []byte {
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescOnce.Do(func() {
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescData = protoimpl.X.CompressGZIP(file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescData)
|
||||
})
|
||||
return file_waArmadilloXMA_WAArmadilloXMA_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_waArmadilloXMA_WAArmadilloXMA_proto_goTypes = []interface{}{
|
||||
(ExtendedContentMessage_OverlayIconGlyph)(0), // 0: WAArmadilloXMA.ExtendedContentMessage.OverlayIconGlyph
|
||||
(ExtendedContentMessage_CtaButtonType)(0), // 1: WAArmadilloXMA.ExtendedContentMessage.CtaButtonType
|
||||
(ExtendedContentMessage_XmaLayoutType)(0), // 2: WAArmadilloXMA.ExtendedContentMessage.XmaLayoutType
|
||||
(ExtendedContentMessage_ExtendedContentType)(0), // 3: WAArmadilloXMA.ExtendedContentMessage.ExtendedContentType
|
||||
(*ExtendedContentMessage)(nil), // 4: WAArmadilloXMA.ExtendedContentMessage
|
||||
(*ExtendedContentMessage_CTA)(nil), // 5: WAArmadilloXMA.ExtendedContentMessage.CTA
|
||||
(*waCommon.SubProtocol)(nil), // 6: WACommon.SubProtocol
|
||||
}
|
||||
var file_waArmadilloXMA_WAArmadilloXMA_proto_depIdxs = []int32{
|
||||
6, // 0: WAArmadilloXMA.ExtendedContentMessage.associatedMessage:type_name -> WACommon.SubProtocol
|
||||
3, // 1: WAArmadilloXMA.ExtendedContentMessage.targetType:type_name -> WAArmadilloXMA.ExtendedContentMessage.ExtendedContentType
|
||||
2, // 2: WAArmadilloXMA.ExtendedContentMessage.xmaLayoutType:type_name -> WAArmadilloXMA.ExtendedContentMessage.XmaLayoutType
|
||||
5, // 3: WAArmadilloXMA.ExtendedContentMessage.ctas:type_name -> WAArmadilloXMA.ExtendedContentMessage.CTA
|
||||
6, // 4: WAArmadilloXMA.ExtendedContentMessage.previews:type_name -> WACommon.SubProtocol
|
||||
6, // 5: WAArmadilloXMA.ExtendedContentMessage.favicon:type_name -> WACommon.SubProtocol
|
||||
6, // 6: WAArmadilloXMA.ExtendedContentMessage.headerImage:type_name -> WACommon.SubProtocol
|
||||
0, // 7: WAArmadilloXMA.ExtendedContentMessage.overlayIconGlyph:type_name -> WAArmadilloXMA.ExtendedContentMessage.OverlayIconGlyph
|
||||
1, // 8: WAArmadilloXMA.ExtendedContentMessage.CTA.buttonType:type_name -> WAArmadilloXMA.ExtendedContentMessage.CtaButtonType
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_waArmadilloXMA_WAArmadilloXMA_proto_init() }
|
||||
func file_waArmadilloXMA_WAArmadilloXMA_proto_init() {
|
||||
if File_waArmadilloXMA_WAArmadilloXMA_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ExtendedContentMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ExtendedContentMessage_CTA); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_waArmadilloXMA_WAArmadilloXMA_proto_rawDesc,
|
||||
NumEnums: 4,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_waArmadilloXMA_WAArmadilloXMA_proto_goTypes,
|
||||
DependencyIndexes: file_waArmadilloXMA_WAArmadilloXMA_proto_depIdxs,
|
||||
EnumInfos: file_waArmadilloXMA_WAArmadilloXMA_proto_enumTypes,
|
||||
MessageInfos: file_waArmadilloXMA_WAArmadilloXMA_proto_msgTypes,
|
||||
}.Build()
|
||||
File_waArmadilloXMA_WAArmadilloXMA_proto = out.File
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_rawDesc = nil
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_goTypes = nil
|
||||
file_waArmadilloXMA_WAArmadilloXMA_proto_depIdxs = nil
|
||||
}
|
||||
BIN
Binary file not shown.
+118
@@ -0,0 +1,118 @@
|
||||
syntax = "proto3";
|
||||
package WAArmadilloXMA;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waArmadilloXMA";
|
||||
|
||||
import "waCommon/WACommon.proto";
|
||||
|
||||
message ExtendedContentMessage {
|
||||
enum OverlayIconGlyph {
|
||||
INFO = 0;
|
||||
EYE_OFF = 1;
|
||||
NEWS_OFF = 2;
|
||||
WARNING = 3;
|
||||
PRIVATE = 4;
|
||||
NONE = 5;
|
||||
MEDIA_LABEL = 6;
|
||||
POST_COVER = 7;
|
||||
POST_LABEL = 8;
|
||||
WARNING_SCREENS = 9;
|
||||
}
|
||||
|
||||
enum CtaButtonType {
|
||||
CTABUTTONTYPE_UNKNOWN = 0;
|
||||
OPEN_NATIVE = 11;
|
||||
}
|
||||
|
||||
enum XmaLayoutType {
|
||||
SINGLE = 0;
|
||||
PORTRAIT = 3;
|
||||
STANDARD_DXMA = 12;
|
||||
LIST_DXMA = 15;
|
||||
}
|
||||
|
||||
enum ExtendedContentType {
|
||||
EXTENDEDCONTENTTYPE_UNKNOWN = 0;
|
||||
IG_STORY_PHOTO_MENTION = 4;
|
||||
IG_SINGLE_IMAGE_POST_SHARE = 9;
|
||||
IG_MULTIPOST_SHARE = 10;
|
||||
IG_SINGLE_VIDEO_POST_SHARE = 11;
|
||||
IG_STORY_PHOTO_SHARE = 12;
|
||||
IG_STORY_VIDEO_SHARE = 13;
|
||||
IG_CLIPS_SHARE = 14;
|
||||
IG_IGTV_SHARE = 15;
|
||||
IG_SHOP_SHARE = 16;
|
||||
IG_PROFILE_SHARE = 19;
|
||||
IG_STORY_PHOTO_HIGHLIGHT_SHARE = 20;
|
||||
IG_STORY_VIDEO_HIGHLIGHT_SHARE = 21;
|
||||
IG_STORY_REPLY = 22;
|
||||
IG_STORY_REACTION = 23;
|
||||
IG_STORY_VIDEO_MENTION = 24;
|
||||
IG_STORY_HIGHLIGHT_REPLY = 25;
|
||||
IG_STORY_HIGHLIGHT_REACTION = 26;
|
||||
IG_EXTERNAL_LINK = 27;
|
||||
IG_RECEIVER_FETCH = 28;
|
||||
FB_FEED_SHARE = 1000;
|
||||
FB_STORY_REPLY = 1001;
|
||||
FB_STORY_SHARE = 1002;
|
||||
FB_STORY_MENTION = 1003;
|
||||
FB_FEED_VIDEO_SHARE = 1004;
|
||||
FB_GAMING_CUSTOM_UPDATE = 1005;
|
||||
FB_PRODUCER_STORY_REPLY = 1006;
|
||||
FB_EVENT = 1007;
|
||||
FB_FEED_POST_PRIVATE_REPLY = 1008;
|
||||
FB_SHORT = 1009;
|
||||
FB_COMMENT_MENTION_SHARE = 1010;
|
||||
MSG_EXTERNAL_LINK_SHARE = 2000;
|
||||
MSG_P2P_PAYMENT = 2001;
|
||||
MSG_LOCATION_SHARING = 2002;
|
||||
MSG_LOCATION_SHARING_V2 = 2003;
|
||||
MSG_HIGHLIGHTS_TAB_FRIEND_UPDATES_REPLY = 2004;
|
||||
MSG_HIGHLIGHTS_TAB_LOCAL_EVENT_REPLY = 2005;
|
||||
MSG_RECEIVER_FETCH = 2006;
|
||||
MSG_IG_MEDIA_SHARE = 2007;
|
||||
MSG_GEN_AI_SEARCH_PLUGIN_RESPONSE = 2008;
|
||||
MSG_REELS_LIST = 2009;
|
||||
MSG_CONTACT = 2010;
|
||||
RTC_AUDIO_CALL = 3000;
|
||||
RTC_VIDEO_CALL = 3001;
|
||||
RTC_MISSED_AUDIO_CALL = 3002;
|
||||
RTC_MISSED_VIDEO_CALL = 3003;
|
||||
RTC_GROUP_AUDIO_CALL = 3004;
|
||||
RTC_GROUP_VIDEO_CALL = 3005;
|
||||
RTC_MISSED_GROUP_AUDIO_CALL = 3006;
|
||||
RTC_MISSED_GROUP_VIDEO_CALL = 3007;
|
||||
DATACLASS_SENDER_COPY = 4000;
|
||||
}
|
||||
|
||||
message CTA {
|
||||
CtaButtonType buttonType = 1;
|
||||
string title = 2;
|
||||
string actionURL = 3;
|
||||
string nativeURL = 4;
|
||||
string ctaType = 5;
|
||||
}
|
||||
|
||||
WACommon.SubProtocol associatedMessage = 1;
|
||||
ExtendedContentType targetType = 2;
|
||||
string targetUsername = 3;
|
||||
string targetID = 4;
|
||||
int64 targetExpiringAtSec = 5;
|
||||
XmaLayoutType xmaLayoutType = 6;
|
||||
repeated CTA ctas = 7;
|
||||
repeated WACommon.SubProtocol previews = 8;
|
||||
string titleText = 9;
|
||||
string subtitleText = 10;
|
||||
uint32 maxTitleNumOfLines = 11;
|
||||
uint32 maxSubtitleNumOfLines = 12;
|
||||
WACommon.SubProtocol favicon = 13;
|
||||
WACommon.SubProtocol headerImage = 14;
|
||||
string headerTitle = 15;
|
||||
OverlayIconGlyph overlayIconGlyph = 16;
|
||||
string overlayTitle = 17;
|
||||
string overlayDescription = 18;
|
||||
string sentWithMessageID = 19;
|
||||
string messageText = 20;
|
||||
string headerSubtitle = 21;
|
||||
string xmaDataclass = 22;
|
||||
string contentRef = 23;
|
||||
}
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v3.21.12
|
||||
// source: waCommon/WACommon.proto
|
||||
|
||||
package waCommon
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type FutureProofBehavior int32
|
||||
|
||||
const (
|
||||
FutureProofBehavior_PLACEHOLDER FutureProofBehavior = 0
|
||||
FutureProofBehavior_NO_PLACEHOLDER FutureProofBehavior = 1
|
||||
FutureProofBehavior_IGNORE FutureProofBehavior = 2
|
||||
)
|
||||
|
||||
// Enum value maps for FutureProofBehavior.
|
||||
var (
|
||||
FutureProofBehavior_name = map[int32]string{
|
||||
0: "PLACEHOLDER",
|
||||
1: "NO_PLACEHOLDER",
|
||||
2: "IGNORE",
|
||||
}
|
||||
FutureProofBehavior_value = map[string]int32{
|
||||
"PLACEHOLDER": 0,
|
||||
"NO_PLACEHOLDER": 1,
|
||||
"IGNORE": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x FutureProofBehavior) Enum() *FutureProofBehavior {
|
||||
p := new(FutureProofBehavior)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x FutureProofBehavior) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (FutureProofBehavior) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waCommon_WACommon_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (FutureProofBehavior) Type() protoreflect.EnumType {
|
||||
return &file_waCommon_WACommon_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x FutureProofBehavior) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FutureProofBehavior.Descriptor instead.
|
||||
func (FutureProofBehavior) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waCommon_WACommon_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type Command_CommandType int32
|
||||
|
||||
const (
|
||||
Command_COMMANDTYPE_UNKNOWN Command_CommandType = 0
|
||||
Command_EVERYONE Command_CommandType = 1
|
||||
Command_SILENT Command_CommandType = 2
|
||||
Command_AI Command_CommandType = 3
|
||||
)
|
||||
|
||||
// Enum value maps for Command_CommandType.
|
||||
var (
|
||||
Command_CommandType_name = map[int32]string{
|
||||
0: "COMMANDTYPE_UNKNOWN",
|
||||
1: "EVERYONE",
|
||||
2: "SILENT",
|
||||
3: "AI",
|
||||
}
|
||||
Command_CommandType_value = map[string]int32{
|
||||
"COMMANDTYPE_UNKNOWN": 0,
|
||||
"EVERYONE": 1,
|
||||
"SILENT": 2,
|
||||
"AI": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Command_CommandType) Enum() *Command_CommandType {
|
||||
p := new(Command_CommandType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Command_CommandType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Command_CommandType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waCommon_WACommon_proto_enumTypes[1].Descriptor()
|
||||
}
|
||||
|
||||
func (Command_CommandType) Type() protoreflect.EnumType {
|
||||
return &file_waCommon_WACommon_proto_enumTypes[1]
|
||||
}
|
||||
|
||||
func (x Command_CommandType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Command_CommandType.Descriptor instead.
|
||||
func (Command_CommandType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waCommon_WACommon_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
type MessageKey struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RemoteJID string `protobuf:"bytes,1,opt,name=remoteJID,proto3" json:"remoteJID,omitempty"`
|
||||
FromMe bool `protobuf:"varint,2,opt,name=fromMe,proto3" json:"fromMe,omitempty"`
|
||||
ID string `protobuf:"bytes,3,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Participant string `protobuf:"bytes,4,opt,name=participant,proto3" json:"participant,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageKey) Reset() {
|
||||
*x = MessageKey{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageKey) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageKey) ProtoMessage() {}
|
||||
|
||||
func (x *MessageKey) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[0]
|
||||
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 MessageKey.ProtoReflect.Descriptor instead.
|
||||
func (*MessageKey) Descriptor() ([]byte, []int) {
|
||||
return file_waCommon_WACommon_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MessageKey) GetRemoteJID() string {
|
||||
if x != nil {
|
||||
return x.RemoteJID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MessageKey) GetFromMe() bool {
|
||||
if x != nil {
|
||||
return x.FromMe
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *MessageKey) GetID() string {
|
||||
if x != nil {
|
||||
return x.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MessageKey) GetParticipant() string {
|
||||
if x != nil {
|
||||
return x.Participant
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
CommandType Command_CommandType `protobuf:"varint,1,opt,name=commandType,proto3,enum=WACommon.Command_CommandType" json:"commandType,omitempty"`
|
||||
Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
|
||||
Length uint32 `protobuf:"varint,3,opt,name=length,proto3" json:"length,omitempty"`
|
||||
ValidationToken string `protobuf:"bytes,4,opt,name=validationToken,proto3" json:"validationToken,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Command) Reset() {
|
||||
*x = Command{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Command) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Command) ProtoMessage() {}
|
||||
|
||||
func (x *Command) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[1]
|
||||
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 Command.ProtoReflect.Descriptor instead.
|
||||
func (*Command) Descriptor() ([]byte, []int) {
|
||||
return file_waCommon_WACommon_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Command) GetCommandType() Command_CommandType {
|
||||
if x != nil {
|
||||
return x.CommandType
|
||||
}
|
||||
return Command_COMMANDTYPE_UNKNOWN
|
||||
}
|
||||
|
||||
func (x *Command) GetOffset() uint32 {
|
||||
if x != nil {
|
||||
return x.Offset
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Command) GetLength() uint32 {
|
||||
if x != nil {
|
||||
return x.Length
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Command) GetValidationToken() string {
|
||||
if x != nil {
|
||||
return x.ValidationToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type MessageText struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"`
|
||||
MentionedJID []string `protobuf:"bytes,2,rep,name=mentionedJID,proto3" json:"mentionedJID,omitempty"`
|
||||
Commands []*Command `protobuf:"bytes,3,rep,name=commands,proto3" json:"commands,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageText) Reset() {
|
||||
*x = MessageText{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageText) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageText) ProtoMessage() {}
|
||||
|
||||
func (x *MessageText) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[2]
|
||||
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 MessageText.ProtoReflect.Descriptor instead.
|
||||
func (*MessageText) Descriptor() ([]byte, []int) {
|
||||
return file_waCommon_WACommon_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *MessageText) GetText() string {
|
||||
if x != nil {
|
||||
return x.Text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MessageText) GetMentionedJID() []string {
|
||||
if x != nil {
|
||||
return x.MentionedJID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageText) GetCommands() []*Command {
|
||||
if x != nil {
|
||||
return x.Commands
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SubProtocol struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
Version int32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func (x *SubProtocol) Reset() {
|
||||
*x = SubProtocol{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *SubProtocol) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SubProtocol) ProtoMessage() {}
|
||||
|
||||
func (x *SubProtocol) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waCommon_WACommon_proto_msgTypes[3]
|
||||
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 SubProtocol.ProtoReflect.Descriptor instead.
|
||||
func (*SubProtocol) Descriptor() ([]byte, []int) {
|
||||
return file_waCommon_WACommon_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *SubProtocol) GetPayload() []byte {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SubProtocol) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_waCommon_WACommon_proto protoreflect.FileDescriptor
|
||||
|
||||
//go:embed WACommon.pb.raw
|
||||
var file_waCommon_WACommon_proto_rawDesc []byte
|
||||
|
||||
var (
|
||||
file_waCommon_WACommon_proto_rawDescOnce sync.Once
|
||||
file_waCommon_WACommon_proto_rawDescData = file_waCommon_WACommon_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_waCommon_WACommon_proto_rawDescGZIP() []byte {
|
||||
file_waCommon_WACommon_proto_rawDescOnce.Do(func() {
|
||||
file_waCommon_WACommon_proto_rawDescData = protoimpl.X.CompressGZIP(file_waCommon_WACommon_proto_rawDescData)
|
||||
})
|
||||
return file_waCommon_WACommon_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_waCommon_WACommon_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_waCommon_WACommon_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_waCommon_WACommon_proto_goTypes = []interface{}{
|
||||
(FutureProofBehavior)(0), // 0: WACommon.FutureProofBehavior
|
||||
(Command_CommandType)(0), // 1: WACommon.Command.CommandType
|
||||
(*MessageKey)(nil), // 2: WACommon.MessageKey
|
||||
(*Command)(nil), // 3: WACommon.Command
|
||||
(*MessageText)(nil), // 4: WACommon.MessageText
|
||||
(*SubProtocol)(nil), // 5: WACommon.SubProtocol
|
||||
}
|
||||
var file_waCommon_WACommon_proto_depIdxs = []int32{
|
||||
1, // 0: WACommon.Command.commandType:type_name -> WACommon.Command.CommandType
|
||||
3, // 1: WACommon.MessageText.commands:type_name -> WACommon.Command
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_waCommon_WACommon_proto_init() }
|
||||
func file_waCommon_WACommon_proto_init() {
|
||||
if File_waCommon_WACommon_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_waCommon_WACommon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageKey); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waCommon_WACommon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Command); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waCommon_WACommon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageText); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waCommon_WACommon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*SubProtocol); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_waCommon_WACommon_proto_rawDesc,
|
||||
NumEnums: 2,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_waCommon_WACommon_proto_goTypes,
|
||||
DependencyIndexes: file_waCommon_WACommon_proto_depIdxs,
|
||||
EnumInfos: file_waCommon_WACommon_proto_enumTypes,
|
||||
MessageInfos: file_waCommon_WACommon_proto_msgTypes,
|
||||
}.Build()
|
||||
File_waCommon_WACommon_proto = out.File
|
||||
file_waCommon_WACommon_proto_rawDesc = nil
|
||||
file_waCommon_WACommon_proto_goTypes = nil
|
||||
file_waCommon_WACommon_proto_depIdxs = nil
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
syntax = "proto3";
|
||||
package WACommon;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waCommon";
|
||||
|
||||
enum FutureProofBehavior {
|
||||
PLACEHOLDER = 0;
|
||||
NO_PLACEHOLDER = 1;
|
||||
IGNORE = 2;
|
||||
}
|
||||
|
||||
message MessageKey {
|
||||
string remoteJID = 1;
|
||||
bool fromMe = 2;
|
||||
string ID = 3;
|
||||
string participant = 4;
|
||||
}
|
||||
|
||||
message Command {
|
||||
enum CommandType {
|
||||
COMMANDTYPE_UNKNOWN = 0;
|
||||
EVERYONE = 1;
|
||||
SILENT = 2;
|
||||
AI = 3;
|
||||
}
|
||||
|
||||
CommandType commandType = 1;
|
||||
uint32 offset = 2;
|
||||
uint32 length = 3;
|
||||
string validationToken = 4;
|
||||
}
|
||||
|
||||
message MessageText {
|
||||
string text = 1;
|
||||
repeated string mentionedJID = 2;
|
||||
repeated Command commands = 3;
|
||||
}
|
||||
|
||||
message SubProtocol {
|
||||
bytes payload = 1;
|
||||
int32 version = 2;
|
||||
}
|
||||
Generated
Vendored
+3069
File diff suppressed because it is too large
Load Diff
Generated
Vendored
BIN
Binary file not shown.
Vendored
+234
@@ -0,0 +1,234 @@
|
||||
syntax = "proto3";
|
||||
package WAConsumerApplication;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waConsumerApplication";
|
||||
|
||||
import "waCommon/WACommon.proto";
|
||||
|
||||
message ConsumerApplication {
|
||||
message Payload {
|
||||
oneof payload {
|
||||
Content content = 1;
|
||||
ApplicationData applicationData = 2;
|
||||
Signal signal = 3;
|
||||
SubProtocolPayload subProtocol = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message SubProtocolPayload {
|
||||
WACommon.FutureProofBehavior futureProof = 1;
|
||||
}
|
||||
|
||||
message Metadata {
|
||||
enum SpecialTextSize {
|
||||
SPECIALTEXTSIZE_UNKNOWN = 0;
|
||||
SMALL = 1;
|
||||
MEDIUM = 2;
|
||||
LARGE = 3;
|
||||
}
|
||||
|
||||
SpecialTextSize specialTextSize = 1;
|
||||
}
|
||||
|
||||
message Signal {
|
||||
}
|
||||
|
||||
message ApplicationData {
|
||||
oneof applicationContent {
|
||||
RevokeMessage revoke = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message Content {
|
||||
oneof content {
|
||||
WACommon.MessageText messageText = 1;
|
||||
ImageMessage imageMessage = 2;
|
||||
ContactMessage contactMessage = 3;
|
||||
LocationMessage locationMessage = 4;
|
||||
ExtendedTextMessage extendedTextMessage = 5;
|
||||
StatusTextMesage statusTextMessage = 6;
|
||||
DocumentMessage documentMessage = 7;
|
||||
AudioMessage audioMessage = 8;
|
||||
VideoMessage videoMessage = 9;
|
||||
ContactsArrayMessage contactsArrayMessage = 10;
|
||||
LiveLocationMessage liveLocationMessage = 11;
|
||||
StickerMessage stickerMessage = 12;
|
||||
GroupInviteMessage groupInviteMessage = 13;
|
||||
ViewOnceMessage viewOnceMessage = 14;
|
||||
ReactionMessage reactionMessage = 16;
|
||||
PollCreationMessage pollCreationMessage = 17;
|
||||
PollUpdateMessage pollUpdateMessage = 18;
|
||||
EditMessage editMessage = 19;
|
||||
}
|
||||
}
|
||||
|
||||
message EditMessage {
|
||||
WACommon.MessageKey key = 1;
|
||||
WACommon.MessageText message = 2;
|
||||
int64 timestampMS = 3;
|
||||
}
|
||||
|
||||
message PollAddOptionMessage {
|
||||
repeated Option pollOption = 1;
|
||||
}
|
||||
|
||||
message PollVoteMessage {
|
||||
repeated bytes selectedOptions = 1;
|
||||
int64 senderTimestampMS = 2;
|
||||
}
|
||||
|
||||
message PollEncValue {
|
||||
bytes encPayload = 1;
|
||||
bytes encIV = 2;
|
||||
}
|
||||
|
||||
message PollUpdateMessage {
|
||||
WACommon.MessageKey pollCreationMessageKey = 1;
|
||||
PollEncValue vote = 2;
|
||||
PollEncValue addOption = 3;
|
||||
}
|
||||
|
||||
message PollCreationMessage {
|
||||
bytes encKey = 1;
|
||||
string name = 2;
|
||||
repeated Option options = 3;
|
||||
uint32 selectableOptionsCount = 4;
|
||||
}
|
||||
|
||||
message Option {
|
||||
string optionName = 1;
|
||||
}
|
||||
|
||||
message ReactionMessage {
|
||||
WACommon.MessageKey key = 1;
|
||||
string text = 2;
|
||||
string groupingKey = 3;
|
||||
int64 senderTimestampMS = 4;
|
||||
string reactionMetadataDataclassData = 5;
|
||||
int32 style = 6;
|
||||
}
|
||||
|
||||
message RevokeMessage {
|
||||
WACommon.MessageKey key = 1;
|
||||
}
|
||||
|
||||
message ViewOnceMessage {
|
||||
oneof viewOnceContent {
|
||||
ImageMessage imageMessage = 1;
|
||||
VideoMessage videoMessage = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message GroupInviteMessage {
|
||||
string groupJID = 1;
|
||||
string inviteCode = 2;
|
||||
int64 inviteExpiration = 3;
|
||||
string groupName = 4;
|
||||
bytes JPEGThumbnail = 5;
|
||||
WACommon.MessageText caption = 6;
|
||||
}
|
||||
|
||||
message LiveLocationMessage {
|
||||
Location location = 1;
|
||||
uint32 accuracyInMeters = 2;
|
||||
float speedInMps = 3;
|
||||
uint32 degreesClockwiseFromMagneticNorth = 4;
|
||||
WACommon.MessageText caption = 5;
|
||||
int64 sequenceNumber = 6;
|
||||
uint32 timeOffset = 7;
|
||||
}
|
||||
|
||||
message ContactsArrayMessage {
|
||||
string displayName = 1;
|
||||
repeated ContactMessage contacts = 2;
|
||||
}
|
||||
|
||||
message ContactMessage {
|
||||
WACommon.SubProtocol contact = 1;
|
||||
}
|
||||
|
||||
message StatusTextMesage {
|
||||
enum FontType {
|
||||
SANS_SERIF = 0;
|
||||
SERIF = 1;
|
||||
NORICAN_REGULAR = 2;
|
||||
BRYNDAN_WRITE = 3;
|
||||
BEBASNEUE_REGULAR = 4;
|
||||
OSWALD_HEAVY = 5;
|
||||
}
|
||||
|
||||
ExtendedTextMessage text = 1;
|
||||
fixed32 textArgb = 6;
|
||||
fixed32 backgroundArgb = 7;
|
||||
FontType font = 8;
|
||||
}
|
||||
|
||||
message ExtendedTextMessage {
|
||||
enum PreviewType {
|
||||
NONE = 0;
|
||||
VIDEO = 1;
|
||||
}
|
||||
|
||||
WACommon.MessageText text = 1;
|
||||
string matchedText = 2;
|
||||
string canonicalURL = 3;
|
||||
string description = 4;
|
||||
string title = 5;
|
||||
WACommon.SubProtocol thumbnail = 6;
|
||||
PreviewType previewType = 7;
|
||||
}
|
||||
|
||||
message LocationMessage {
|
||||
Location location = 1;
|
||||
string address = 2;
|
||||
}
|
||||
|
||||
message StickerMessage {
|
||||
WACommon.SubProtocol sticker = 1;
|
||||
}
|
||||
|
||||
message DocumentMessage {
|
||||
WACommon.SubProtocol document = 1;
|
||||
string fileName = 2;
|
||||
}
|
||||
|
||||
message VideoMessage {
|
||||
WACommon.SubProtocol video = 1;
|
||||
WACommon.MessageText caption = 2;
|
||||
}
|
||||
|
||||
message AudioMessage {
|
||||
WACommon.SubProtocol audio = 1;
|
||||
bool PTT = 2;
|
||||
}
|
||||
|
||||
message ImageMessage {
|
||||
WACommon.SubProtocol image = 1;
|
||||
WACommon.MessageText caption = 2;
|
||||
}
|
||||
|
||||
message InteractiveAnnotation {
|
||||
oneof action {
|
||||
Location location = 2;
|
||||
}
|
||||
|
||||
repeated Point polygonVertices = 1;
|
||||
}
|
||||
|
||||
message Point {
|
||||
double x = 1;
|
||||
double y = 2;
|
||||
}
|
||||
|
||||
message Location {
|
||||
double degreesLatitude = 1;
|
||||
double degreesLongitude = 2;
|
||||
string name = 3;
|
||||
}
|
||||
|
||||
message MediaPayload {
|
||||
WACommon.SubProtocol protocol = 1;
|
||||
}
|
||||
|
||||
Payload payload = 1;
|
||||
Metadata metadata = 2;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package waConsumerApplication
|
||||
|
||||
import (
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/armadilloutil"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMediaTransport"
|
||||
)
|
||||
|
||||
type ConsumerApplication_Content_Content = isConsumerApplication_Content_Content
|
||||
|
||||
func (*ConsumerApplication) IsMessageApplicationSub() {}
|
||||
|
||||
const (
|
||||
ImageTransportVersion = 1
|
||||
StickerTransportVersion = 1
|
||||
VideoTransportVersion = 1
|
||||
AudioTransportVersion = 1
|
||||
DocumentTransportVersion = 1
|
||||
ContactTransportVersion = 1
|
||||
)
|
||||
|
||||
func (msg *ConsumerApplication_ImageMessage) Decode() (dec *waMediaTransport.ImageTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.ImageTransport{}, msg.GetImage(), ImageTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_ImageMessage) Set(payload *waMediaTransport.ImageTransport) (err error) {
|
||||
msg.Image, err = armadilloutil.Marshal(payload, ImageTransportVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_StickerMessage) Decode() (dec *waMediaTransport.StickerTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.StickerTransport{}, msg.GetSticker(), StickerTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_StickerMessage) Set(payload *waMediaTransport.StickerTransport) (err error) {
|
||||
msg.Sticker, err = armadilloutil.Marshal(payload, StickerTransportVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_ExtendedTextMessage) DecodeThumbnail() (dec *waMediaTransport.ImageTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.ImageTransport{}, msg.GetThumbnail(), ImageTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_ExtendedTextMessage) SetThumbnail(payload *waMediaTransport.ImageTransport) (err error) {
|
||||
msg.Thumbnail, err = armadilloutil.Marshal(payload, ImageTransportVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_VideoMessage) Decode() (dec *waMediaTransport.VideoTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.VideoTransport{}, msg.GetVideo(), VideoTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_VideoMessage) Set(payload *waMediaTransport.VideoTransport) (err error) {
|
||||
msg.Video, err = armadilloutil.Marshal(payload, VideoTransportVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_AudioMessage) Decode() (dec *waMediaTransport.AudioTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.AudioTransport{}, msg.GetAudio(), AudioTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_AudioMessage) Set(payload *waMediaTransport.AudioTransport) (err error) {
|
||||
msg.Audio, err = armadilloutil.Marshal(payload, AudioTransportVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_DocumentMessage) Decode() (dec *waMediaTransport.DocumentTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.DocumentTransport{}, msg.GetDocument(), DocumentTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_DocumentMessage) Set(payload *waMediaTransport.DocumentTransport) (err error) {
|
||||
msg.Document, err = armadilloutil.Marshal(payload, DocumentTransportVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_ContactMessage) Decode() (dec *waMediaTransport.ContactTransport, err error) {
|
||||
return armadilloutil.Unmarshal(&waMediaTransport.ContactTransport{}, msg.GetContact(), ContactTransportVersion)
|
||||
}
|
||||
|
||||
func (msg *ConsumerApplication_ContactMessage) Set(payload *waMediaTransport.ContactTransport) (err error) {
|
||||
msg.Contact, err = armadilloutil.Marshal(payload, ContactTransportVersion)
|
||||
return
|
||||
}
|
||||
Generated
Vendored
+1962
File diff suppressed because it is too large
Load Diff
Generated
Vendored
BIN
Binary file not shown.
+154
@@ -0,0 +1,154 @@
|
||||
syntax = "proto3";
|
||||
package WAMediaTransport;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waMediaTransport";
|
||||
|
||||
import "waCommon/WACommon.proto";
|
||||
|
||||
message WAMediaTransport {
|
||||
message Ancillary {
|
||||
message Thumbnail {
|
||||
message DownloadableThumbnail {
|
||||
bytes fileSHA256 = 1;
|
||||
bytes fileEncSHA256 = 2;
|
||||
string directPath = 3;
|
||||
bytes mediaKey = 4;
|
||||
int64 mediaKeyTimestamp = 5;
|
||||
string objectID = 6;
|
||||
}
|
||||
|
||||
bytes JPEGThumbnail = 1;
|
||||
DownloadableThumbnail downloadableThumbnail = 2;
|
||||
uint32 thumbnailWidth = 3;
|
||||
uint32 thumbnailHeight = 4;
|
||||
}
|
||||
|
||||
uint64 fileLength = 1;
|
||||
string mimetype = 2;
|
||||
Thumbnail thumbnail = 3;
|
||||
string objectID = 4;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
bytes fileSHA256 = 1;
|
||||
bytes mediaKey = 2;
|
||||
bytes fileEncSHA256 = 3;
|
||||
string directPath = 4;
|
||||
int64 mediaKeyTimestamp = 5;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
message ImageTransport {
|
||||
message Ancillary {
|
||||
enum HdType {
|
||||
NONE = 0;
|
||||
LQ_4K = 1;
|
||||
HQ_4K = 2;
|
||||
}
|
||||
|
||||
uint32 height = 1;
|
||||
uint32 width = 2;
|
||||
bytes scansSidecar = 3;
|
||||
repeated uint32 scanLengths = 4;
|
||||
bytes midQualityFileSHA256 = 5;
|
||||
HdType hdType = 6;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
WAMediaTransport transport = 1;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
message VideoTransport {
|
||||
message Ancillary {
|
||||
enum Attribution {
|
||||
NONE = 0;
|
||||
GIPHY = 1;
|
||||
TENOR = 2;
|
||||
}
|
||||
|
||||
uint32 seconds = 1;
|
||||
WACommon.MessageText caption = 2;
|
||||
bool gifPlayback = 3;
|
||||
uint32 height = 4;
|
||||
uint32 width = 5;
|
||||
bytes sidecar = 6;
|
||||
Attribution gifAttribution = 7;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
WAMediaTransport transport = 1;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
message AudioTransport {
|
||||
message Ancillary {
|
||||
uint32 seconds = 1;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
WAMediaTransport transport = 1;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
message DocumentTransport {
|
||||
message Ancillary {
|
||||
uint32 pageCount = 1;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
WAMediaTransport transport = 1;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
message StickerTransport {
|
||||
message Ancillary {
|
||||
uint32 pageCount = 1;
|
||||
uint32 height = 2;
|
||||
uint32 width = 3;
|
||||
uint32 firstFrameLength = 4;
|
||||
bytes firstFrameSidecar = 5;
|
||||
string mustacheText = 6;
|
||||
bool isThirdParty = 7;
|
||||
string receiverFetchID = 8;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
WAMediaTransport transport = 1;
|
||||
bool isAnimated = 2;
|
||||
string receiverFetchID = 3;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
message ContactTransport {
|
||||
message Ancillary {
|
||||
string displayName = 1;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
oneof contact {
|
||||
string vcard = 1;
|
||||
WAMediaTransport downloadableVcard = 2;
|
||||
}
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
Generated
Vendored
+1120
File diff suppressed because it is too large
Load Diff
Generated
Vendored
BIN
Binary file not shown.
+87
@@ -0,0 +1,87 @@
|
||||
syntax = "proto3";
|
||||
package WAMsgApplication;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waMsgApplication";
|
||||
|
||||
import "waCommon/WACommon.proto";
|
||||
|
||||
message MessageApplication {
|
||||
message Metadata {
|
||||
enum ThreadType {
|
||||
DEFAULT = 0;
|
||||
VANISH_MODE = 1;
|
||||
DISAPPEARING_MESSAGES = 2;
|
||||
}
|
||||
|
||||
message QuotedMessage {
|
||||
string stanzaID = 1;
|
||||
string remoteJID = 2;
|
||||
string participant = 3;
|
||||
Payload payload = 4;
|
||||
}
|
||||
|
||||
message EphemeralSettingMap {
|
||||
string chatJID = 1;
|
||||
EphemeralSetting ephemeralSetting = 2;
|
||||
}
|
||||
|
||||
oneof ephemeral {
|
||||
EphemeralSetting chatEphemeralSetting = 1;
|
||||
EphemeralSettingMap ephemeralSettingList = 2;
|
||||
bytes ephemeralSharedSecret = 3;
|
||||
}
|
||||
|
||||
uint32 forwardingScore = 5;
|
||||
bool isForwarded = 6;
|
||||
WACommon.SubProtocol businessMetadata = 7;
|
||||
bytes frankingKey = 8;
|
||||
int32 frankingVersion = 9;
|
||||
QuotedMessage quotedMessage = 10;
|
||||
ThreadType threadType = 11;
|
||||
string readonlyMetadataDataclass = 12;
|
||||
string groupID = 13;
|
||||
uint32 groupSize = 14;
|
||||
uint32 groupIndex = 15;
|
||||
string botResponseID = 16;
|
||||
string collapsibleID = 17;
|
||||
}
|
||||
|
||||
message Payload {
|
||||
oneof content {
|
||||
Content coreContent = 1;
|
||||
Signal signal = 2;
|
||||
ApplicationData applicationData = 3;
|
||||
SubProtocolPayload subProtocol = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message SubProtocolPayload {
|
||||
oneof subProtocol {
|
||||
WACommon.SubProtocol consumerMessage = 2;
|
||||
WACommon.SubProtocol businessMessage = 3;
|
||||
WACommon.SubProtocol paymentMessage = 4;
|
||||
WACommon.SubProtocol multiDevice = 5;
|
||||
WACommon.SubProtocol voip = 6;
|
||||
WACommon.SubProtocol armadillo = 7;
|
||||
}
|
||||
|
||||
WACommon.FutureProofBehavior futureProof = 1;
|
||||
}
|
||||
|
||||
message ApplicationData {
|
||||
}
|
||||
|
||||
message Signal {
|
||||
}
|
||||
|
||||
message Content {
|
||||
}
|
||||
|
||||
message EphemeralSetting {
|
||||
uint32 ephemeralExpiration = 2;
|
||||
int64 ephemeralSettingTimestamp = 3;
|
||||
bool isEphemeralSettingReset = 4;
|
||||
}
|
||||
|
||||
Payload payload = 1;
|
||||
Metadata metadata = 2;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package waMsgApplication
|
||||
|
||||
import (
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/armadilloutil"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waArmadilloApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waConsumerApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMultiDevice"
|
||||
)
|
||||
|
||||
const (
|
||||
ConsumerApplicationVersion = 1
|
||||
ArmadilloApplicationVersion = 1
|
||||
MultiDeviceApplicationVersion = 1 // TODO: check
|
||||
)
|
||||
|
||||
func (msg *MessageApplication_SubProtocolPayload_ConsumerMessage) Decode() (*waConsumerApplication.ConsumerApplication, error) {
|
||||
return armadilloutil.Unmarshal(&waConsumerApplication.ConsumerApplication{}, msg.ConsumerMessage, ConsumerApplicationVersion)
|
||||
}
|
||||
|
||||
func (msg *MessageApplication_SubProtocolPayload_ConsumerMessage) Set(payload *waConsumerApplication.ConsumerApplication) (err error) {
|
||||
msg.ConsumerMessage, err = armadilloutil.Marshal(payload, ConsumerApplicationVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *MessageApplication_SubProtocolPayload_Armadillo) Decode() (*waArmadilloApplication.Armadillo, error) {
|
||||
return armadilloutil.Unmarshal(&waArmadilloApplication.Armadillo{}, msg.Armadillo, ArmadilloApplicationVersion)
|
||||
}
|
||||
|
||||
func (msg *MessageApplication_SubProtocolPayload_Armadillo) Set(payload *waArmadilloApplication.Armadillo) (err error) {
|
||||
msg.Armadillo, err = armadilloutil.Marshal(payload, ArmadilloApplicationVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (msg *MessageApplication_SubProtocolPayload_MultiDevice) Decode() (*waMultiDevice.MultiDevice, error) {
|
||||
return armadilloutil.Unmarshal(&waMultiDevice.MultiDevice{}, msg.MultiDevice, MultiDeviceApplicationVersion)
|
||||
}
|
||||
|
||||
func (msg *MessageApplication_SubProtocolPayload_MultiDevice) Set(payload *waMultiDevice.MultiDevice) (err error) {
|
||||
msg.MultiDevice, err = armadilloutil.Marshal(payload, MultiDeviceApplicationVersion)
|
||||
return
|
||||
}
|
||||
+964
@@ -0,0 +1,964 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v3.21.12
|
||||
// source: waMsgTransport/WAMsgTransport.proto
|
||||
|
||||
package waMsgTransport
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
waCommon "go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type MessageTransport_Protocol_Ancillary_BackupDirective_ActionType int32
|
||||
|
||||
const (
|
||||
MessageTransport_Protocol_Ancillary_BackupDirective_NOOP MessageTransport_Protocol_Ancillary_BackupDirective_ActionType = 0
|
||||
MessageTransport_Protocol_Ancillary_BackupDirective_UPSERT MessageTransport_Protocol_Ancillary_BackupDirective_ActionType = 1
|
||||
MessageTransport_Protocol_Ancillary_BackupDirective_DELETE MessageTransport_Protocol_Ancillary_BackupDirective_ActionType = 2
|
||||
MessageTransport_Protocol_Ancillary_BackupDirective_UPSERT_AND_DELETE MessageTransport_Protocol_Ancillary_BackupDirective_ActionType = 3
|
||||
)
|
||||
|
||||
// Enum value maps for MessageTransport_Protocol_Ancillary_BackupDirective_ActionType.
|
||||
var (
|
||||
MessageTransport_Protocol_Ancillary_BackupDirective_ActionType_name = map[int32]string{
|
||||
0: "NOOP",
|
||||
1: "UPSERT",
|
||||
2: "DELETE",
|
||||
3: "UPSERT_AND_DELETE",
|
||||
}
|
||||
MessageTransport_Protocol_Ancillary_BackupDirective_ActionType_value = map[string]int32{
|
||||
"NOOP": 0,
|
||||
"UPSERT": 1,
|
||||
"DELETE": 2,
|
||||
"UPSERT_AND_DELETE": 3,
|
||||
}
|
||||
)
|
||||
|
||||
func (x MessageTransport_Protocol_Ancillary_BackupDirective_ActionType) Enum() *MessageTransport_Protocol_Ancillary_BackupDirective_ActionType {
|
||||
p := new(MessageTransport_Protocol_Ancillary_BackupDirective_ActionType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x MessageTransport_Protocol_Ancillary_BackupDirective_ActionType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (MessageTransport_Protocol_Ancillary_BackupDirective_ActionType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (MessageTransport_Protocol_Ancillary_BackupDirective_ActionType) Type() protoreflect.EnumType {
|
||||
return &file_waMsgTransport_WAMsgTransport_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x MessageTransport_Protocol_Ancillary_BackupDirective_ActionType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MessageTransport_Protocol_Ancillary_BackupDirective_ActionType.Descriptor instead.
|
||||
func (MessageTransport_Protocol_Ancillary_BackupDirective_ActionType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 0, 0, 0}
|
||||
}
|
||||
|
||||
type MessageTransport struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Payload *MessageTransport_Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
Protocol *MessageTransport_Protocol `protobuf:"bytes,2,opt,name=protocol,proto3" json:"protocol,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport) Reset() {
|
||||
*x = MessageTransport{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[0]
|
||||
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 MessageTransport.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MessageTransport) GetPayload() *MessageTransport_Payload {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport) GetProtocol() *MessageTransport_Protocol {
|
||||
if x != nil {
|
||||
return x.Protocol
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeviceListMetadata struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
SenderKeyHash []byte `protobuf:"bytes,1,opt,name=senderKeyHash,proto3" json:"senderKeyHash,omitempty"`
|
||||
SenderTimestamp uint64 `protobuf:"varint,2,opt,name=senderTimestamp,proto3" json:"senderTimestamp,omitempty"`
|
||||
RecipientKeyHash []byte `protobuf:"bytes,8,opt,name=recipientKeyHash,proto3" json:"recipientKeyHash,omitempty"`
|
||||
RecipientTimestamp uint64 `protobuf:"varint,9,opt,name=recipientTimestamp,proto3" json:"recipientTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DeviceListMetadata) Reset() {
|
||||
*x = DeviceListMetadata{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DeviceListMetadata) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeviceListMetadata) ProtoMessage() {}
|
||||
|
||||
func (x *DeviceListMetadata) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[1]
|
||||
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 DeviceListMetadata.ProtoReflect.Descriptor instead.
|
||||
func (*DeviceListMetadata) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *DeviceListMetadata) GetSenderKeyHash() []byte {
|
||||
if x != nil {
|
||||
return x.SenderKeyHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DeviceListMetadata) GetSenderTimestamp() uint64 {
|
||||
if x != nil {
|
||||
return x.SenderTimestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceListMetadata) GetRecipientKeyHash() []byte {
|
||||
if x != nil {
|
||||
return x.RecipientKeyHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *DeviceListMetadata) GetRecipientTimestamp() uint64 {
|
||||
if x != nil {
|
||||
return x.RecipientTimestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type MessageTransport_Payload struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ApplicationPayload *waCommon.SubProtocol `protobuf:"bytes,1,opt,name=applicationPayload,proto3" json:"applicationPayload,omitempty"`
|
||||
FutureProof waCommon.FutureProofBehavior `protobuf:"varint,3,opt,name=futureProof,proto3,enum=WACommon.FutureProofBehavior" json:"futureProof,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Payload) Reset() {
|
||||
*x = MessageTransport_Payload{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Payload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Payload) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Payload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[2]
|
||||
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 MessageTransport_Payload.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Payload) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Payload) GetApplicationPayload() *waCommon.SubProtocol {
|
||||
if x != nil {
|
||||
return x.ApplicationPayload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Payload) GetFutureProof() waCommon.FutureProofBehavior {
|
||||
if x != nil {
|
||||
return x.FutureProof
|
||||
}
|
||||
return waCommon.FutureProofBehavior(0)
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Integral *MessageTransport_Protocol_Integral `protobuf:"bytes,1,opt,name=integral,proto3" json:"integral,omitempty"`
|
||||
Ancillary *MessageTransport_Protocol_Ancillary `protobuf:"bytes,2,opt,name=ancillary,proto3" json:"ancillary,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol) Reset() {
|
||||
*x = MessageTransport_Protocol{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[3]
|
||||
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 MessageTransport_Protocol.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol) GetIntegral() *MessageTransport_Protocol_Integral {
|
||||
if x != nil {
|
||||
return x.Integral
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol) GetAncillary() *MessageTransport_Protocol_Ancillary {
|
||||
if x != nil {
|
||||
return x.Ancillary
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Ancillary struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Skdm *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage `protobuf:"bytes,2,opt,name=skdm,proto3" json:"skdm,omitempty"`
|
||||
DeviceListMetadata *DeviceListMetadata `protobuf:"bytes,3,opt,name=deviceListMetadata,proto3" json:"deviceListMetadata,omitempty"`
|
||||
Icdc *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices `protobuf:"bytes,4,opt,name=icdc,proto3" json:"icdc,omitempty"`
|
||||
BackupDirective *MessageTransport_Protocol_Ancillary_BackupDirective `protobuf:"bytes,5,opt,name=backupDirective,proto3" json:"backupDirective,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) Reset() {
|
||||
*x = MessageTransport_Protocol_Ancillary{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Ancillary) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[4]
|
||||
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 MessageTransport_Protocol_Ancillary.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Ancillary) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 0}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) GetSkdm() *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage {
|
||||
if x != nil {
|
||||
return x.Skdm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) GetDeviceListMetadata() *DeviceListMetadata {
|
||||
if x != nil {
|
||||
return x.DeviceListMetadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) GetIcdc() *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices {
|
||||
if x != nil {
|
||||
return x.Icdc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary) GetBackupDirective() *MessageTransport_Protocol_Ancillary_BackupDirective {
|
||||
if x != nil {
|
||||
return x.BackupDirective
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Integral struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Padding []byte `protobuf:"bytes,1,opt,name=padding,proto3" json:"padding,omitempty"`
|
||||
DSM *MessageTransport_Protocol_Integral_DeviceSentMessage `protobuf:"bytes,2,opt,name=DSM,proto3" json:"DSM,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral) Reset() {
|
||||
*x = MessageTransport_Protocol_Integral{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Integral) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[5]
|
||||
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 MessageTransport_Protocol_Integral.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Integral) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 1}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral) GetPadding() []byte {
|
||||
if x != nil {
|
||||
return x.Padding
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral) GetDSM() *MessageTransport_Protocol_Integral_DeviceSentMessage {
|
||||
if x != nil {
|
||||
return x.DSM
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Ancillary_BackupDirective struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
MessageID string `protobuf:"bytes,1,opt,name=messageID,proto3" json:"messageID,omitempty"`
|
||||
ActionType MessageTransport_Protocol_Ancillary_BackupDirective_ActionType `protobuf:"varint,2,opt,name=actionType,proto3,enum=WAMsgTransport.MessageTransport_Protocol_Ancillary_BackupDirective_ActionType" json:"actionType,omitempty"`
|
||||
SupplementalKey string `protobuf:"bytes,3,opt,name=supplementalKey,proto3" json:"supplementalKey,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_BackupDirective) Reset() {
|
||||
*x = MessageTransport_Protocol_Ancillary_BackupDirective{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_BackupDirective) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Ancillary_BackupDirective) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_BackupDirective) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[6]
|
||||
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 MessageTransport_Protocol_Ancillary_BackupDirective.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Ancillary_BackupDirective) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 0, 0}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_BackupDirective) GetMessageID() string {
|
||||
if x != nil {
|
||||
return x.MessageID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_BackupDirective) GetActionType() MessageTransport_Protocol_Ancillary_BackupDirective_ActionType {
|
||||
if x != nil {
|
||||
return x.ActionType
|
||||
}
|
||||
return MessageTransport_Protocol_Ancillary_BackupDirective_NOOP
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_BackupDirective) GetSupplementalKey() string {
|
||||
if x != nil {
|
||||
return x.SupplementalKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Ancillary_ICDCParticipantDevices struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
SenderIdentity *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription `protobuf:"bytes,1,opt,name=senderIdentity,proto3" json:"senderIdentity,omitempty"`
|
||||
RecipientIdentities []*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription `protobuf:"bytes,2,rep,name=recipientIdentities,proto3" json:"recipientIdentities,omitempty"`
|
||||
RecipientUserJIDs []string `protobuf:"bytes,3,rep,name=recipientUserJIDs,proto3" json:"recipientUserJIDs,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) Reset() {
|
||||
*x = MessageTransport_Protocol_Ancillary_ICDCParticipantDevices{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[7]
|
||||
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 MessageTransport_Protocol_Ancillary_ICDCParticipantDevices.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 0, 1}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) GetSenderIdentity() *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription {
|
||||
if x != nil {
|
||||
return x.SenderIdentity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) GetRecipientIdentities() []*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription {
|
||||
if x != nil {
|
||||
return x.RecipientIdentities
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices) GetRecipientUserJIDs() []string {
|
||||
if x != nil {
|
||||
return x.RecipientUserJIDs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
GroupID string `protobuf:"bytes,1,opt,name=groupID,proto3" json:"groupID,omitempty"`
|
||||
AxolotlSenderKeyDistributionMessage []byte `protobuf:"bytes,2,opt,name=axolotlSenderKeyDistributionMessage,proto3" json:"axolotlSenderKeyDistributionMessage,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) Reset() {
|
||||
*x = MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[8]
|
||||
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 MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 0, 2}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) GetGroupID() string {
|
||||
if x != nil {
|
||||
return x.GroupID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage) GetAxolotlSenderKeyDistributionMessage() []byte {
|
||||
if x != nil {
|
||||
return x.AxolotlSenderKeyDistributionMessage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Seq int32 `protobuf:"varint,1,opt,name=seq,proto3" json:"seq,omitempty"`
|
||||
SigningDevice []byte `protobuf:"bytes,2,opt,name=signingDevice,proto3" json:"signingDevice,omitempty"`
|
||||
UnknownDevices [][]byte `protobuf:"bytes,3,rep,name=unknownDevices,proto3" json:"unknownDevices,omitempty"`
|
||||
UnknownDeviceIDs []int32 `protobuf:"varint,4,rep,packed,name=unknownDeviceIDs,proto3" json:"unknownDeviceIDs,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) Reset() {
|
||||
*x = MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) ProtoMessage() {
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[9]
|
||||
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 MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 0, 1, 0}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) GetSeq() int32 {
|
||||
if x != nil {
|
||||
return x.Seq
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) GetSigningDevice() []byte {
|
||||
if x != nil {
|
||||
return x.SigningDevice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) GetUnknownDevices() [][]byte {
|
||||
if x != nil {
|
||||
return x.UnknownDevices
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription) GetUnknownDeviceIDs() []int32 {
|
||||
if x != nil {
|
||||
return x.UnknownDeviceIDs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageTransport_Protocol_Integral_DeviceSentMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
DestinationJID string `protobuf:"bytes,1,opt,name=destinationJID,proto3" json:"destinationJID,omitempty"`
|
||||
Phash string `protobuf:"bytes,2,opt,name=phash,proto3" json:"phash,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral_DeviceSentMessage) Reset() {
|
||||
*x = MessageTransport_Protocol_Integral_DeviceSentMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral_DeviceSentMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MessageTransport_Protocol_Integral_DeviceSentMessage) ProtoMessage() {}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral_DeviceSentMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMsgTransport_WAMsgTransport_proto_msgTypes[10]
|
||||
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 MessageTransport_Protocol_Integral_DeviceSentMessage.ProtoReflect.Descriptor instead.
|
||||
func (*MessageTransport_Protocol_Integral_DeviceSentMessage) Descriptor() ([]byte, []int) {
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP(), []int{0, 1, 1, 0}
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral_DeviceSentMessage) GetDestinationJID() string {
|
||||
if x != nil {
|
||||
return x.DestinationJID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *MessageTransport_Protocol_Integral_DeviceSentMessage) GetPhash() string {
|
||||
if x != nil {
|
||||
return x.Phash
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_waMsgTransport_WAMsgTransport_proto protoreflect.FileDescriptor
|
||||
|
||||
//go:embed WAMsgTransport.pb.raw
|
||||
var file_waMsgTransport_WAMsgTransport_proto_rawDesc []byte
|
||||
|
||||
var (
|
||||
file_waMsgTransport_WAMsgTransport_proto_rawDescOnce sync.Once
|
||||
file_waMsgTransport_WAMsgTransport_proto_rawDescData = file_waMsgTransport_WAMsgTransport_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_waMsgTransport_WAMsgTransport_proto_rawDescGZIP() []byte {
|
||||
file_waMsgTransport_WAMsgTransport_proto_rawDescOnce.Do(func() {
|
||||
file_waMsgTransport_WAMsgTransport_proto_rawDescData = protoimpl.X.CompressGZIP(file_waMsgTransport_WAMsgTransport_proto_rawDescData)
|
||||
})
|
||||
return file_waMsgTransport_WAMsgTransport_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_waMsgTransport_WAMsgTransport_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_waMsgTransport_WAMsgTransport_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_waMsgTransport_WAMsgTransport_proto_goTypes = []interface{}{
|
||||
(MessageTransport_Protocol_Ancillary_BackupDirective_ActionType)(0), // 0: WAMsgTransport.MessageTransport.Protocol.Ancillary.BackupDirective.ActionType
|
||||
(*MessageTransport)(nil), // 1: WAMsgTransport.MessageTransport
|
||||
(*DeviceListMetadata)(nil), // 2: WAMsgTransport.DeviceListMetadata
|
||||
(*MessageTransport_Payload)(nil), // 3: WAMsgTransport.MessageTransport.Payload
|
||||
(*MessageTransport_Protocol)(nil), // 4: WAMsgTransport.MessageTransport.Protocol
|
||||
(*MessageTransport_Protocol_Ancillary)(nil), // 5: WAMsgTransport.MessageTransport.Protocol.Ancillary
|
||||
(*MessageTransport_Protocol_Integral)(nil), // 6: WAMsgTransport.MessageTransport.Protocol.Integral
|
||||
(*MessageTransport_Protocol_Ancillary_BackupDirective)(nil), // 7: WAMsgTransport.MessageTransport.Protocol.Ancillary.BackupDirective
|
||||
(*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices)(nil), // 8: WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices
|
||||
(*MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage)(nil), // 9: WAMsgTransport.MessageTransport.Protocol.Ancillary.SenderKeyDistributionMessage
|
||||
(*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription)(nil), // 10: WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices.ICDCIdentityListDescription
|
||||
(*MessageTransport_Protocol_Integral_DeviceSentMessage)(nil), // 11: WAMsgTransport.MessageTransport.Protocol.Integral.DeviceSentMessage
|
||||
(*waCommon.SubProtocol)(nil), // 12: WACommon.SubProtocol
|
||||
(waCommon.FutureProofBehavior)(0), // 13: WACommon.FutureProofBehavior
|
||||
}
|
||||
var file_waMsgTransport_WAMsgTransport_proto_depIdxs = []int32{
|
||||
3, // 0: WAMsgTransport.MessageTransport.payload:type_name -> WAMsgTransport.MessageTransport.Payload
|
||||
4, // 1: WAMsgTransport.MessageTransport.protocol:type_name -> WAMsgTransport.MessageTransport.Protocol
|
||||
12, // 2: WAMsgTransport.MessageTransport.Payload.applicationPayload:type_name -> WACommon.SubProtocol
|
||||
13, // 3: WAMsgTransport.MessageTransport.Payload.futureProof:type_name -> WACommon.FutureProofBehavior
|
||||
6, // 4: WAMsgTransport.MessageTransport.Protocol.integral:type_name -> WAMsgTransport.MessageTransport.Protocol.Integral
|
||||
5, // 5: WAMsgTransport.MessageTransport.Protocol.ancillary:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary
|
||||
9, // 6: WAMsgTransport.MessageTransport.Protocol.Ancillary.skdm:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary.SenderKeyDistributionMessage
|
||||
2, // 7: WAMsgTransport.MessageTransport.Protocol.Ancillary.deviceListMetadata:type_name -> WAMsgTransport.DeviceListMetadata
|
||||
8, // 8: WAMsgTransport.MessageTransport.Protocol.Ancillary.icdc:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices
|
||||
7, // 9: WAMsgTransport.MessageTransport.Protocol.Ancillary.backupDirective:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary.BackupDirective
|
||||
11, // 10: WAMsgTransport.MessageTransport.Protocol.Integral.DSM:type_name -> WAMsgTransport.MessageTransport.Protocol.Integral.DeviceSentMessage
|
||||
0, // 11: WAMsgTransport.MessageTransport.Protocol.Ancillary.BackupDirective.actionType:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary.BackupDirective.ActionType
|
||||
10, // 12: WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices.senderIdentity:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices.ICDCIdentityListDescription
|
||||
10, // 13: WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices.recipientIdentities:type_name -> WAMsgTransport.MessageTransport.Protocol.Ancillary.ICDCParticipantDevices.ICDCIdentityListDescription
|
||||
14, // [14:14] is the sub-list for method output_type
|
||||
14, // [14:14] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_waMsgTransport_WAMsgTransport_proto_init() }
|
||||
func file_waMsgTransport_WAMsgTransport_proto_init() {
|
||||
if File_waMsgTransport_WAMsgTransport_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeviceListMetadata); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Payload); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Ancillary); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Integral); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Ancillary_BackupDirective); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Ancillary_ICDCParticipantDevices_ICDCIdentityListDescription); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMsgTransport_WAMsgTransport_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MessageTransport_Protocol_Integral_DeviceSentMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_waMsgTransport_WAMsgTransport_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 11,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_waMsgTransport_WAMsgTransport_proto_goTypes,
|
||||
DependencyIndexes: file_waMsgTransport_WAMsgTransport_proto_depIdxs,
|
||||
EnumInfos: file_waMsgTransport_WAMsgTransport_proto_enumTypes,
|
||||
MessageInfos: file_waMsgTransport_WAMsgTransport_proto_msgTypes,
|
||||
}.Build()
|
||||
File_waMsgTransport_WAMsgTransport_proto = out.File
|
||||
file_waMsgTransport_WAMsgTransport_proto_rawDesc = nil
|
||||
file_waMsgTransport_WAMsgTransport_proto_goTypes = nil
|
||||
file_waMsgTransport_WAMsgTransport_proto_depIdxs = nil
|
||||
}
|
||||
BIN
Binary file not shown.
+75
@@ -0,0 +1,75 @@
|
||||
syntax = "proto3";
|
||||
package WAMsgTransport;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waMsgTransport";
|
||||
|
||||
import "waCommon/WACommon.proto";
|
||||
|
||||
message MessageTransport {
|
||||
message Payload {
|
||||
WACommon.SubProtocol applicationPayload = 1;
|
||||
WACommon.FutureProofBehavior futureProof = 3;
|
||||
}
|
||||
|
||||
message Protocol {
|
||||
message Ancillary {
|
||||
message BackupDirective {
|
||||
enum ActionType {
|
||||
NOOP = 0;
|
||||
UPSERT = 1;
|
||||
DELETE = 2;
|
||||
UPSERT_AND_DELETE = 3;
|
||||
}
|
||||
|
||||
string messageID = 1;
|
||||
ActionType actionType = 2;
|
||||
string supplementalKey = 3;
|
||||
}
|
||||
|
||||
message ICDCParticipantDevices {
|
||||
message ICDCIdentityListDescription {
|
||||
int32 seq = 1;
|
||||
bytes signingDevice = 2;
|
||||
repeated bytes unknownDevices = 3;
|
||||
repeated int32 unknownDeviceIDs = 4;
|
||||
}
|
||||
|
||||
ICDCIdentityListDescription senderIdentity = 1;
|
||||
repeated ICDCIdentityListDescription recipientIdentities = 2;
|
||||
repeated string recipientUserJIDs = 3;
|
||||
}
|
||||
|
||||
message SenderKeyDistributionMessage {
|
||||
string groupID = 1;
|
||||
bytes axolotlSenderKeyDistributionMessage = 2;
|
||||
}
|
||||
|
||||
SenderKeyDistributionMessage skdm = 2;
|
||||
DeviceListMetadata deviceListMetadata = 3;
|
||||
ICDCParticipantDevices icdc = 4;
|
||||
BackupDirective backupDirective = 5;
|
||||
}
|
||||
|
||||
message Integral {
|
||||
message DeviceSentMessage {
|
||||
string destinationJID = 1;
|
||||
string phash = 2;
|
||||
}
|
||||
|
||||
bytes padding = 1;
|
||||
DeviceSentMessage DSM = 2;
|
||||
}
|
||||
|
||||
Integral integral = 1;
|
||||
Ancillary ancillary = 2;
|
||||
}
|
||||
|
||||
Payload payload = 1;
|
||||
Protocol protocol = 2;
|
||||
}
|
||||
|
||||
message DeviceListMetadata {
|
||||
bytes senderKeyHash = 1;
|
||||
uint64 senderTimestamp = 2;
|
||||
bytes recipientKeyHash = 8;
|
||||
uint64 recipientTimestamp = 9;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package waMsgTransport
|
||||
|
||||
import (
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/armadilloutil"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgApplication"
|
||||
)
|
||||
|
||||
const (
|
||||
MessageApplicationVersion = 2
|
||||
)
|
||||
|
||||
func (msg *MessageTransport_Payload) Decode() (*waMsgApplication.MessageApplication, error) {
|
||||
return armadilloutil.Unmarshal(&waMsgApplication.MessageApplication{}, msg.GetApplicationPayload(), MessageApplicationVersion)
|
||||
}
|
||||
|
||||
func (msg *MessageTransport_Payload) Set(payload *waMsgApplication.MessageApplication) (err error) {
|
||||
msg.ApplicationPayload, err = armadilloutil.Marshal(payload, MessageApplicationVersion)
|
||||
return
|
||||
}
|
||||
+859
@@ -0,0 +1,859 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v3.21.12
|
||||
// source: waMultiDevice/WAMultiDevice.proto
|
||||
|
||||
package waMultiDevice
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type MultiDevice struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Payload *MultiDevice_Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||
Metadata *MultiDevice_Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice) Reset() {
|
||||
*x = MultiDevice{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[0]
|
||||
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 MultiDevice.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MultiDevice) GetPayload() *MultiDevice_Payload {
|
||||
if x != nil {
|
||||
return x.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice) GetMetadata() *MultiDevice_Metadata {
|
||||
if x != nil {
|
||||
return x.Metadata
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MultiDevice_Metadata struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Metadata) Reset() {
|
||||
*x = MultiDevice_Metadata{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Metadata) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_Metadata) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_Metadata) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[1]
|
||||
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 MultiDevice_Metadata.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_Metadata) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type MultiDevice_Payload struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Payload:
|
||||
//
|
||||
// *MultiDevice_Payload_ApplicationData
|
||||
// *MultiDevice_Payload_Signal
|
||||
Payload isMultiDevice_Payload_Payload `protobuf_oneof:"payload"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Payload) Reset() {
|
||||
*x = MultiDevice_Payload{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Payload) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_Payload) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_Payload) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[2]
|
||||
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 MultiDevice_Payload.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_Payload) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 1}
|
||||
}
|
||||
|
||||
func (m *MultiDevice_Payload) GetPayload() isMultiDevice_Payload_Payload {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Payload) GetApplicationData() *MultiDevice_ApplicationData {
|
||||
if x, ok := x.GetPayload().(*MultiDevice_Payload_ApplicationData); ok {
|
||||
return x.ApplicationData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Payload) GetSignal() *MultiDevice_Signal {
|
||||
if x, ok := x.GetPayload().(*MultiDevice_Payload_Signal); ok {
|
||||
return x.Signal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isMultiDevice_Payload_Payload interface {
|
||||
isMultiDevice_Payload_Payload()
|
||||
}
|
||||
|
||||
type MultiDevice_Payload_ApplicationData struct {
|
||||
ApplicationData *MultiDevice_ApplicationData `protobuf:"bytes,1,opt,name=applicationData,proto3,oneof"`
|
||||
}
|
||||
|
||||
type MultiDevice_Payload_Signal struct {
|
||||
Signal *MultiDevice_Signal `protobuf:"bytes,2,opt,name=signal,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*MultiDevice_Payload_ApplicationData) isMultiDevice_Payload_Payload() {}
|
||||
|
||||
func (*MultiDevice_Payload_Signal) isMultiDevice_Payload_Payload() {}
|
||||
|
||||
type MultiDevice_ApplicationData struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to ApplicationData:
|
||||
//
|
||||
// *MultiDevice_ApplicationData_AppStateSyncKeyShare
|
||||
// *MultiDevice_ApplicationData_AppStateSyncKeyRequest
|
||||
ApplicationData isMultiDevice_ApplicationData_ApplicationData `protobuf_oneof:"applicationData"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData) Reset() {
|
||||
*x = MultiDevice_ApplicationData{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_ApplicationData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[3]
|
||||
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 MultiDevice_ApplicationData.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2}
|
||||
}
|
||||
|
||||
func (m *MultiDevice_ApplicationData) GetApplicationData() isMultiDevice_ApplicationData_ApplicationData {
|
||||
if m != nil {
|
||||
return m.ApplicationData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData) GetAppStateSyncKeyShare() *MultiDevice_ApplicationData_AppStateSyncKeyShareMessage {
|
||||
if x, ok := x.GetApplicationData().(*MultiDevice_ApplicationData_AppStateSyncKeyShare); ok {
|
||||
return x.AppStateSyncKeyShare
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData) GetAppStateSyncKeyRequest() *MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage {
|
||||
if x, ok := x.GetApplicationData().(*MultiDevice_ApplicationData_AppStateSyncKeyRequest); ok {
|
||||
return x.AppStateSyncKeyRequest
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isMultiDevice_ApplicationData_ApplicationData interface {
|
||||
isMultiDevice_ApplicationData_ApplicationData()
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKeyShare struct {
|
||||
AppStateSyncKeyShare *MultiDevice_ApplicationData_AppStateSyncKeyShareMessage `protobuf:"bytes,1,opt,name=appStateSyncKeyShare,proto3,oneof"`
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKeyRequest struct {
|
||||
AppStateSyncKeyRequest *MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage `protobuf:"bytes,2,opt,name=appStateSyncKeyRequest,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyShare) isMultiDevice_ApplicationData_ApplicationData() {
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyRequest) isMultiDevice_ApplicationData_ApplicationData() {
|
||||
}
|
||||
|
||||
type MultiDevice_Signal struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Signal) Reset() {
|
||||
*x = MultiDevice_Signal{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_Signal) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_Signal) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_Signal) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[4]
|
||||
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 MultiDevice_Signal.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_Signal) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 3}
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
KeyIDs []*MultiDevice_ApplicationData_AppStateSyncKeyId `protobuf:"bytes,1,rep,name=keyIDs,proto3" json:"keyIDs,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage) Reset() {
|
||||
*x = MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[5]
|
||||
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 MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2, 0}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage) GetKeyIDs() []*MultiDevice_ApplicationData_AppStateSyncKeyId {
|
||||
if x != nil {
|
||||
return x.KeyIDs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKeyShareMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Keys []*MultiDevice_ApplicationData_AppStateSyncKey `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyShareMessage) Reset() {
|
||||
*x = MultiDevice_ApplicationData_AppStateSyncKeyShareMessage{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyShareMessage) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyShareMessage) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyShareMessage) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[6]
|
||||
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 MultiDevice_ApplicationData_AppStateSyncKeyShareMessage.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyShareMessage) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2, 1}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyShareMessage) GetKeys() []*MultiDevice_ApplicationData_AppStateSyncKey {
|
||||
if x != nil {
|
||||
return x.Keys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKey struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
KeyID *MultiDevice_ApplicationData_AppStateSyncKeyId `protobuf:"bytes,1,opt,name=keyID,proto3" json:"keyID,omitempty"`
|
||||
KeyData *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData `protobuf:"bytes,2,opt,name=keyData,proto3" json:"keyData,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey) Reset() {
|
||||
*x = MultiDevice_ApplicationData_AppStateSyncKey{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKey) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[7]
|
||||
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 MultiDevice_ApplicationData_AppStateSyncKey.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKey) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2, 2}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey) GetKeyID() *MultiDevice_ApplicationData_AppStateSyncKeyId {
|
||||
if x != nil {
|
||||
return x.KeyID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey) GetKeyData() *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData {
|
||||
if x != nil {
|
||||
return x.KeyData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKeyId struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
KeyID []byte `protobuf:"bytes,1,opt,name=keyID,proto3" json:"keyID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyId) Reset() {
|
||||
*x = MultiDevice_ApplicationData_AppStateSyncKeyId{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyId) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyId) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyId) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[8]
|
||||
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 MultiDevice_ApplicationData_AppStateSyncKeyId.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKeyId) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2, 3}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKeyId) GetKeyID() []byte {
|
||||
if x != nil {
|
||||
return x.KeyID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
KeyData []byte `protobuf:"bytes,1,opt,name=keyData,proto3" json:"keyData,omitempty"`
|
||||
Fingerprint *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint `protobuf:"bytes,2,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"`
|
||||
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) Reset() {
|
||||
*x = MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) ProtoMessage() {}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[9]
|
||||
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 MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2, 2, 0}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) GetKeyData() []byte {
|
||||
if x != nil {
|
||||
return x.KeyData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) GetFingerprint() *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint {
|
||||
if x != nil {
|
||||
return x.Fingerprint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData) GetTimestamp() int64 {
|
||||
if x != nil {
|
||||
return x.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RawID uint32 `protobuf:"varint,1,opt,name=rawID,proto3" json:"rawID,omitempty"`
|
||||
CurrentIndex uint32 `protobuf:"varint,2,opt,name=currentIndex,proto3" json:"currentIndex,omitempty"`
|
||||
DeviceIndexes []uint32 `protobuf:"varint,3,rep,packed,name=deviceIndexes,proto3" json:"deviceIndexes,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) Reset() {
|
||||
*x = MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) ProtoMessage() {
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_waMultiDevice_WAMultiDevice_proto_msgTypes[10]
|
||||
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 MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint.ProtoReflect.Descriptor instead.
|
||||
func (*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) Descriptor() ([]byte, []int) {
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP(), []int{0, 2, 2, 0, 0}
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) GetRawID() uint32 {
|
||||
if x != nil {
|
||||
return x.RawID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) GetCurrentIndex() uint32 {
|
||||
if x != nil {
|
||||
return x.CurrentIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint) GetDeviceIndexes() []uint32 {
|
||||
if x != nil {
|
||||
return x.DeviceIndexes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_waMultiDevice_WAMultiDevice_proto protoreflect.FileDescriptor
|
||||
|
||||
//go:embed WAMultiDevice.pb.raw
|
||||
var file_waMultiDevice_WAMultiDevice_proto_rawDesc []byte
|
||||
|
||||
var (
|
||||
file_waMultiDevice_WAMultiDevice_proto_rawDescOnce sync.Once
|
||||
file_waMultiDevice_WAMultiDevice_proto_rawDescData = file_waMultiDevice_WAMultiDevice_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_waMultiDevice_WAMultiDevice_proto_rawDescGZIP() []byte {
|
||||
file_waMultiDevice_WAMultiDevice_proto_rawDescOnce.Do(func() {
|
||||
file_waMultiDevice_WAMultiDevice_proto_rawDescData = protoimpl.X.CompressGZIP(file_waMultiDevice_WAMultiDevice_proto_rawDescData)
|
||||
})
|
||||
return file_waMultiDevice_WAMultiDevice_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_waMultiDevice_WAMultiDevice_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||
var file_waMultiDevice_WAMultiDevice_proto_goTypes = []interface{}{
|
||||
(*MultiDevice)(nil), // 0: WAMultiDevice.MultiDevice
|
||||
(*MultiDevice_Metadata)(nil), // 1: WAMultiDevice.MultiDevice.Metadata
|
||||
(*MultiDevice_Payload)(nil), // 2: WAMultiDevice.MultiDevice.Payload
|
||||
(*MultiDevice_ApplicationData)(nil), // 3: WAMultiDevice.MultiDevice.ApplicationData
|
||||
(*MultiDevice_Signal)(nil), // 4: WAMultiDevice.MultiDevice.Signal
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage)(nil), // 5: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyRequestMessage
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKeyShareMessage)(nil), // 6: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyShareMessage
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKey)(nil), // 7: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKeyId)(nil), // 8: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyId
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData)(nil), // 9: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.AppStateSyncKeyData
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint)(nil), // 10: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.AppStateSyncKeyData.AppStateSyncKeyFingerprint
|
||||
}
|
||||
var file_waMultiDevice_WAMultiDevice_proto_depIdxs = []int32{
|
||||
2, // 0: WAMultiDevice.MultiDevice.payload:type_name -> WAMultiDevice.MultiDevice.Payload
|
||||
1, // 1: WAMultiDevice.MultiDevice.metadata:type_name -> WAMultiDevice.MultiDevice.Metadata
|
||||
3, // 2: WAMultiDevice.MultiDevice.Payload.applicationData:type_name -> WAMultiDevice.MultiDevice.ApplicationData
|
||||
4, // 3: WAMultiDevice.MultiDevice.Payload.signal:type_name -> WAMultiDevice.MultiDevice.Signal
|
||||
6, // 4: WAMultiDevice.MultiDevice.ApplicationData.appStateSyncKeyShare:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyShareMessage
|
||||
5, // 5: WAMultiDevice.MultiDevice.ApplicationData.appStateSyncKeyRequest:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyRequestMessage
|
||||
8, // 6: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyRequestMessage.keyIDs:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyId
|
||||
7, // 7: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyShareMessage.keys:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey
|
||||
8, // 8: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.keyID:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKeyId
|
||||
9, // 9: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.keyData:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.AppStateSyncKeyData
|
||||
10, // 10: WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.AppStateSyncKeyData.fingerprint:type_name -> WAMultiDevice.MultiDevice.ApplicationData.AppStateSyncKey.AppStateSyncKeyData.AppStateSyncKeyFingerprint
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_waMultiDevice_WAMultiDevice_proto_init() }
|
||||
func file_waMultiDevice_WAMultiDevice_proto_init() {
|
||||
if File_waMultiDevice_WAMultiDevice_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_Metadata); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_Payload); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_Signal); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData_AppStateSyncKeyRequestMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData_AppStateSyncKeyShareMessage); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData_AppStateSyncKey); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData_AppStateSyncKeyId); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MultiDevice_ApplicationData_AppStateSyncKey_AppStateSyncKeyData_AppStateSyncKeyFingerprint); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[2].OneofWrappers = []interface{}{
|
||||
(*MultiDevice_Payload_ApplicationData)(nil),
|
||||
(*MultiDevice_Payload_Signal)(nil),
|
||||
}
|
||||
file_waMultiDevice_WAMultiDevice_proto_msgTypes[3].OneofWrappers = []interface{}{
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKeyShare)(nil),
|
||||
(*MultiDevice_ApplicationData_AppStateSyncKeyRequest)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_waMultiDevice_WAMultiDevice_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 11,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_waMultiDevice_WAMultiDevice_proto_goTypes,
|
||||
DependencyIndexes: file_waMultiDevice_WAMultiDevice_proto_depIdxs,
|
||||
MessageInfos: file_waMultiDevice_WAMultiDevice_proto_msgTypes,
|
||||
}.Build()
|
||||
File_waMultiDevice_WAMultiDevice_proto = out.File
|
||||
file_waMultiDevice_WAMultiDevice_proto_rawDesc = nil
|
||||
file_waMultiDevice_WAMultiDevice_proto_goTypes = nil
|
||||
file_waMultiDevice_WAMultiDevice_proto_depIdxs = nil
|
||||
}
|
||||
BIN
Binary file not shown.
+57
@@ -0,0 +1,57 @@
|
||||
syntax = "proto3";
|
||||
package WAMultiDevice;
|
||||
option go_package = "go.mau.fi/whatsmeow/binary/armadillo/waMultiDevice";
|
||||
|
||||
message MultiDevice {
|
||||
message Metadata {
|
||||
}
|
||||
|
||||
message Payload {
|
||||
oneof payload {
|
||||
ApplicationData applicationData = 1;
|
||||
Signal signal = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ApplicationData {
|
||||
message AppStateSyncKeyRequestMessage {
|
||||
repeated AppStateSyncKeyId keyIDs = 1;
|
||||
}
|
||||
|
||||
message AppStateSyncKeyShareMessage {
|
||||
repeated AppStateSyncKey keys = 1;
|
||||
}
|
||||
|
||||
message AppStateSyncKey {
|
||||
message AppStateSyncKeyData {
|
||||
message AppStateSyncKeyFingerprint {
|
||||
uint32 rawID = 1;
|
||||
uint32 currentIndex = 2;
|
||||
repeated uint32 deviceIndexes = 3 [packed=true];
|
||||
}
|
||||
|
||||
bytes keyData = 1;
|
||||
AppStateSyncKeyFingerprint fingerprint = 2;
|
||||
int64 timestamp = 3;
|
||||
}
|
||||
|
||||
AppStateSyncKeyId keyID = 1;
|
||||
AppStateSyncKeyData keyData = 2;
|
||||
}
|
||||
|
||||
message AppStateSyncKeyId {
|
||||
bytes keyID = 1;
|
||||
}
|
||||
|
||||
oneof applicationData {
|
||||
AppStateSyncKeyShareMessage appStateSyncKeyShare = 1;
|
||||
AppStateSyncKeyRequestMessage appStateSyncKeyRequest = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message Signal {
|
||||
}
|
||||
|
||||
Payload payload = 1;
|
||||
Metadata metadata = 2;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package waMultiDevice
|
||||
|
||||
func (*MultiDevice) IsMessageApplicationSub() {}
|
||||
+20
@@ -123,6 +123,16 @@ func (au *AttrUtility) GetUnixTime(key string, require bool) (time.Time, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (au *AttrUtility) GetUnixMilli(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.UnixMilli(intVal), true
|
||||
}
|
||||
}
|
||||
|
||||
// OptionalString returns the string under the given key.
|
||||
func (au *AttrUtility) OptionalString(key string) string {
|
||||
strVal, _ := au.GetString(key, false)
|
||||
@@ -176,6 +186,16 @@ func (au *AttrUtility) UnixTime(key string) time.Time {
|
||||
return val
|
||||
}
|
||||
|
||||
func (au *AttrUtility) OptionalUnixMilli(key string) time.Time {
|
||||
val, _ := au.GetUnixMilli(key, false)
|
||||
return val
|
||||
}
|
||||
|
||||
func (au *AttrUtility) UnixMilli(key string) time.Time {
|
||||
val, _ := au.GetUnixMilli(key, true)
|
||||
return val
|
||||
}
|
||||
|
||||
// OK returns true if there are no errors.
|
||||
func (au *AttrUtility) OK() bool {
|
||||
return len(au.Errors) == 0
|
||||
|
||||
+53
@@ -204,6 +204,10 @@ func (r *binaryDecoder) read(string bool) (interface{}, error) {
|
||||
}
|
||||
|
||||
return token.GetDoubleToken(tag-token.Dictionary0, i)
|
||||
case token.FBJID:
|
||||
return r.readFBJID()
|
||||
case token.InteropJID:
|
||||
return r.readInteropJID()
|
||||
case token.JIDPair:
|
||||
return r.readJIDPair()
|
||||
case token.ADJID:
|
||||
@@ -234,6 +238,55 @@ func (r *binaryDecoder) readJIDPair() (interface{}, error) {
|
||||
return types.NewJID(user.(string), server.(string)), nil
|
||||
}
|
||||
|
||||
func (r *binaryDecoder) readInteropJID() (interface{}, error) {
|
||||
user, err := r.read(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
device, err := r.readInt16(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
integrator, err := r.readInt16(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server, err := r.read(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if server != types.InteropServer {
|
||||
return nil, fmt.Errorf("%w: expected %q, got %q", ErrInvalidJIDType, types.InteropServer, server)
|
||||
}
|
||||
return types.JID{
|
||||
User: user.(string),
|
||||
Device: uint16(device),
|
||||
Integrator: uint16(integrator),
|
||||
Server: types.InteropServer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *binaryDecoder) readFBJID() (interface{}, error) {
|
||||
user, err := r.read(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
device, err := r.readInt16(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server, err := r.read(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if server != types.MessengerServer {
|
||||
return nil, fmt.Errorf("%w: expected %q, got %q", ErrInvalidJIDType, types.MessengerServer, server)
|
||||
}
|
||||
return types.JID{
|
||||
User: user.(string),
|
||||
Device: uint16(device),
|
||||
Server: server.(string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *binaryDecoder) readADJID() (interface{}, error) {
|
||||
agent, err := r.readByte()
|
||||
if err != nil {
|
||||
|
||||
+14
-3
@@ -159,11 +159,22 @@ func (w *binaryEncoder) writeStringRaw(value string) {
|
||||
}
|
||||
|
||||
func (w *binaryEncoder) writeJID(jid types.JID) {
|
||||
if jid.AD {
|
||||
if (jid.Server == types.DefaultUserServer && jid.Device > 0) || jid.Server == types.HiddenUserServer || jid.Server == types.HostedServer {
|
||||
w.pushByte(token.ADJID)
|
||||
w.pushByte(jid.Agent)
|
||||
w.pushByte(jid.Device)
|
||||
w.pushByte(jid.ActualAgent())
|
||||
w.pushByte(uint8(jid.Device))
|
||||
w.writeString(jid.User)
|
||||
} else if jid.Server == types.MessengerServer {
|
||||
w.pushByte(token.FBJID)
|
||||
w.write(jid.User)
|
||||
w.pushInt16(int(jid.Device))
|
||||
w.write(jid.Server)
|
||||
} else if jid.Server == types.InteropServer {
|
||||
w.pushByte(token.InteropJID)
|
||||
w.write(jid.User)
|
||||
w.pushInt16(int(jid.Device))
|
||||
w.pushInt16(int(jid.Integrator))
|
||||
w.write(jid.Server)
|
||||
} else {
|
||||
w.pushByte(token.JIDPair)
|
||||
if len(jid.User) == 0 {
|
||||
|
||||
+51
-1
@@ -8,11 +8,14 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
// Attrs is a type alias for the attributes of an XML element (Node).
|
||||
type Attrs = map[string]interface{}
|
||||
type Attrs = map[string]any
|
||||
|
||||
// Node represents an XML element.
|
||||
type Node struct {
|
||||
@@ -21,6 +24,53 @@ type Node struct {
|
||||
Content interface{} // The content inside the element. Can be nil, a list of Nodes, or a byte array.
|
||||
}
|
||||
|
||||
type marshalableNode struct {
|
||||
Tag string
|
||||
Attrs Attrs
|
||||
Content json.RawMessage
|
||||
}
|
||||
|
||||
func (n *Node) UnmarshalJSON(data []byte) error {
|
||||
var mn marshalableNode
|
||||
err := json.Unmarshal(data, &mn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, val := range mn.Attrs {
|
||||
switch typedVal := val.(type) {
|
||||
case string:
|
||||
parsed, err := types.ParseJID(typedVal)
|
||||
if err == nil && parsed.Server == types.DefaultUserServer || parsed.Server == types.NewsletterServer || parsed.Server == types.GroupServer || parsed.Server == types.BroadcastServer {
|
||||
mn.Attrs[key] = parsed
|
||||
}
|
||||
case float64:
|
||||
mn.Attrs[key] = int64(typedVal)
|
||||
}
|
||||
}
|
||||
n.Tag = mn.Tag
|
||||
n.Attrs = mn.Attrs
|
||||
if len(mn.Content) > 0 {
|
||||
if mn.Content[0] == '[' {
|
||||
var nodes []Node
|
||||
err = json.Unmarshal(mn.Content, &nodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.Content = nodes
|
||||
} else if mn.Content[0] == '"' {
|
||||
var binaryContent []byte
|
||||
err = json.Unmarshal(mn.Content, &binaryContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.Content = binaryContent
|
||||
} else {
|
||||
return fmt.Errorf("node content must be an array of nodes or a base64 string")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChildren returns the Content of the node as a list of nodes. If the content is not a list of nodes, this returns nil.
|
||||
func (n *Node) GetChildren() []Node {
|
||||
if n.Content == nil {
|
||||
|
||||
+10054
-4114
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
+729
-200
File diff suppressed because it is too large
Load Diff
+8
-6
File diff suppressed because one or more lines are too long
Vendored
+18
@@ -59,6 +59,24 @@ func (cli *Client) handleCallEvent(node *waBinary.Node) {
|
||||
},
|
||||
Data: &child,
|
||||
})
|
||||
case "preaccept":
|
||||
cli.dispatchEvent(&events.CallPreAccept{
|
||||
BasicCallMeta: basicMeta,
|
||||
CallRemoteMeta: types.CallRemoteMeta{
|
||||
RemotePlatform: ag.String("platform"),
|
||||
RemoteVersion: ag.String("version"),
|
||||
},
|
||||
Data: &child,
|
||||
})
|
||||
case "transport":
|
||||
cli.dispatchEvent(&events.CallTransport{
|
||||
BasicCallMeta: basicMeta,
|
||||
CallRemoteMeta: types.CallRemoteMeta{
|
||||
RemotePlatform: ag.String("platform"),
|
||||
RemoteVersion: ag.String("version"),
|
||||
},
|
||||
Data: &child,
|
||||
})
|
||||
case "terminate":
|
||||
cli.dispatchEvent(&events.CallTerminate{
|
||||
BasicCallMeta: basicMeta,
|
||||
|
||||
Vendored
+154
-26
@@ -19,6 +19,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"go.mau.fi/util/random"
|
||||
"golang.org/x/net/proxy"
|
||||
|
||||
"go.mau.fi/whatsmeow/appstate"
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
@@ -28,7 +32,6 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// EventHandler is a function that can handle events from WhatsApp.
|
||||
@@ -42,6 +45,11 @@ type wrappedEventHandler struct {
|
||||
id uint32
|
||||
}
|
||||
|
||||
type deviceCache struct {
|
||||
devices []types.JID
|
||||
dhash string
|
||||
}
|
||||
|
||||
// Client contains everything necessary to connect to and interact with the WhatsApp web API.
|
||||
type Client struct {
|
||||
Store *store.Device
|
||||
@@ -53,13 +61,16 @@ type Client struct {
|
||||
socketLock sync.RWMutex
|
||||
socketWait chan struct{}
|
||||
|
||||
isLoggedIn uint32
|
||||
expectedDisconnectVal uint32
|
||||
isLoggedIn atomic.Bool
|
||||
expectedDisconnect atomic.Bool
|
||||
EnableAutoReconnect bool
|
||||
LastSuccessfulConnect time.Time
|
||||
AutoReconnectErrors int
|
||||
// AutoReconnectHook is called when auto-reconnection fails. If the function returns false,
|
||||
// the client will not attempt to reconnect. The number of retries can be read from AutoReconnectErrors.
|
||||
AutoReconnectHook func(error) bool
|
||||
|
||||
sendActiveReceipts uint32
|
||||
sendActiveReceipts atomic.Uint32
|
||||
|
||||
// EmitAppStateEventsOnFullSync can be set to true if you want to get app state events emitted
|
||||
// even when re-syncing the whole state.
|
||||
@@ -73,7 +84,7 @@ type Client struct {
|
||||
appStateSyncLock sync.Mutex
|
||||
|
||||
historySyncNotifications chan *waProto.HistorySyncNotification
|
||||
historySyncHandlerStarted uint32
|
||||
historySyncHandlerStarted atomic.Bool
|
||||
|
||||
uploadPreKeysLock sync.Mutex
|
||||
lastPreKeyUpload time.Time
|
||||
@@ -92,6 +103,9 @@ type Client struct {
|
||||
messageRetries map[string]int
|
||||
messageRetriesLock sync.Mutex
|
||||
|
||||
incomingRetryRequestCounter map[incomingRetryKey]int
|
||||
incomingRetryRequestCounterLock sync.Mutex
|
||||
|
||||
appStateKeyRequests map[string]time.Time
|
||||
appStateKeyRequestsLock sync.RWMutex
|
||||
|
||||
@@ -101,10 +115,10 @@ type Client struct {
|
||||
|
||||
groupParticipantsCache map[types.JID][]types.JID
|
||||
groupParticipantsCacheLock sync.Mutex
|
||||
userDevicesCache map[types.JID][]types.JID
|
||||
userDevicesCache map[types.JID]deviceCache
|
||||
userDevicesCacheLock sync.Mutex
|
||||
|
||||
recentMessagesMap map[recentMessageKey]*waProto.Message
|
||||
recentMessagesMap map[recentMessageKey]RecentMessage
|
||||
recentMessagesList [recentMessagesSize]recentMessageKey
|
||||
recentMessagesPtr int
|
||||
recentMessagesLock sync.RWMutex
|
||||
@@ -122,6 +136,10 @@ type Client struct {
|
||||
// the client will disconnect.
|
||||
PrePairCallback func(jid types.JID, platform, businessName string) bool
|
||||
|
||||
// GetClientPayload is called to get the client payload for connecting to the server.
|
||||
// This should NOT be used for WhatsApp (to change the OS name, update fields in store.BaseClientPayload directly).
|
||||
GetClientPayload func() *waProto.ClientPayload
|
||||
|
||||
// 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.
|
||||
@@ -137,10 +155,25 @@ type Client struct {
|
||||
phoneLinkingCache *phoneLinkingCache
|
||||
|
||||
uniqueID string
|
||||
idCounter uint32
|
||||
idCounter atomic.Uint64
|
||||
|
||||
proxy socket.Proxy
|
||||
http *http.Client
|
||||
proxy Proxy
|
||||
socksProxy proxy.Dialer
|
||||
proxyOnlyLogin bool
|
||||
http *http.Client
|
||||
|
||||
// This field changes the client to act like a Messenger client instead of a WhatsApp one.
|
||||
//
|
||||
// Note that you cannot use a Messenger account just by setting this field, you must use a
|
||||
// separate library for all the non-e2ee-related stuff like logging in.
|
||||
// The library is currently embedded in mautrix-meta (https://github.com/mautrix/meta), but may be separated later.
|
||||
MessengerConfig *MessengerConfig
|
||||
RefreshCAT func() error
|
||||
}
|
||||
|
||||
type MessengerConfig struct {
|
||||
UserAgent string
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// Size of buffer for the channel that all incoming XML nodes go through.
|
||||
@@ -167,7 +200,7 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
if log == nil {
|
||||
log = waLog.Noop
|
||||
}
|
||||
uniqueIDPrefix := randbytes.Make(2)
|
||||
uniqueIDPrefix := random.Bytes(2)
|
||||
cli := &Client{
|
||||
http: &http.Client{
|
||||
Transport: (http.DefaultTransport.(*http.Transport)).Clone(),
|
||||
@@ -185,12 +218,14 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
appStateProc: appstate.NewProcessor(deviceStore, log.Sub("AppState")),
|
||||
socketWait: make(chan struct{}),
|
||||
|
||||
incomingRetryRequestCounter: make(map[incomingRetryKey]int),
|
||||
|
||||
historySyncNotifications: make(chan *waProto.HistorySyncNotification, 32),
|
||||
|
||||
groupParticipantsCache: make(map[types.JID][]types.JID),
|
||||
userDevicesCache: make(map[types.JID][]types.JID),
|
||||
userDevicesCache: make(map[types.JID]deviceCache),
|
||||
|
||||
recentMessagesMap: make(map[recentMessageKey]*waProto.Message, recentMessagesSize),
|
||||
recentMessagesMap: make(map[recentMessageKey]RecentMessage, 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),
|
||||
@@ -218,19 +253,35 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
return cli
|
||||
}
|
||||
|
||||
// SetProxyAddress is a helper method that parses a URL string and calls SetProxy.
|
||||
// SetProxyAddress is a helper method that parses a URL string and calls SetProxy or SetSOCKSProxy based on the URL scheme.
|
||||
//
|
||||
// Returns an error if url.Parse fails to parse the given address.
|
||||
func (cli *Client) SetProxyAddress(addr string) error {
|
||||
func (cli *Client) SetProxyAddress(addr string, opts ...SetProxyOptions) error {
|
||||
if addr == "" {
|
||||
cli.SetProxy(nil, opts...)
|
||||
return nil
|
||||
}
|
||||
parsed, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli.SetProxy(http.ProxyURL(parsed))
|
||||
if parsed.Scheme == "http" || parsed.Scheme == "https" {
|
||||
cli.SetProxy(http.ProxyURL(parsed), opts...)
|
||||
} else if parsed.Scheme == "socks5" {
|
||||
px, err := proxy.FromURL(parsed, proxy.Direct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli.SetSOCKSProxy(px, opts...)
|
||||
} else {
|
||||
return fmt.Errorf("unsupported proxy scheme %q", parsed.Scheme)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetProxy sets the proxy to use for WhatsApp web websocket connections and media uploads/downloads.
|
||||
type Proxy = func(*http.Request) (*url.URL, error)
|
||||
|
||||
// SetProxy sets a HTTP proxy to use for WhatsApp web websocket connections and media uploads/downloads.
|
||||
//
|
||||
// Must be called before Connect() to take effect in the websocket connection.
|
||||
// If you want to change the proxy after connecting, you must call Disconnect() and then Connect() again manually.
|
||||
@@ -250,9 +301,59 @@ func (cli *Client) SetProxyAddress(addr string) error {
|
||||
// return mediaProxyURL, nil
|
||||
// }
|
||||
// })
|
||||
func (cli *Client) SetProxy(proxy socket.Proxy) {
|
||||
cli.proxy = proxy
|
||||
cli.http.Transport.(*http.Transport).Proxy = proxy
|
||||
func (cli *Client) SetProxy(proxy Proxy, opts ...SetProxyOptions) {
|
||||
var opt SetProxyOptions
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
if !opt.NoWebsocket {
|
||||
cli.proxy = proxy
|
||||
cli.socksProxy = nil
|
||||
}
|
||||
if !opt.NoMedia {
|
||||
transport := cli.http.Transport.(*http.Transport)
|
||||
transport.Proxy = proxy
|
||||
transport.Dial = nil
|
||||
transport.DialContext = nil
|
||||
}
|
||||
}
|
||||
|
||||
type SetProxyOptions struct {
|
||||
// If NoWebsocket is true, the proxy won't be used for the websocket
|
||||
NoWebsocket bool
|
||||
// If NoMedia is true, the proxy won't be used for media uploads/downloads
|
||||
NoMedia bool
|
||||
}
|
||||
|
||||
// SetSOCKSProxy sets a SOCKS5 proxy to use for WhatsApp web websocket connections and media uploads/downloads.
|
||||
//
|
||||
// Same details as SetProxy apply, but using a different proxy for the websocket and media is not currently supported.
|
||||
func (cli *Client) SetSOCKSProxy(px proxy.Dialer, opts ...SetProxyOptions) {
|
||||
var opt SetProxyOptions
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
if !opt.NoWebsocket {
|
||||
cli.socksProxy = px
|
||||
cli.proxy = nil
|
||||
}
|
||||
if !opt.NoMedia {
|
||||
transport := cli.http.Transport.(*http.Transport)
|
||||
transport.Proxy = nil
|
||||
transport.Dial = cli.socksProxy.Dial
|
||||
contextDialer, ok := cli.socksProxy.(proxy.ContextDialer)
|
||||
if ok {
|
||||
transport.DialContext = contextDialer.DialContext
|
||||
} else {
|
||||
transport.DialContext = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleProxyOnlyForLogin changes whether the proxy set with SetProxy or related methods
|
||||
// is only used for the pre-login websocket and not authenticated websockets.
|
||||
func (cli *Client) ToggleProxyOnlyForLogin(only bool) {
|
||||
cli.proxyOnlyLogin = only
|
||||
}
|
||||
|
||||
func (cli *Client) getSocketWaitChan() <-chan struct{} {
|
||||
@@ -308,7 +409,27 @@ func (cli *Client) Connect() error {
|
||||
}
|
||||
|
||||
cli.resetExpectedDisconnect()
|
||||
fs := socket.NewFrameSocket(cli.Log.Sub("Socket"), socket.WAConnHeader, cli.proxy)
|
||||
wsDialer := websocket.Dialer{}
|
||||
if !cli.proxyOnlyLogin || cli.Store.ID == nil {
|
||||
if cli.proxy != nil {
|
||||
wsDialer.Proxy = cli.proxy
|
||||
} else if cli.socksProxy != nil {
|
||||
wsDialer.NetDial = cli.socksProxy.Dial
|
||||
contextDialer, ok := cli.socksProxy.(proxy.ContextDialer)
|
||||
if ok {
|
||||
wsDialer.NetDialContext = contextDialer.DialContext
|
||||
}
|
||||
}
|
||||
}
|
||||
fs := socket.NewFrameSocket(cli.Log.Sub("Socket"), wsDialer)
|
||||
if cli.MessengerConfig != nil {
|
||||
fs.URL = "wss://web-chat-e2ee.facebook.com/ws/chat"
|
||||
fs.HTTPHeaders.Set("Origin", cli.MessengerConfig.BaseURL)
|
||||
fs.HTTPHeaders.Set("User-Agent", cli.MessengerConfig.UserAgent)
|
||||
fs.HTTPHeaders.Set("Sec-Fetch-Dest", "empty")
|
||||
fs.HTTPHeaders.Set("Sec-Fetch-Mode", "websocket")
|
||||
fs.HTTPHeaders.Set("Sec-Fetch-Site", "cross-site")
|
||||
}
|
||||
if err := fs.Connect(); err != nil {
|
||||
fs.Close(0)
|
||||
return err
|
||||
@@ -323,7 +444,7 @@ func (cli *Client) Connect() error {
|
||||
|
||||
// IsLoggedIn returns true after the client is successfully connected and authenticated on WhatsApp.
|
||||
func (cli *Client) IsLoggedIn() bool {
|
||||
return atomic.LoadUint32(&cli.isLoggedIn) == 1
|
||||
return cli.isLoggedIn.Load()
|
||||
}
|
||||
|
||||
func (cli *Client) onDisconnect(ns *socket.NoiseSocket, remote bool) {
|
||||
@@ -348,15 +469,15 @@ func (cli *Client) onDisconnect(ns *socket.NoiseSocket, remote bool) {
|
||||
}
|
||||
|
||||
func (cli *Client) expectDisconnect() {
|
||||
atomic.StoreUint32(&cli.expectedDisconnectVal, 1)
|
||||
cli.expectedDisconnect.Store(true)
|
||||
}
|
||||
|
||||
func (cli *Client) resetExpectedDisconnect() {
|
||||
atomic.StoreUint32(&cli.expectedDisconnectVal, 0)
|
||||
cli.expectedDisconnect.Store(false)
|
||||
}
|
||||
|
||||
func (cli *Client) isExpectedDisconnect() bool {
|
||||
return atomic.LoadUint32(&cli.expectedDisconnectVal) == 1
|
||||
return cli.expectedDisconnect.Load()
|
||||
}
|
||||
|
||||
func (cli *Client) autoReconnect() {
|
||||
@@ -374,6 +495,10 @@ func (cli *Client) autoReconnect() {
|
||||
return
|
||||
} else if err != nil {
|
||||
cli.Log.Errorf("Error reconnecting after autoreconnect sleep: %v", err)
|
||||
if cli.AutoReconnectHook != nil && !cli.AutoReconnectHook(err) {
|
||||
cli.Log.Debugf("AutoReconnectHook returned false, not reconnecting")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@@ -419,6 +544,9 @@ func (cli *Client) unlockedDisconnect() {
|
||||
// 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.MessengerConfig != nil {
|
||||
return errors.New("can't logout with Messenger credentials")
|
||||
}
|
||||
ownID := cli.getOwnID()
|
||||
if ownID.IsEmpty() {
|
||||
return ErrNotLoggedIn
|
||||
@@ -667,7 +795,7 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessage
|
||||
if info.Sender.IsEmpty() {
|
||||
return nil, ErrNotLoggedIn
|
||||
}
|
||||
} else if chatJID.Server == types.DefaultUserServer {
|
||||
} else if chatJID.Server == types.DefaultUserServer || chatJID.Server == types.NewsletterServer {
|
||||
info.Sender = chatJID
|
||||
} else if webMsg.GetParticipant() != "" {
|
||||
info.Sender, err = types.ParseJID(webMsg.GetParticipant())
|
||||
|
||||
+31
-3
@@ -7,7 +7,6 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func (cli *Client) handleStreamError(node *waBinary.Node) {
|
||||
atomic.StoreUint32(&cli.isLoggedIn, 0)
|
||||
cli.isLoggedIn.Store(false)
|
||||
cli.clearResponseWaiters(node)
|
||||
code, _ := node.Attrs["code"].(string)
|
||||
conflict, _ := node.GetOptionalChildByTag("conflict")
|
||||
@@ -48,6 +47,16 @@ func (cli *Client) handleStreamError(node *waBinary.Node) {
|
||||
// This seems to happen when the server wants to restart or something.
|
||||
// The disconnection will be emitted as an events.Disconnected and then the auto-reconnect will do its thing.
|
||||
cli.Log.Warnf("Got 503 stream error, assuming automatic reconnect will handle it")
|
||||
case cli.RefreshCAT != nil && (code == events.ConnectFailureCATInvalid.NumberString() || code == events.ConnectFailureCATExpired.NumberString()):
|
||||
cli.Log.Infof("Got %s stream error, refreshing CAT before reconnecting...", code)
|
||||
cli.socketLock.RLock()
|
||||
defer cli.socketLock.RUnlock()
|
||||
err := cli.RefreshCAT()
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to refresh CAT: %v", err)
|
||||
cli.expectDisconnect()
|
||||
go cli.dispatchEvent(&events.CATRefreshError{Error: err})
|
||||
}
|
||||
default:
|
||||
cli.Log.Errorf("Unknown stream error: %s", node.XMLString())
|
||||
go cli.dispatchEvent(&events.StreamError{Code: code, Raw: node})
|
||||
@@ -89,9 +98,20 @@ func (cli *Client) handleConnectFailure(node *waBinary.Node) {
|
||||
willAutoReconnect = false
|
||||
case reason == events.ConnectFailureServiceUnavailable:
|
||||
// Auto-reconnect for 503s
|
||||
case reason == events.ConnectFailureCATInvalid || reason == events.ConnectFailureCATExpired:
|
||||
// Auto-reconnect when rotating CAT, lock socket to ensure refresh goes through before reconnect
|
||||
cli.socketLock.RLock()
|
||||
defer cli.socketLock.RUnlock()
|
||||
case reason == 500 && message == "biz vname fetch error":
|
||||
// These happen for business accounts randomly, also auto-reconnect
|
||||
}
|
||||
if reason == 403 {
|
||||
cli.Log.Debugf(
|
||||
"Message for 403 connect failure: %s / %s",
|
||||
ag.OptionalString("logout_message_header"),
|
||||
ag.OptionalString("logout_message_subtext"),
|
||||
)
|
||||
}
|
||||
if reason.IsLoggedOut() {
|
||||
cli.Log.Infof("Got %s connect failure, sending LoggedOut event and deleting session", reason)
|
||||
go cli.dispatchEvent(&events.LoggedOut{OnConnect: true, Reason: reason})
|
||||
@@ -108,6 +128,14 @@ func (cli *Client) handleConnectFailure(node *waBinary.Node) {
|
||||
} else if reason == events.ConnectFailureClientOutdated {
|
||||
cli.Log.Errorf("Client outdated (405) connect failure (client version: %s)", store.GetWAVersion().String())
|
||||
go cli.dispatchEvent(&events.ClientOutdated{})
|
||||
} else if reason == events.ConnectFailureCATInvalid || reason == events.ConnectFailureCATExpired {
|
||||
cli.Log.Infof("Got %d/%s connect failure, refreshing CAT before reconnecting...", int(reason), message)
|
||||
err := cli.RefreshCAT()
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to refresh CAT: %v", err)
|
||||
cli.expectDisconnect()
|
||||
go cli.dispatchEvent(&events.CATRefreshError{Error: err})
|
||||
}
|
||||
} else if willAutoReconnect {
|
||||
cli.Log.Warnf("Got %d/%s connect failure, assuming automatic reconnect will handle it", int(reason), message)
|
||||
} else {
|
||||
@@ -120,7 +148,7 @@ func (cli *Client) handleConnectSuccess(node *waBinary.Node) {
|
||||
cli.Log.Infof("Successfully authenticated")
|
||||
cli.LastSuccessfulConnect = time.Now()
|
||||
cli.AutoReconnectErrors = 0
|
||||
atomic.StoreUint32(&cli.isLoggedIn, 1)
|
||||
cli.isLoggedIn.Store(true)
|
||||
go func() {
|
||||
if dbCount, err := cli.Store.PreKeys.UploadedPreKeyCount(); err != nil {
|
||||
cli.Log.Errorf("Failed to get number of prekeys in database: %v", err)
|
||||
|
||||
+54
-36
@@ -10,6 +10,7 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -17,9 +18,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/util/retryafter"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMediaTransport"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/socket"
|
||||
"go.mau.fi/whatsmeow/util/cbcutil"
|
||||
@@ -208,6 +211,10 @@ func (cli *Client) Download(msg DownloadableMessage) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) DownloadFB(transport *waMediaTransport.WAMediaTransport_Integral, mediaType MediaType) ([]byte, error) {
|
||||
return cli.DownloadMediaWithPath(transport.GetDirectPath(), transport.GetFileEncSHA256(), transport.GetFileSHA256(), transport.GetMediaKey(), -1, mediaType, mediaTypeToMMSType[mediaType])
|
||||
}
|
||||
|
||||
// DownloadMediaWithPath downloads an attachment by manually specifying the path and encryption details.
|
||||
func (cli *Client) DownloadMediaWithPath(directPath string, encFileHash, fileHash, mediaKey []byte, fileLength int, mediaType MediaType, mmsType string) (data []byte, err error) {
|
||||
var mediaConn *MediaConn
|
||||
@@ -219,15 +226,16 @@ func (cli *Client) DownloadMediaWithPath(directPath string, encFileHash, fileHas
|
||||
mmsType = mediaTypeToMMSType[mediaType]
|
||||
}
|
||||
for i, host := range mediaConn.Hosts {
|
||||
// TODO omit hash for unencrypted media?
|
||||
mediaURL := fmt.Sprintf("https://%s%s&hash=%s&mms-type=%s&__wa-mms=", host.Hostname, directPath, base64.URLEncoding.EncodeToString(encFileHash), mmsType)
|
||||
data, err = cli.downloadAndDecrypt(mediaURL, mediaKey, mediaType, fileLength, encFileHash, fileHash)
|
||||
// TODO there are probably some errors that shouldn't retry
|
||||
if err != nil {
|
||||
if i >= len(mediaConn.Hosts)-1 {
|
||||
return nil, fmt.Errorf("failed to download media from last host: %w", err)
|
||||
}
|
||||
cli.Log.Warnf("Failed to download media: %s, trying with next host...", err)
|
||||
if err == nil {
|
||||
return
|
||||
} else if i >= len(mediaConn.Hosts)-1 {
|
||||
return nil, fmt.Errorf("failed to download media from last host: %w", err)
|
||||
}
|
||||
// TODO there are probably some errors that shouldn't retry
|
||||
cli.Log.Warnf("Failed to download media: %s, trying with next host...", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -235,8 +243,11 @@ func (cli *Client) DownloadMediaWithPath(directPath string, encFileHash, fileHas
|
||||
func (cli *Client) downloadAndDecrypt(url string, mediaKey []byte, appInfo MediaType, fileLength int, fileEncSha256, fileSha256 []byte) (data []byte, err error) {
|
||||
iv, cipherKey, macKey, _ := getMediaKeys(mediaKey, appInfo)
|
||||
var ciphertext, mac []byte
|
||||
if ciphertext, mac, err = cli.downloadEncryptedMediaWithRetries(url, fileEncSha256); err != nil {
|
||||
if ciphertext, mac, err = cli.downloadPossiblyEncryptedMediaWithRetries(url, fileEncSha256); err != nil {
|
||||
|
||||
} else if mediaKey == nil && fileEncSha256 == nil && mac == nil {
|
||||
// Unencrypted media, just return the downloaded data
|
||||
data = ciphertext
|
||||
} else if err = validateMedia(iv, ciphertext, macKey, mac); err != nil {
|
||||
|
||||
} else if data, err = cbcutil.Decrypt(cipherKey, iv, ciphertext); err != nil {
|
||||
@@ -254,52 +265,59 @@ func getMediaKeys(mediaKey []byte, appInfo MediaType) (iv, cipherKey, macKey, re
|
||||
return mediaKeyExpanded[:16], mediaKeyExpanded[16:48], mediaKeyExpanded[48:80], mediaKeyExpanded[80:]
|
||||
}
|
||||
|
||||
func (cli *Client) downloadEncryptedMediaWithRetries(url string, checksum []byte) (file, mac []byte, err error) {
|
||||
func shouldRetryMediaDownload(err error) bool {
|
||||
var netErr net.Error
|
||||
var httpErr DownloadHTTPError
|
||||
return errors.As(err, &netErr) ||
|
||||
strings.HasPrefix(err.Error(), "stream error:") || // hacky check for http2 errors
|
||||
(errors.As(err, &httpErr) && retryafter.Should(httpErr.StatusCode, true))
|
||||
}
|
||||
|
||||
func (cli *Client) downloadPossiblyEncryptedMediaWithRetries(url string, checksum []byte) (file, mac []byte, err error) {
|
||||
for retryNum := 0; retryNum < 5; retryNum++ {
|
||||
file, mac, err = cli.downloadEncryptedMedia(url, checksum)
|
||||
if err == nil {
|
||||
if checksum == nil {
|
||||
file, err = cli.downloadMedia(url)
|
||||
} else {
|
||||
file, mac, err = cli.downloadEncryptedMedia(url, checksum)
|
||||
}
|
||||
if err == nil || !shouldRetryMediaDownload(err) {
|
||||
return
|
||||
}
|
||||
netErr, ok := err.(net.Error)
|
||||
if !ok {
|
||||
// Not a network error, don't retry
|
||||
return
|
||||
retryDuration := time.Duration(retryNum+1) * time.Second
|
||||
var httpErr DownloadHTTPError
|
||||
if errors.As(err, &httpErr) {
|
||||
retryDuration = retryafter.Parse(httpErr.Response.Header.Get("Retry-After"), retryDuration)
|
||||
}
|
||||
cli.Log.Warnf("Failed to download media due to network error: %w, retrying...", netErr)
|
||||
time.Sleep(time.Duration(retryNum+1) * time.Second)
|
||||
cli.Log.Warnf("Failed to download media due to network error: %w, retrying in %s...", err, retryDuration)
|
||||
time.Sleep(retryDuration)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) downloadEncryptedMedia(url string, checksum []byte) (file, mac []byte, err error) {
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(http.MethodGet, url, nil)
|
||||
func (cli *Client) downloadMedia(url string) ([]byte, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to prepare request: %w", err)
|
||||
return
|
||||
return nil, fmt.Errorf("failed to prepare request: %w", err)
|
||||
}
|
||||
req.Header.Set("Origin", socket.Origin)
|
||||
req.Header.Set("Referer", socket.Origin+"/")
|
||||
var resp *http.Response
|
||||
resp, err = cli.http.Do(req)
|
||||
if cli.MessengerConfig != nil {
|
||||
req.Header.Set("User-Agent", cli.MessengerConfig.UserAgent)
|
||||
}
|
||||
// TODO user agent for whatsapp downloads?
|
||||
resp, err := cli.http.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
err = ErrMediaDownloadFailedWith403
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
err = ErrMediaDownloadFailedWith404
|
||||
} else if resp.StatusCode == http.StatusGone {
|
||||
err = ErrMediaDownloadFailedWith410
|
||||
} else {
|
||||
err = fmt.Errorf("download failed with status code %d", resp.StatusCode)
|
||||
}
|
||||
return
|
||||
return nil, DownloadHTTPError{Response: resp}
|
||||
}
|
||||
var data []byte
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (cli *Client) downloadEncryptedMedia(url string, checksum []byte) (file, mac []byte, err error) {
|
||||
data, err := cli.downloadMedia(url)
|
||||
if err != nil {
|
||||
return
|
||||
} else if len(data) <= 10 {
|
||||
|
||||
Vendored
+18
-4
@@ -9,6 +9,7 @@ package whatsmeow
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
)
|
||||
@@ -103,15 +104,28 @@ var (
|
||||
var (
|
||||
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")
|
||||
ErrRecipientADJID = errors.New("message recipient must be a user JID with no device part")
|
||||
ErrServerReturnedError = errors.New("server returned error")
|
||||
)
|
||||
|
||||
type DownloadHTTPError struct {
|
||||
*http.Response
|
||||
}
|
||||
|
||||
func (dhe DownloadHTTPError) Error() string {
|
||||
return fmt.Sprintf("download failed with status code %d", dhe.StatusCode)
|
||||
}
|
||||
|
||||
func (dhe DownloadHTTPError) Is(other error) bool {
|
||||
var otherDHE DownloadHTTPError
|
||||
return errors.As(other, &otherDHE) && dhe.StatusCode == otherDHE.StatusCode
|
||||
}
|
||||
|
||||
// Some errors that Client.Download can return
|
||||
var (
|
||||
ErrMediaDownloadFailedWith403 = errors.New("download failed with status code 403")
|
||||
ErrMediaDownloadFailedWith404 = errors.New("download failed with status code 404")
|
||||
ErrMediaDownloadFailedWith410 = errors.New("download failed with status code 410")
|
||||
ErrMediaDownloadFailedWith403 = DownloadHTTPError{Response: &http.Response{StatusCode: 403}}
|
||||
ErrMediaDownloadFailedWith404 = DownloadHTTPError{Response: &http.Response{StatusCode: 404}}
|
||||
ErrMediaDownloadFailedWith410 = DownloadHTTPError{Response: &http.Response{StatusCode: 410}}
|
||||
ErrNoURLPresent = errors.New("no url present")
|
||||
ErrFileLengthMismatch = errors.New("file length does not match")
|
||||
ErrTooShortFile = errors.New("file too short")
|
||||
|
||||
Vendored
+110
-34
@@ -148,30 +148,93 @@ const (
|
||||
)
|
||||
|
||||
// UpdateGroupParticipants can be used to add, remove, promote and demote members in a WhatsApp group.
|
||||
func (cli *Client) UpdateGroupParticipants(jid types.JID, participantChanges map[types.JID]ParticipantChange) (*waBinary.Node, error) {
|
||||
func (cli *Client) UpdateGroupParticipants(jid types.JID, participantChanges []types.JID, action ParticipantChange) ([]types.GroupParticipant, error) {
|
||||
content := make([]waBinary.Node, len(participantChanges))
|
||||
i := 0
|
||||
for participantJID, change := range participantChanges {
|
||||
for i, participantJID := range participantChanges {
|
||||
content[i] = waBinary.Node{
|
||||
Tag: string(change),
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "participant",
|
||||
Attrs: waBinary.Attrs{"jid": participantJID},
|
||||
}},
|
||||
Tag: "participant",
|
||||
Attrs: waBinary.Attrs{"jid": participantJID},
|
||||
}
|
||||
i++
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "w:g2",
|
||||
Type: iqSet,
|
||||
To: jid,
|
||||
Content: content,
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
Tag: string(action),
|
||||
Content: content,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO proper return value?
|
||||
return resp, nil
|
||||
requestAction, ok := resp.GetOptionalChildByTag(string(action))
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: string(action), In: "response to group participants update"}
|
||||
}
|
||||
requestParticipants := requestAction.GetChildrenByTag("participant")
|
||||
participants := make([]types.GroupParticipant, len(requestParticipants))
|
||||
for i, child := range requestParticipants {
|
||||
participants[i] = parseParticipant(child.AttrGetter(), &child)
|
||||
}
|
||||
return participants, nil
|
||||
}
|
||||
|
||||
// GetGroupRequestParticipants gets the list of participants that have requested to join the group.
|
||||
func (cli *Client) GetGroupRequestParticipants(jid types.JID) ([]types.JID, error) {
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqGet, jid, waBinary.Node{
|
||||
Tag: "membership_approval_requests",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request, ok := resp.GetOptionalChildByTag("membership_approval_requests")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "membership_approval_requests", In: "response to group request participants query"}
|
||||
}
|
||||
requestParticipants := request.GetChildrenByTag("membership_approval_request")
|
||||
participants := make([]types.JID, len(requestParticipants))
|
||||
for i, req := range requestParticipants {
|
||||
participants[i] = req.AttrGetter().JID("jid")
|
||||
}
|
||||
return participants, nil
|
||||
}
|
||||
|
||||
type ParticipantRequestChange string
|
||||
|
||||
const (
|
||||
ParticipantChangeApprove ParticipantRequestChange = "approve"
|
||||
ParticipantChangeReject ParticipantRequestChange = "reject"
|
||||
)
|
||||
|
||||
// UpdateGroupRequestParticipants can be used to approve or reject requests to join the group.
|
||||
func (cli *Client) UpdateGroupRequestParticipants(jid types.JID, participantChanges []types.JID, action ParticipantRequestChange) ([]types.GroupParticipant, error) {
|
||||
content := make([]waBinary.Node, len(participantChanges))
|
||||
for i, participantJID := range participantChanges {
|
||||
content[i] = waBinary.Node{
|
||||
Tag: "participant",
|
||||
Attrs: waBinary.Attrs{"jid": participantJID},
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
Tag: "membership_requests_action",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: string(action),
|
||||
Content: content,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request, ok := resp.GetOptionalChildByTag("membership_requests_action")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "membership_requests_action", In: "response to group request participants update"}
|
||||
}
|
||||
requestAction, ok := request.GetOptionalChildByTag(string(action))
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: string(action), In: "response to group request participants update"}
|
||||
}
|
||||
requestParticipants := requestAction.GetChildrenByTag("participant")
|
||||
participants := make([]types.GroupParticipant, len(requestParticipants))
|
||||
for i, child := range requestParticipants {
|
||||
participants[i] = parseParticipant(child.AttrGetter(), &child)
|
||||
}
|
||||
return participants, nil
|
||||
}
|
||||
|
||||
// SetGroupPhoto updates the group picture/icon of the given group on WhatsApp.
|
||||
@@ -501,6 +564,33 @@ func (cli *Client) getGroupMembers(ctx context.Context, jid types.JID) ([]types.
|
||||
return cli.groupParticipantsCache[jid], nil
|
||||
}
|
||||
|
||||
func parseParticipant(childAG *waBinary.AttrUtility, child *waBinary.Node) types.GroupParticipant {
|
||||
pcpType := childAG.OptionalString("type")
|
||||
participant := types.GroupParticipant{
|
||||
IsAdmin: pcpType == "admin" || pcpType == "superadmin",
|
||||
IsSuperAdmin: pcpType == "superadmin",
|
||||
JID: childAG.JID("jid"),
|
||||
LID: childAG.OptionalJIDOrEmpty("lid"),
|
||||
DisplayName: childAG.OptionalString("display_name"),
|
||||
}
|
||||
if participant.JID.Server == types.HiddenUserServer && participant.LID.IsEmpty() {
|
||||
participant.LID = participant.JID
|
||||
//participant.JID = types.EmptyJID
|
||||
}
|
||||
if errorCode := childAG.OptionalInt("error"); errorCode != 0 {
|
||||
participant.Error = errorCode
|
||||
addRequest, ok := child.GetOptionalChildByTag("add_request")
|
||||
if ok {
|
||||
addAG := addRequest.AttrGetter()
|
||||
participant.AddRequest = &types.GroupParticipantAddRequest{
|
||||
Code: addAG.String("code"),
|
||||
Expiration: addAG.UnixTime("expiration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
return participant
|
||||
}
|
||||
|
||||
func (cli *Client) parseGroupNode(groupNode *waBinary.Node) (*types.GroupInfo, error) {
|
||||
var group types.GroupInfo
|
||||
ag := groupNode.AttrGetter()
|
||||
@@ -521,24 +611,7 @@ func (cli *Client) parseGroupNode(groupNode *waBinary.Node) (*types.GroupInfo, e
|
||||
childAG := child.AttrGetter()
|
||||
switch child.Tag {
|
||||
case "participant":
|
||||
pcpType := childAG.OptionalString("type")
|
||||
participant := types.GroupParticipant{
|
||||
IsAdmin: pcpType == "admin" || pcpType == "superadmin",
|
||||
IsSuperAdmin: pcpType == "superadmin",
|
||||
JID: childAG.JID("jid"),
|
||||
}
|
||||
if errorCode := childAG.OptionalInt("error"); errorCode != 0 {
|
||||
participant.Error = errorCode
|
||||
addRequest, ok := child.GetOptionalChildByTag("add_request")
|
||||
if ok {
|
||||
addAG := addRequest.AttrGetter()
|
||||
participant.AddRequest = &types.GroupParticipantAddRequest{
|
||||
Code: addAG.String("code"),
|
||||
Expiration: addAG.UnixTime("expiration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
group.Participants = append(group.Participants, participant)
|
||||
group.Participants = append(group.Participants, parseParticipant(childAG, &child))
|
||||
case "description":
|
||||
body, bodyOK := child.GetOptionalChildByTag("body")
|
||||
if bodyOK {
|
||||
@@ -565,6 +638,9 @@ func (cli *Client) parseGroupNode(groupNode *waBinary.Node) (*types.GroupInfo, e
|
||||
case "parent":
|
||||
group.IsParent = true
|
||||
group.DefaultMembershipApprovalMode = childAG.OptionalString("default_membership_approval_mode")
|
||||
case "incognito":
|
||||
group.IsIncognito = true
|
||||
// TODO: membership_approval_mode
|
||||
default:
|
||||
cli.Log.Debugf("Unknown element in group node %s: %s", group.JID.String(), child.XMLString())
|
||||
}
|
||||
|
||||
+51
-18
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/libsignal/ecc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
@@ -19,6 +20,9 @@ import (
|
||||
)
|
||||
|
||||
const NoiseHandshakeResponseTimeout = 20 * time.Second
|
||||
const WACertIssuerSerial = 0
|
||||
|
||||
var WACertPubKey = [...]byte{0x14, 0x23, 0x75, 0x57, 0x4d, 0xa, 0x58, 0x71, 0x66, 0xaa, 0xe7, 0x1e, 0xbe, 0x51, 0x64, 0x37, 0xc4, 0xa2, 0x8b, 0x73, 0xe3, 0x69, 0x5c, 0x6c, 0xe1, 0xf7, 0xf9, 0x54, 0x5d, 0xa8, 0xee, 0x6b}
|
||||
|
||||
// doHandshake implements the Noise_XX_25519_AESGCM_SHA256 handshake for the WhatsApp web API.
|
||||
func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair) error {
|
||||
@@ -76,23 +80,8 @@ func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair)
|
||||
certDecrypted, err := nh.Decrypt(certificateCiphertext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt noise certificate ciphertext: %w", err)
|
||||
}
|
||||
var cert waProto.NoiseCertificate
|
||||
err = proto.Unmarshal(certDecrypted, &cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal noise certificate: %w", err)
|
||||
}
|
||||
certDetailsRaw := cert.GetDetails()
|
||||
certSignature := cert.GetSignature()
|
||||
if certDetailsRaw == nil || certSignature == nil {
|
||||
return fmt.Errorf("missing parts of noise certificate")
|
||||
}
|
||||
var certDetails waProto.NoiseCertificate_Details
|
||||
err = proto.Unmarshal(certDetailsRaw, &certDetails)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal noise certificate details: %w", err)
|
||||
} else if !bytes.Equal(certDetails.GetKey(), staticDecrypted) {
|
||||
return fmt.Errorf("cert key doesn't match decrypted static")
|
||||
} else if err = verifyServerCert(certDecrypted, staticDecrypted); err != nil {
|
||||
return fmt.Errorf("failed to verify server cert: %w", err)
|
||||
}
|
||||
|
||||
encryptedPubkey := nh.Encrypt(cli.Store.NoiseKey.Pub[:])
|
||||
@@ -101,7 +90,14 @@ func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair)
|
||||
return fmt.Errorf("failed to mix noise private key in: %w", err)
|
||||
}
|
||||
|
||||
clientFinishPayloadBytes, err := proto.Marshal(cli.Store.GetClientPayload())
|
||||
var clientPayload *waProto.ClientPayload
|
||||
if cli.GetClientPayload != nil {
|
||||
clientPayload = cli.GetClientPayload()
|
||||
} else {
|
||||
clientPayload = cli.Store.GetClientPayload()
|
||||
}
|
||||
|
||||
clientFinishPayloadBytes, err := proto.Marshal(clientPayload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal client finish payload: %w", err)
|
||||
}
|
||||
@@ -129,3 +125,40 @@ func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyServerCert(certDecrypted, staticDecrypted []byte) error {
|
||||
var certChain waProto.CertChain
|
||||
err := proto.Unmarshal(certDecrypted, &certChain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal noise certificate: %w", err)
|
||||
}
|
||||
var intermediateCertDetails, leafCertDetails waProto.CertChain_NoiseCertificate_Details
|
||||
intermediateCertDetailsRaw := certChain.GetIntermediate().GetDetails()
|
||||
intermediateCertSignature := certChain.GetIntermediate().GetSignature()
|
||||
leafCertDetailsRaw := certChain.GetLeaf().GetDetails()
|
||||
leafCertSignature := certChain.GetLeaf().GetSignature()
|
||||
if intermediateCertDetailsRaw == nil || intermediateCertSignature == nil || leafCertDetailsRaw == nil || leafCertSignature == nil {
|
||||
return fmt.Errorf("missing parts of noise certificate")
|
||||
} else if len(intermediateCertSignature) != 64 {
|
||||
return fmt.Errorf("unexpected length of intermediate cert signature %d (expected 64)", len(intermediateCertSignature))
|
||||
} else if len(leafCertSignature) != 64 {
|
||||
return fmt.Errorf("unexpected length of leaf cert signature %d (expected 64)", len(leafCertSignature))
|
||||
} else if !ecc.VerifySignature(ecc.NewDjbECPublicKey(WACertPubKey), intermediateCertDetailsRaw, [64]byte(intermediateCertSignature)) {
|
||||
return fmt.Errorf("failed to verify intermediate cert signature")
|
||||
} else if err = proto.Unmarshal(intermediateCertDetailsRaw, &intermediateCertDetails); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal noise certificate details: %w", err)
|
||||
} else if intermediateCertDetails.GetIssuerSerial() != WACertIssuerSerial {
|
||||
return fmt.Errorf("unexpected intermediate issuer serial %d (expected %d)", intermediateCertDetails.GetIssuerSerial(), WACertIssuerSerial)
|
||||
} else if len(intermediateCertDetails.GetKey()) != 32 {
|
||||
return fmt.Errorf("unexpected length of intermediate cert key %d (expected 32)", len(intermediateCertDetails.GetKey()))
|
||||
} else if !ecc.VerifySignature(ecc.NewDjbECPublicKey([32]byte(intermediateCertDetails.GetKey())), leafCertDetailsRaw, [64]byte(leafCertSignature)) {
|
||||
return fmt.Errorf("failed to verify intermediate cert signature")
|
||||
} else if err = proto.Unmarshal(leafCertDetailsRaw, &leafCertDetails); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal noise certificate details: %w", err)
|
||||
} else if leafCertDetails.GetIssuerSerial() != intermediateCertDetails.GetSerial() {
|
||||
return fmt.Errorf("unexpected leaf issuer serial %d (expected %d)", leafCertDetails.GetIssuerSerial(), intermediateCertDetails.GetSerial())
|
||||
} else if !bytes.Equal(leafCertDetails.GetKey(), staticDecrypted) {
|
||||
return fmt.Errorf("cert key doesn't match decrypted static")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+18
@@ -9,6 +9,8 @@ package whatsmeow
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.mau.fi/libsignal/keys/prekey"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
@@ -66,3 +68,19 @@ func (int *DangerousInternalClient) RequestAppStateKeys(ctx context.Context, key
|
||||
func (int *DangerousInternalClient) SendRetryReceipt(node *waBinary.Node, info *types.MessageInfo, forceIncludeIdentity bool) {
|
||||
int.c.sendRetryReceipt(node, info, forceIncludeIdentity)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) EncryptMessageForDevice(plaintext []byte, to types.JID, bundle *prekey.Bundle, extraAttrs waBinary.Attrs) (*waBinary.Node, bool, error) {
|
||||
return int.c.encryptMessageForDevice(plaintext, to, bundle, extraAttrs)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) GetOwnID() types.JID {
|
||||
return int.c.getOwnID()
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DecryptDM(child *waBinary.Node, from types.JID, isPreKey bool) ([]byte, error) {
|
||||
return int.c.decryptDM(child, from, isPreKey)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) MakeDeviceIdentityNode() waBinary.Node {
|
||||
return int.c.makeDeviceIdentityNode()
|
||||
}
|
||||
|
||||
-2
@@ -11,7 +11,6 @@ import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
@@ -67,7 +66,6 @@ func (cli *Client) sendKeepAlive(ctx context.Context) (isSuccess, shouldContinue
|
||||
Namespace: "w:p",
|
||||
Type: "get",
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{Tag: "ping"}},
|
||||
})
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to send keepalive: %v", err)
|
||||
|
||||
+2
-2
@@ -9,6 +9,7 @@ package whatsmeow
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
func getMediaRetryKey(mediaKey []byte) (cipherKey []byte) {
|
||||
@@ -34,7 +34,7 @@ func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphe
|
||||
err = fmt.Errorf("failed to marshal payload: %w", err)
|
||||
return
|
||||
}
|
||||
iv = randbytes.Make(12)
|
||||
iv = random.Bytes(12)
|
||||
ciphertext, err = gcmutil.Encrypt(getMediaRetryKey(mediaKey), iv, plaintext, []byte(messageID))
|
||||
return
|
||||
}
|
||||
|
||||
+102
-37
@@ -14,15 +14,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/libsignal/signalerror"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/libsignal/groups"
|
||||
"go.mau.fi/libsignal/protocol"
|
||||
"go.mau.fi/libsignal/session"
|
||||
"go.mau.fi/libsignal/signalerror"
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/whatsmeow/appstate"
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
@@ -30,7 +29,6 @@ import (
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
var pbSerializer = store.SignalProtobufSerializer
|
||||
@@ -46,7 +44,12 @@ func (cli *Client) handleEncryptedMessage(node *waBinary.Node) {
|
||||
if len(info.PushName) > 0 && info.PushName != "-" {
|
||||
go cli.updatePushName(info.Sender, info, info.PushName)
|
||||
}
|
||||
cli.decryptMessages(info, node)
|
||||
go cli.sendAck(node)
|
||||
if info.Sender.Server == types.NewsletterServer {
|
||||
cli.handlePlaintextMessage(info, node)
|
||||
} else {
|
||||
cli.decryptMessages(info, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +75,10 @@ func (cli *Client) parseMessageSource(node *waBinary.Node, requireParticipant bo
|
||||
if from.Server == types.BroadcastServer {
|
||||
source.BroadcastListOwner = ag.OptionalJIDOrEmpty("recipient")
|
||||
}
|
||||
} else if from.Server == types.NewsletterServer {
|
||||
source.Chat = from
|
||||
source.Sender = from
|
||||
// TODO IsFromMe?
|
||||
} else if from.User == clientID.User {
|
||||
source.IsFromMe = true
|
||||
source.Sender = from
|
||||
@@ -98,40 +105,83 @@ func (cli *Client) parseMessageInfo(node *waBinary.Node) (*types.MessageInfo, er
|
||||
}
|
||||
ag := node.AttrGetter()
|
||||
info.ID = types.MessageID(ag.String("id"))
|
||||
info.ServerID = types.MessageServerID(ag.OptionalInt("server_id"))
|
||||
info.Timestamp = ag.UnixTime("t")
|
||||
info.PushName = ag.OptionalString("notify")
|
||||
info.Category = ag.OptionalString("category")
|
||||
info.Type = ag.OptionalString("type")
|
||||
info.Edit = types.EditAttribute(ag.OptionalString("edit"))
|
||||
if !ag.OK() {
|
||||
return nil, ag.Error()
|
||||
}
|
||||
|
||||
for _, child := range node.GetChildren() {
|
||||
if child.Tag == "multicast" {
|
||||
switch child.Tag {
|
||||
case "multicast":
|
||||
info.Multicast = true
|
||||
} else if child.Tag == "verified_name" {
|
||||
case "verified_name":
|
||||
info.VerifiedName, err = parseVerifiedNameContent(child)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to parse verified_name node in %s: %v", info.ID, err)
|
||||
}
|
||||
} else if mediaType, ok := child.AttrGetter().GetString("mediatype", false); ok {
|
||||
info.MediaType = mediaType
|
||||
case "franking":
|
||||
// TODO
|
||||
case "trace":
|
||||
// TODO
|
||||
default:
|
||||
if mediaType, ok := child.AttrGetter().GetString("mediatype", false); ok {
|
||||
info.MediaType = mediaType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func (cli *Client) handlePlaintextMessage(info *types.MessageInfo, node *waBinary.Node) {
|
||||
// TODO edits have an additional <meta msg_edit_t="1696321271735" original_msg_t="1696321248"/> node
|
||||
plaintext, ok := node.GetOptionalChildByTag("plaintext")
|
||||
if !ok {
|
||||
// 3:
|
||||
return
|
||||
}
|
||||
plaintextBody, ok := plaintext.Content.([]byte)
|
||||
if !ok {
|
||||
cli.Log.Warnf("Plaintext message from %s doesn't have byte content", info.SourceString())
|
||||
return
|
||||
}
|
||||
var msg waProto.Message
|
||||
err := proto.Unmarshal(plaintextBody, &msg)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Error unmarshaling plaintext message from %s: %v", info.SourceString(), err)
|
||||
return
|
||||
}
|
||||
cli.storeMessageSecret(info, &msg)
|
||||
evt := &events.Message{
|
||||
Info: *info,
|
||||
RawMessage: &msg,
|
||||
}
|
||||
meta, ok := node.GetOptionalChildByTag("meta")
|
||||
if ok {
|
||||
evt.NewsletterMeta = &events.NewsletterMessageMeta{
|
||||
EditTS: meta.AttrGetter().UnixMilli("msg_edit_t"),
|
||||
OriginalTS: meta.AttrGetter().UnixTime("original_msg_t"),
|
||||
}
|
||||
}
|
||||
cli.dispatchEvent(evt.UnwrapRaw())
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node) {
|
||||
go cli.sendAck(node)
|
||||
if len(node.GetChildrenByTag("unavailable")) > 0 && len(node.GetChildrenByTag("enc")) == 0 {
|
||||
cli.Log.Warnf("Unavailable message %s from %s", info.ID, info.SourceString())
|
||||
go cli.sendRetryReceipt(node, info, true)
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{Info: *info, IsUnavailable: true})
|
||||
return
|
||||
}
|
||||
|
||||
children := node.GetChildren()
|
||||
cli.Log.Debugf("Decrypting %d messages from %s", len(children), info.SourceString())
|
||||
cli.Log.Debugf("Decrypting message from %s", info.SourceString())
|
||||
handled := false
|
||||
containsDirectMsg := false
|
||||
for _, child := range children {
|
||||
@@ -158,28 +208,33 @@ func (cli *Client) decryptMessages(info *types.MessageInfo, node *waBinary.Node)
|
||||
cli.Log.Warnf("Error decrypting message from %s: %v", info.SourceString(), err)
|
||||
isUnavailable := encType == "skmsg" && !containsDirectMsg && errors.Is(err, signalerror.ErrNoSenderKeyForUser)
|
||||
go cli.sendRetryReceipt(node, info, isUnavailable)
|
||||
decryptFailMode, _ := child.Attrs["decrypt-fail"].(string)
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{
|
||||
Info: *info,
|
||||
IsUnavailable: isUnavailable,
|
||||
DecryptFailMode: events.DecryptFailMode(decryptFailMode),
|
||||
DecryptFailMode: events.DecryptFailMode(ag.OptionalString("decrypt-fail")),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var msg waProto.Message
|
||||
err = proto.Unmarshal(decrypted, &msg)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Error unmarshaling decrypted message from %s: %v", info.SourceString(), err)
|
||||
continue
|
||||
}
|
||||
retryCount := ag.OptionalInt("count")
|
||||
if retryCount > 0 {
|
||||
cli.cancelDelayedRequestFromPhone(info.ID)
|
||||
}
|
||||
|
||||
cli.handleDecryptedMessage(info, &msg, retryCount)
|
||||
handled = true
|
||||
var msg waProto.Message
|
||||
switch ag.Int("v") {
|
||||
case 2:
|
||||
err = proto.Unmarshal(decrypted, &msg)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Error unmarshaling decrypted message from %s: %v", info.SourceString(), err)
|
||||
continue
|
||||
}
|
||||
cli.handleDecryptedMessage(info, &msg, retryCount)
|
||||
handled = true
|
||||
case 3:
|
||||
handled = cli.handleDecryptedArmadillo(info, decrypted, retryCount)
|
||||
default:
|
||||
cli.Log.Warnf("Unknown version %d in decrypted message from %s", ag.Int("v"), info.SourceString())
|
||||
}
|
||||
}
|
||||
if handled {
|
||||
go cli.sendMessageReceipt(info)
|
||||
@@ -228,6 +283,9 @@ func (cli *Client) decryptDM(child *waBinary.Node, from types.JID, isPreKey bool
|
||||
return nil, fmt.Errorf("failed to decrypt normal message: %w", err)
|
||||
}
|
||||
}
|
||||
if child.AttrGetter().Int("v") == 3 {
|
||||
return plaintext, nil
|
||||
}
|
||||
return unpadMessage(plaintext)
|
||||
}
|
||||
|
||||
@@ -245,6 +303,9 @@ func (cli *Client) decryptGroupMsg(child *waBinary.Node, from types.JID, chat ty
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt group message: %w", err)
|
||||
}
|
||||
if child.AttrGetter().Int("v") == 3 {
|
||||
return plaintext, nil
|
||||
}
|
||||
return unpadMessage(plaintext)
|
||||
}
|
||||
|
||||
@@ -267,19 +328,19 @@ func unpadMessage(plaintext []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
func padMessage(plaintext []byte) []byte {
|
||||
pad := randbytes.Make(1)
|
||||
pad := random.Bytes(1)
|
||||
pad[0] &= 0xf
|
||||
if pad[0] == 0 {
|
||||
pad[0] = 0xf
|
||||
}
|
||||
plaintext = append(plaintext, bytes.Repeat(pad[:], int(pad[0]))...)
|
||||
plaintext = append(plaintext, bytes.Repeat(pad, int(pad[0]))...)
|
||||
return plaintext
|
||||
}
|
||||
|
||||
func (cli *Client) handleSenderKeyDistributionMessage(chat, from types.JID, rawSKDMsg *waProto.SenderKeyDistributionMessage) {
|
||||
func (cli *Client) handleSenderKeyDistributionMessage(chat, from types.JID, axolotlSKDM []byte) {
|
||||
builder := groups.NewGroupSessionBuilder(cli.Store, pbSerializer)
|
||||
senderKeyName := protocol.NewSenderKeyName(chat.String(), from.SignalAddress())
|
||||
sdkMsg, err := protocol.NewSenderKeyDistributionMessageFromBytes(rawSKDMsg.AxolotlSenderKeyDistributionMessage, pbSerializer.SenderKeyDistributionMessage)
|
||||
sdkMsg, err := protocol.NewSenderKeyDistributionMessageFromBytes(axolotlSKDM, pbSerializer.SenderKeyDistributionMessage)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to parse sender key distribution message from %s for %s: %v", from, chat, err)
|
||||
return
|
||||
@@ -290,7 +351,7 @@ func (cli *Client) handleSenderKeyDistributionMessage(chat, from types.JID, rawS
|
||||
|
||||
func (cli *Client) handleHistorySyncNotificationLoop() {
|
||||
defer func() {
|
||||
atomic.StoreUint32(&cli.historySyncHandlerStarted, 0)
|
||||
cli.historySyncHandlerStarted.Store(false)
|
||||
err := recover()
|
||||
if err != nil {
|
||||
cli.Log.Errorf("History sync handler panicked: %v\n%s", err, debug.Stack())
|
||||
@@ -298,7 +359,7 @@ func (cli *Client) handleHistorySyncNotificationLoop() {
|
||||
|
||||
// Check in case something new appeared in the channel between the loop stopping
|
||||
// and the atomic variable being updated. If yes, restart the loop.
|
||||
if len(cli.historySyncNotifications) > 0 && atomic.CompareAndSwapUint32(&cli.historySyncHandlerStarted, 0, 1) {
|
||||
if len(cli.historySyncNotifications) > 0 && cli.historySyncHandlerStarted.CompareAndSwap(false, true) {
|
||||
cli.Log.Warnf("New history sync notifications appeared after loop stopped, restarting loop...")
|
||||
go cli.handleHistorySyncNotificationLoop()
|
||||
}
|
||||
@@ -391,10 +452,10 @@ func (cli *Client) handleProtocolMessage(info *types.MessageInfo, msg *waProto.M
|
||||
|
||||
if protoMsg.GetHistorySyncNotification() != nil && info.IsFromMe {
|
||||
cli.historySyncNotifications <- protoMsg.HistorySyncNotification
|
||||
if atomic.CompareAndSwapUint32(&cli.historySyncHandlerStarted, 0, 1) {
|
||||
if cli.historySyncHandlerStarted.CompareAndSwap(false, true) {
|
||||
go cli.handleHistorySyncNotificationLoop()
|
||||
}
|
||||
go cli.sendProtocolMessageReceipt(info.ID, "hist_sync")
|
||||
go cli.sendProtocolMessageReceipt(info.ID, types.ReceiptTypeHistorySync)
|
||||
}
|
||||
|
||||
if protoMsg.GetPeerDataOperationRequestResponseMessage().GetPeerDataOperationRequestType() == waProto.PeerDataOperationRequestType_PLACEHOLDER_MESSAGE_RESEND {
|
||||
@@ -406,7 +467,7 @@ func (cli *Client) handleProtocolMessage(info *types.MessageInfo, msg *waProto.M
|
||||
}
|
||||
|
||||
if info.Category == "peer" {
|
||||
go cli.sendProtocolMessageReceipt(info.ID, "peer_msg")
|
||||
go cli.sendProtocolMessageReceipt(info.ID, types.ReceiptTypePeerMsg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,9 +478,9 @@ func (cli *Client) processProtocolParts(info *types.MessageInfo, msg *waProto.Me
|
||||
}
|
||||
if msg.GetSenderKeyDistributionMessage() != nil {
|
||||
if !info.IsGroup {
|
||||
cli.Log.Warnf("Got sender key distribution message in non-group chat from", info.Sender)
|
||||
cli.Log.Warnf("Got sender key distribution message in non-group chat from %s", info.Sender)
|
||||
} else {
|
||||
cli.handleSenderKeyDistributionMessage(info.Chat, info.Sender, msg.SenderKeyDistributionMessage)
|
||||
cli.handleSenderKeyDistributionMessage(info.Chat, info.Sender, msg.SenderKeyDistributionMessage.AxolotlSenderKeyDistributionMessage)
|
||||
}
|
||||
}
|
||||
// N.B. Edits are protocol messages, but they're also wrapped inside EditedMessage,
|
||||
@@ -427,6 +488,10 @@ func (cli *Client) processProtocolParts(info *types.MessageInfo, msg *waProto.Me
|
||||
if msg.GetProtocolMessage() != nil {
|
||||
cli.handleProtocolMessage(info, msg)
|
||||
}
|
||||
cli.storeMessageSecret(info, msg)
|
||||
}
|
||||
|
||||
func (cli *Client) storeMessageSecret(info *types.MessageInfo, msg *waProto.Message) {
|
||||
if msgSecret := msg.GetMessageContextInfo().GetMessageSecret(); len(msgSecret) > 0 {
|
||||
err := cli.Store.MsgSecrets.PutMessageSecret(info.Chat, info.Sender, info.ID, msgSecret)
|
||||
if err != nil {
|
||||
@@ -511,7 +576,7 @@ func (cli *Client) handleDecryptedMessage(info *types.MessageInfo, msg *waProto.
|
||||
cli.dispatchEvent(evt.UnwrapRaw())
|
||||
}
|
||||
|
||||
func (cli *Client) sendProtocolMessageReceipt(id, msgType string) {
|
||||
func (cli *Client) sendProtocolMessageReceipt(id types.MessageID, msgType types.ReceiptType) {
|
||||
clientID := cli.Store.ID
|
||||
if len(id) == 0 || clientID == nil {
|
||||
return
|
||||
@@ -519,8 +584,8 @@ func (cli *Client) sendProtocolMessageReceipt(id, msgType string) {
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: waBinary.Attrs{
|
||||
"id": id,
|
||||
"type": msgType,
|
||||
"id": string(id),
|
||||
"type": string(msgType),
|
||||
"to": types.NewJID(clientID.User, types.LegacyUserServer),
|
||||
},
|
||||
Content: nil,
|
||||
|
||||
+3
-3
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
@@ -18,7 +19,6 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/gcmutil"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
type MsgSecretType string
|
||||
@@ -107,7 +107,7 @@ func (cli *Client) encryptMsgSecret(chat, origSender types.JID, origMsgID types.
|
||||
}
|
||||
secretKey, additionalData := generateMsgSecretKey(useCase, ownID, origMsgID, origSender, baseEncKey)
|
||||
|
||||
iv = randbytes.Make(12)
|
||||
iv = random.Bytes(12)
|
||||
ciphertext, err = gcmutil.Encrypt(secretKey, iv, plaintext, additionalData)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encrypt secret message: %w", err)
|
||||
@@ -221,7 +221,7 @@ func (cli *Client) BuildPollVote(pollInfo *types.MessageInfo, optionNames []stri
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildPollCreation("meow?", []string{"yes", "no"}, 1))
|
||||
func (cli *Client) BuildPollCreation(name string, optionNames []string, selectableOptionCount int) *waProto.Message {
|
||||
msgSecret := randbytes.Make(32)
|
||||
msgSecret := random.Bytes(32)
|
||||
if selectableOptionCount < 0 || selectableOptionCount > len(optionNames) {
|
||||
selectableOptionCount = 0
|
||||
}
|
||||
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
// Copyright (c) 2023 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
// NewsletterSubscribeLiveUpdates subscribes to receive live updates from a WhatsApp channel temporarily (for the duration returned).
|
||||
func (cli *Client) NewsletterSubscribeLiveUpdates(ctx context.Context, jid types.JID) (time.Duration, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
Namespace: "newsletter",
|
||||
Type: iqSet,
|
||||
To: jid,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "live_updates",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
child := resp.GetChildByTag("live_updates")
|
||||
dur := child.AttrGetter().Int("duration")
|
||||
return time.Duration(dur) * time.Second, nil
|
||||
}
|
||||
|
||||
// NewsletterMarkViewed marks a channel message as viewed, incrementing the view counter.
|
||||
//
|
||||
// This is not the same as marking the channel as read on your other devices, use the usual MarkRead function for that.
|
||||
func (cli *Client) NewsletterMarkViewed(jid types.JID, serverIDs []types.MessageServerID) error {
|
||||
items := make([]waBinary.Node, len(serverIDs))
|
||||
for i, id := range serverIDs {
|
||||
items[i] = waBinary.Node{
|
||||
Tag: "item",
|
||||
Attrs: waBinary.Attrs{
|
||||
"server_id": id,
|
||||
},
|
||||
}
|
||||
}
|
||||
reqID := cli.generateRequestID()
|
||||
resp := cli.waitResponse(reqID)
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: waBinary.Attrs{
|
||||
"to": jid,
|
||||
"type": "view",
|
||||
"id": reqID,
|
||||
},
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "list",
|
||||
Content: items,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
cli.cancelResponse(reqID, resp)
|
||||
return err
|
||||
}
|
||||
// TODO handle response?
|
||||
<-resp
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewsletterSendReaction sends a reaction to a channel message.
|
||||
// To remove a reaction sent earlier, set reaction to an empty string.
|
||||
//
|
||||
// The last parameter is the message ID of the reaction itself. It can be left empty to let whatsmeow generate a random one.
|
||||
func (cli *Client) NewsletterSendReaction(jid types.JID, serverID types.MessageServerID, reaction string, messageID types.MessageID) error {
|
||||
if messageID == "" {
|
||||
messageID = cli.GenerateMessageID()
|
||||
}
|
||||
reactionAttrs := waBinary.Attrs{}
|
||||
messageAttrs := waBinary.Attrs{
|
||||
"to": jid,
|
||||
"id": messageID,
|
||||
"server_id": serverID,
|
||||
"type": "reaction",
|
||||
}
|
||||
if reaction != "" {
|
||||
reactionAttrs["code"] = reaction
|
||||
} else {
|
||||
messageAttrs["edit"] = string(types.EditAttributeSenderRevoke)
|
||||
}
|
||||
return cli.sendNode(waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: messageAttrs,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "reaction",
|
||||
Attrs: reactionAttrs,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
queryFetchNewsletter = "6563316087068696"
|
||||
queryFetchNewsletterDehydrated = "7272540469429201"
|
||||
queryRecommendedNewsletters = "7263823273662354" //variables -> input -> {limit: 20, country_codes: [string]}, output: xwa2_newsletters_recommended
|
||||
queryNewslettersDirectory = "6190824427689257" // variables -> input -> {view: "RECOMMENDED", limit: 50, start_cursor: base64, filters: {country_codes: [string]}}
|
||||
querySubscribedNewsletters = "6388546374527196" // variables -> empty, output: xwa2_newsletter_subscribed
|
||||
queryNewsletterSubscribers = "9800646650009898" //variables -> input -> {newsletter_id, count}, output: xwa2_newsletter_subscribers -> subscribers -> edges
|
||||
mutationMuteNewsletter = "6274038279359549" //variables -> {newsletter_id, updates->{description, settings}}, output: xwa2_newsletter_update -> NewsletterMetadata without viewer meta
|
||||
mutationUnmuteNewsletter = "6068417879924485"
|
||||
mutationUpdateNewsletter = "7150902998257522"
|
||||
mutationCreateNewsletter = "6234210096708695"
|
||||
mutationUnfollowNewsletter = "6392786840836363"
|
||||
mutationFollowNewsletter = "9926858900719341"
|
||||
)
|
||||
|
||||
func (cli *Client) sendMexIQ(ctx context.Context, queryID string, variables any) (json.RawMessage, error) {
|
||||
payload, err := json.Marshal(map[string]any{
|
||||
"variables": variables,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "w:mex",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "query",
|
||||
Attrs: waBinary.Attrs{
|
||||
"query_id": queryID,
|
||||
},
|
||||
Content: payload,
|
||||
}},
|
||||
Context: ctx,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, ok := resp.GetOptionalChildByTag("result")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "result", In: "mex response"}
|
||||
}
|
||||
resultContent, ok := result.Content.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected content type %T in mex response", result.Content)
|
||||
}
|
||||
var gqlResp types.GraphQLResponse
|
||||
err = json.Unmarshal(resultContent, &gqlResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal graphql response: %w", err)
|
||||
} else if len(gqlResp.Errors) > 0 {
|
||||
return gqlResp.Data, fmt.Errorf("graphql error: %w", gqlResp.Errors)
|
||||
}
|
||||
return gqlResp.Data, nil
|
||||
}
|
||||
|
||||
type respGetNewsletterInfo struct {
|
||||
Newsletter *types.NewsletterMetadata `json:"xwa2_newsletter"`
|
||||
}
|
||||
|
||||
func (cli *Client) getNewsletterInfo(input map[string]any, fetchViewerMeta bool) (*types.NewsletterMetadata, error) {
|
||||
data, err := cli.sendMexIQ(context.TODO(), queryFetchNewsletter, map[string]any{
|
||||
"fetch_creation_time": true,
|
||||
"fetch_full_image": true,
|
||||
"fetch_viewer_metadata": fetchViewerMeta,
|
||||
"input": input,
|
||||
})
|
||||
var respData respGetNewsletterInfo
|
||||
if data != nil {
|
||||
jsonErr := json.Unmarshal(data, &respData)
|
||||
if err == nil && jsonErr != nil {
|
||||
err = jsonErr
|
||||
}
|
||||
}
|
||||
return respData.Newsletter, err
|
||||
}
|
||||
|
||||
// GetNewsletterInfo gets the info of a newsletter that you're joined to.
|
||||
func (cli *Client) GetNewsletterInfo(jid types.JID) (*types.NewsletterMetadata, error) {
|
||||
return cli.getNewsletterInfo(map[string]any{
|
||||
"key": jid.String(),
|
||||
"type": types.NewsletterKeyTypeJID,
|
||||
}, true)
|
||||
}
|
||||
|
||||
// GetNewsletterInfoWithInvite gets the info of a newsletter with an invite link.
|
||||
//
|
||||
// You can either pass the full link (https://whatsapp.com/channel/...) or just the `...` part.
|
||||
//
|
||||
// Note that the ViewerMeta field of the returned NewsletterMetadata will be nil.
|
||||
func (cli *Client) GetNewsletterInfoWithInvite(key string) (*types.NewsletterMetadata, error) {
|
||||
return cli.getNewsletterInfo(map[string]any{
|
||||
"key": strings.TrimPrefix(key, NewsletterLinkPrefix),
|
||||
"type": types.NewsletterKeyTypeInvite,
|
||||
}, false)
|
||||
}
|
||||
|
||||
type respGetSubscribedNewsletters struct {
|
||||
Newsletters []*types.NewsletterMetadata `json:"xwa2_newsletter_subscribed"`
|
||||
}
|
||||
|
||||
// GetSubscribedNewsletters gets the info of all newsletters that you're joined to.
|
||||
func (cli *Client) GetSubscribedNewsletters() ([]*types.NewsletterMetadata, error) {
|
||||
data, err := cli.sendMexIQ(context.TODO(), querySubscribedNewsletters, map[string]any{})
|
||||
var respData respGetSubscribedNewsletters
|
||||
if data != nil {
|
||||
jsonErr := json.Unmarshal(data, &respData)
|
||||
if err == nil && jsonErr != nil {
|
||||
err = jsonErr
|
||||
}
|
||||
}
|
||||
return respData.Newsletters, err
|
||||
}
|
||||
|
||||
type CreateNewsletterParams struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Picture []byte `json:"picture,omitempty"`
|
||||
}
|
||||
|
||||
type respCreateNewsletter struct {
|
||||
Newsletter *types.NewsletterMetadata `json:"xwa2_newsletter_create"`
|
||||
}
|
||||
|
||||
// CreateNewsletter creates a new WhatsApp channel.
|
||||
func (cli *Client) CreateNewsletter(params CreateNewsletterParams) (*types.NewsletterMetadata, error) {
|
||||
resp, err := cli.sendMexIQ(context.TODO(), mutationCreateNewsletter, map[string]any{
|
||||
"newsletter_input": ¶ms,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var respData respCreateNewsletter
|
||||
err = json.Unmarshal(resp, &respData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return respData.Newsletter, nil
|
||||
}
|
||||
|
||||
// AcceptTOSNotice accepts a ToS notice.
|
||||
//
|
||||
// To accept the terms for creating newsletters, use
|
||||
//
|
||||
// cli.AcceptTOSNotice("20601218", "5")
|
||||
func (cli *Client) AcceptTOSNotice(noticeID, stage string) error {
|
||||
_, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "tos",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "notice",
|
||||
Attrs: waBinary.Attrs{
|
||||
"id": noticeID,
|
||||
"stage": stage,
|
||||
},
|
||||
}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// NewsletterToggleMute changes the mute status of a newsletter.
|
||||
func (cli *Client) NewsletterToggleMute(jid types.JID, mute bool) error {
|
||||
query := mutationUnmuteNewsletter
|
||||
if mute {
|
||||
query = mutationMuteNewsletter
|
||||
}
|
||||
_, err := cli.sendMexIQ(context.TODO(), query, map[string]any{
|
||||
"newsletter_id": jid.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// FollowNewsletter makes the user follow (join) a WhatsApp channel.
|
||||
func (cli *Client) FollowNewsletter(jid types.JID) error {
|
||||
_, err := cli.sendMexIQ(context.TODO(), mutationFollowNewsletter, map[string]any{
|
||||
"newsletter_id": jid.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// UnfollowNewsletter makes the user unfollow (leave) a WhatsApp channel.
|
||||
func (cli *Client) UnfollowNewsletter(jid types.JID) error {
|
||||
_, err := cli.sendMexIQ(context.TODO(), mutationUnfollowNewsletter, map[string]any{
|
||||
"newsletter_id": jid.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type GetNewsletterMessagesParams struct {
|
||||
Count int
|
||||
Before types.MessageServerID
|
||||
}
|
||||
|
||||
// GetNewsletterMessages gets messages in a WhatsApp channel.
|
||||
func (cli *Client) GetNewsletterMessages(jid types.JID, params *GetNewsletterMessagesParams) ([]*types.NewsletterMessage, error) {
|
||||
attrs := waBinary.Attrs{
|
||||
"type": "jid",
|
||||
"jid": jid,
|
||||
}
|
||||
if params != nil {
|
||||
if params.Count != 0 {
|
||||
attrs["count"] = params.Count
|
||||
}
|
||||
if params.Before != 0 {
|
||||
attrs["before"] = params.Before
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "newsletter",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "messages",
|
||||
Attrs: attrs,
|
||||
}},
|
||||
Context: context.TODO(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages, ok := resp.GetOptionalChildByTag("messages")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "messages", In: "newsletter messages response"}
|
||||
}
|
||||
return cli.parseNewsletterMessages(&messages), nil
|
||||
}
|
||||
|
||||
type GetNewsletterUpdatesParams struct {
|
||||
Count int
|
||||
Since time.Time
|
||||
After types.MessageServerID
|
||||
}
|
||||
|
||||
// GetNewsletterMessageUpdates gets updates in a WhatsApp channel.
|
||||
//
|
||||
// These are the same kind of updates that NewsletterSubscribeLiveUpdates triggers (reaction and view counts).
|
||||
func (cli *Client) GetNewsletterMessageUpdates(jid types.JID, params *GetNewsletterUpdatesParams) ([]*types.NewsletterMessage, error) {
|
||||
attrs := waBinary.Attrs{}
|
||||
if params != nil {
|
||||
if params.Count != 0 {
|
||||
attrs["count"] = params.Count
|
||||
}
|
||||
if !params.Since.IsZero() {
|
||||
attrs["since"] = params.Since.Unix()
|
||||
}
|
||||
if params.After != 0 {
|
||||
attrs["after"] = params.After
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "newsletter",
|
||||
Type: iqGet,
|
||||
To: jid,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "message_updates",
|
||||
Attrs: attrs,
|
||||
}},
|
||||
Context: context.TODO(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messages, ok := resp.GetOptionalChildByTag("message_updates", "messages")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "messages", In: "newsletter messages response"}
|
||||
}
|
||||
return cli.parseNewsletterMessages(&messages), nil
|
||||
}
|
||||
+136
-8
@@ -7,10 +7,14 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/whatsmeow/appstate"
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
@@ -100,7 +104,7 @@ func (cli *Client) handleDeviceNotification(node *waBinary.Node) {
|
||||
cli.Log.Debugf("No device list cached for %s, ignoring device list notification", from)
|
||||
return
|
||||
}
|
||||
cachedParticipantHash := participantListHashV2(cached)
|
||||
cachedParticipantHash := participantListHashV2(cached.devices)
|
||||
for _, child := range node.GetChildren() {
|
||||
if child.Tag != "add" && child.Tag != "remove" {
|
||||
cli.Log.Debugf("Unknown device list change tag %s", child.Tag)
|
||||
@@ -112,17 +116,17 @@ func (cli *Client) handleDeviceNotification(node *waBinary.Node) {
|
||||
changedDeviceJID := deviceChild.AttrGetter().JID("jid")
|
||||
switch child.Tag {
|
||||
case "add":
|
||||
cached = append(cached, changedDeviceJID)
|
||||
cached.devices = append(cached.devices, changedDeviceJID)
|
||||
case "remove":
|
||||
for i, jid := range cached {
|
||||
for i, jid := range cached.devices {
|
||||
if jid == changedDeviceJID {
|
||||
cached = append(cached[:i], cached[i+1:]...)
|
||||
cached.devices = append(cached.devices[:i], cached.devices[i+1:]...)
|
||||
}
|
||||
}
|
||||
case "update":
|
||||
// ???
|
||||
}
|
||||
newParticipantHash := participantListHashV2(cached)
|
||||
newParticipantHash := participantListHashV2(cached.devices)
|
||||
if newParticipantHash == deviceHash {
|
||||
cli.Log.Debugf("%s's device list hash changed from %s to %s (%s). New hash matches", from, cachedParticipantHash, deviceHash, child.Tag)
|
||||
cli.userDevicesCache[from] = cached
|
||||
@@ -133,6 +137,14 @@ func (cli *Client) handleDeviceNotification(node *waBinary.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleFBDeviceNotification(node *waBinary.Node) {
|
||||
cli.userDevicesCacheLock.Lock()
|
||||
defer cli.userDevicesCacheLock.Unlock()
|
||||
jid := node.AttrGetter().JID("from")
|
||||
userDevices := parseFBDeviceList(jid, node.GetChildByTag("devices"))
|
||||
cli.userDevicesCache[jid] = userDevices
|
||||
}
|
||||
|
||||
func (cli *Client) handleOwnDevicesNotification(node *waBinary.Node) {
|
||||
cli.userDevicesCacheLock.Lock()
|
||||
defer cli.userDevicesCacheLock.Unlock()
|
||||
@@ -146,13 +158,12 @@ func (cli *Client) handleOwnDevicesNotification(node *waBinary.Node) {
|
||||
cli.Log.Debugf("Ignoring own device change notification, device list not cached")
|
||||
return
|
||||
}
|
||||
oldHash := participantListHashV2(cached)
|
||||
oldHash := participantListHashV2(cached.devices)
|
||||
expectedNewHash := node.AttrGetter().String("dhash")
|
||||
var newDeviceList []types.JID
|
||||
for _, child := range node.GetChildren() {
|
||||
jid := child.AttrGetter().JID("jid")
|
||||
if child.Tag == "device" && !jid.IsEmpty() {
|
||||
jid.AD = true
|
||||
newDeviceList = append(newDeviceList, jid)
|
||||
}
|
||||
}
|
||||
@@ -162,10 +173,32 @@ func (cli *Client) handleOwnDevicesNotification(node *waBinary.Node) {
|
||||
delete(cli.userDevicesCache, ownID)
|
||||
} else {
|
||||
cli.Log.Debugf("Received own device list change notification %s -> %s", oldHash, newHash)
|
||||
cli.userDevicesCache[ownID] = newDeviceList
|
||||
cli.userDevicesCache[ownID] = deviceCache{devices: newDeviceList, dhash: expectedNewHash}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleBlocklist(node *waBinary.Node) {
|
||||
ag := node.AttrGetter()
|
||||
evt := events.Blocklist{
|
||||
Action: events.BlocklistAction(ag.OptionalString("action")),
|
||||
DHash: ag.String("dhash"),
|
||||
PrevDHash: ag.OptionalString("prev_dhash"),
|
||||
}
|
||||
for _, child := range node.GetChildren() {
|
||||
ag := child.AttrGetter()
|
||||
change := events.BlocklistChange{
|
||||
JID: ag.JID("jid"),
|
||||
Action: events.BlocklistChangeAction(ag.String("action")),
|
||||
}
|
||||
if !ag.OK() {
|
||||
cli.Log.Warnf("Unexpected data in blocklist event child %v: %v", child.XMLString(), ag.Error())
|
||||
continue
|
||||
}
|
||||
evt.Changes = append(evt.Changes, change)
|
||||
}
|
||||
cli.dispatchEvent(&evt)
|
||||
}
|
||||
|
||||
func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) {
|
||||
for _, child := range node.GetChildren() {
|
||||
switch child.Tag {
|
||||
@@ -178,6 +211,8 @@ func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) {
|
||||
Timestamp: node.AttrGetter().UnixTime("t"),
|
||||
JID: cli.getOwnID().ToNonAD(),
|
||||
})
|
||||
case "blocklist":
|
||||
cli.handleBlocklist(&child)
|
||||
default:
|
||||
cli.Log.Debugf("Unhandled account sync item %s", child.Tag)
|
||||
}
|
||||
@@ -230,6 +265,93 @@ func (cli *Client) handlePrivacyTokenNotification(node *waBinary.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) parseNewsletterMessages(node *waBinary.Node) []*types.NewsletterMessage {
|
||||
children := node.GetChildren()
|
||||
output := make([]*types.NewsletterMessage, 0, len(children))
|
||||
for _, child := range children {
|
||||
if child.Tag != "message" {
|
||||
continue
|
||||
}
|
||||
msg := types.NewsletterMessage{
|
||||
MessageServerID: child.AttrGetter().Int("server_id"),
|
||||
ViewsCount: 0,
|
||||
ReactionCounts: nil,
|
||||
}
|
||||
for _, subchild := range child.GetChildren() {
|
||||
switch subchild.Tag {
|
||||
case "plaintext":
|
||||
byteContent, ok := subchild.Content.([]byte)
|
||||
if ok {
|
||||
msg.Message = new(waProto.Message)
|
||||
err := proto.Unmarshal(byteContent, msg.Message)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to unmarshal newsletter message: %v", err)
|
||||
msg.Message = nil
|
||||
}
|
||||
}
|
||||
case "views_count":
|
||||
msg.ViewsCount = subchild.AttrGetter().Int("count")
|
||||
case "reactions":
|
||||
msg.ReactionCounts = make(map[string]int)
|
||||
for _, reaction := range subchild.GetChildren() {
|
||||
rag := reaction.AttrGetter()
|
||||
msg.ReactionCounts[rag.String("code")] = rag.Int("count")
|
||||
}
|
||||
}
|
||||
}
|
||||
output = append(output, &msg)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func (cli *Client) handleNewsletterNotification(node *waBinary.Node) {
|
||||
ag := node.AttrGetter()
|
||||
liveUpdates := node.GetChildByTag("live_updates")
|
||||
cli.dispatchEvent(&events.NewsletterLiveUpdate{
|
||||
JID: ag.JID("from"),
|
||||
Time: ag.UnixTime("t"),
|
||||
Messages: cli.parseNewsletterMessages(&liveUpdates),
|
||||
})
|
||||
}
|
||||
|
||||
type newsLetterEventWrapper struct {
|
||||
Data newsletterEvent `json:"data"`
|
||||
}
|
||||
|
||||
type newsletterEvent struct {
|
||||
Join *events.NewsletterJoin `json:"xwa2_notify_newsletter_on_join"`
|
||||
Leave *events.NewsletterLeave `json:"xwa2_notify_newsletter_on_leave"`
|
||||
MuteChange *events.NewsletterMuteChange `json:"xwa2_notify_newsletter_on_mute_change"`
|
||||
// _on_admin_metadata_update -> id, thread_metadata, messages
|
||||
// _on_metadata_update
|
||||
// _on_state_change -> id, is_requestor, state
|
||||
}
|
||||
|
||||
func (cli *Client) handleMexNotification(node *waBinary.Node) {
|
||||
for _, child := range node.GetChildren() {
|
||||
if child.Tag != "update" {
|
||||
continue
|
||||
}
|
||||
childData, ok := child.Content.([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var wrapper newsLetterEventWrapper
|
||||
err := json.Unmarshal(childData, &wrapper)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to unmarshal JSON in mex event: %v", err)
|
||||
continue
|
||||
}
|
||||
if wrapper.Data.Join != nil {
|
||||
cli.dispatchEvent(wrapper.Data.Join)
|
||||
} else if wrapper.Data.Leave != nil {
|
||||
cli.dispatchEvent(wrapper.Data.Leave)
|
||||
} else if wrapper.Data.MuteChange != nil {
|
||||
cli.dispatchEvent(wrapper.Data.MuteChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleNotification(node *waBinary.Node) {
|
||||
ag := node.AttrGetter()
|
||||
notifType := ag.String("type")
|
||||
@@ -246,6 +368,8 @@ func (cli *Client) handleNotification(node *waBinary.Node) {
|
||||
go cli.handleAccountSyncNotification(node)
|
||||
case "devices":
|
||||
go cli.handleDeviceNotification(node)
|
||||
case "fbid:devices":
|
||||
go cli.handleFBDeviceNotification(node)
|
||||
case "w:gp2":
|
||||
evt, err := cli.parseGroupNotification(node)
|
||||
if err != nil {
|
||||
@@ -261,6 +385,10 @@ func (cli *Client) handleNotification(node *waBinary.Node) {
|
||||
go cli.handlePrivacyTokenNotification(node)
|
||||
case "link_code_companion_reg":
|
||||
go cli.tryHandleCodePairNotification(node)
|
||||
case "newsletter":
|
||||
go cli.handleNewsletterNotification(node)
|
||||
case "mex":
|
||||
go cli.handleMexNotification(node)
|
||||
// Other types: business, disappearing_mode, server, status, pay, psa
|
||||
default:
|
||||
cli.Log.Debugf("Unhandled notification with type %s", notifType)
|
||||
|
||||
+15
-36
@@ -15,16 +15,14 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"go.mau.fi/util/random"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/util/hkdfutil"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// PairClientType is the type of client to use with PairCode.
|
||||
@@ -44,29 +42,6 @@ const (
|
||||
PairClientOtherWebClient
|
||||
)
|
||||
|
||||
func platformTypeToPairClientType(platformType waProto.DeviceProps_PlatformType) PairClientType {
|
||||
switch platformType {
|
||||
case waProto.DeviceProps_CHROME:
|
||||
return PairClientChrome
|
||||
case waProto.DeviceProps_EDGE:
|
||||
return PairClientEdge
|
||||
case waProto.DeviceProps_FIREFOX:
|
||||
return PairClientFirefox
|
||||
case waProto.DeviceProps_IE:
|
||||
return PairClientIE
|
||||
case waProto.DeviceProps_OPERA:
|
||||
return PairClientOpera
|
||||
case waProto.DeviceProps_SAFARI:
|
||||
return PairClientSafari
|
||||
case waProto.DeviceProps_DESKTOP:
|
||||
return PairClientElectron
|
||||
case waProto.DeviceProps_UWP:
|
||||
return PairClientUWP
|
||||
default:
|
||||
return PairClientOtherWebClient
|
||||
}
|
||||
}
|
||||
|
||||
var notNumbers = regexp.MustCompile("[^0-9]")
|
||||
var linkingBase32 = base32.NewEncoding("123456789ABCDEFGHJKLMNPQRSTVWXYZ")
|
||||
|
||||
@@ -79,9 +54,9 @@ type phoneLinkingCache struct {
|
||||
|
||||
func generateCompanionEphemeralKey() (ephemeralKeyPair *keys.KeyPair, ephemeralKey []byte, encodedLinkingCode string) {
|
||||
ephemeralKeyPair = keys.NewKeyPair()
|
||||
salt := randbytes.Make(32)
|
||||
iv := randbytes.Make(16)
|
||||
linkingCode := randbytes.Make(5)
|
||||
salt := random.Bytes(32)
|
||||
iv := random.Bytes(16)
|
||||
linkingCode := random.Bytes(5)
|
||||
encodedLinkingCode = linkingBase32.EncodeToString(linkingCode)
|
||||
linkCodeKey := pbkdf2.Key([]byte(encodedLinkingCode), salt, 2<<16, 32, sha256.New)
|
||||
linkCipherBlock, _ := aes.NewCipher(linkCodeKey)
|
||||
@@ -96,15 +71,19 @@ func generateCompanionEphemeralKey() (ephemeralKeyPair *keys.KeyPair, ephemeralK
|
||||
|
||||
// PairPhone generates a pairing code that can be used to link to a phone without scanning a QR code.
|
||||
//
|
||||
// You must connect the client normally before calling this (which means you'll also receive a QR code
|
||||
// event, but that can be ignored when doing code pairing).
|
||||
//
|
||||
// The exact expiry of pairing codes is unknown, but QR codes are always generated and the login websocket is closed
|
||||
// after the QR codes run out, which means there's a 160-second time limit. It is recommended to generate the pairing
|
||||
// code immediately after connecting to the websocket to have the maximum time.
|
||||
//
|
||||
// The clientType parameter must be one of the PairClient* constants, but which one doesn't matter.
|
||||
// The client display name must be formatted as `Browser (OS)`, and only common browsers/OSes are allowed
|
||||
// (the server will validate it and return 400 if it's wrong).
|
||||
//
|
||||
// See https://faq.whatsapp.com/1324084875126592 for more info
|
||||
func (cli *Client) PairPhone(phone string, showPushNotification bool) (string, error) {
|
||||
clientType := platformTypeToPairClientType(store.DeviceProps.GetPlatformType())
|
||||
clientDisplayName := store.DeviceProps.GetOs()
|
||||
|
||||
func (cli *Client) PairPhone(phone string, showPushNotification bool, clientType PairClientType, clientDisplayName string) (string, error) {
|
||||
ephemeralKeyPair, ephemeralKey, encodedLinkingCode := generateCompanionEphemeralKey()
|
||||
phone = notNumbers.ReplaceAllString(phone, "")
|
||||
jid := types.NewJID(phone, types.DefaultUserServer)
|
||||
@@ -187,9 +166,9 @@ func (cli *Client) handleCodePairNotification(parentNode *waBinary.Node) error {
|
||||
}
|
||||
}
|
||||
|
||||
advSecretRandom := randbytes.Make(32)
|
||||
keyBundleSalt := randbytes.Make(32)
|
||||
keyBundleNonce := randbytes.Make(12)
|
||||
advSecretRandom := random.Bytes(32)
|
||||
keyBundleSalt := random.Bytes(32)
|
||||
keyBundleNonce := random.Bytes(12)
|
||||
|
||||
// Decrypt the primary device's ephemeral public key, which was encrypted with the 8-character pairing code,
|
||||
// then compute the DH shared secret using our ephemeral private key we generated earlier.
|
||||
|
||||
-1
@@ -125,7 +125,6 @@ func (cli *Client) fetchPreKeys(ctx context.Context, users []types.JID) (map[typ
|
||||
continue
|
||||
}
|
||||
jid := child.AttrGetter().JID("jid")
|
||||
jid.AD = true
|
||||
bundle, err := nodeToPreKeyBundle(uint32(jid.Device), child)
|
||||
respData[jid] = preKeyResp{bundle, err}
|
||||
}
|
||||
|
||||
+2
-3
@@ -8,7 +8,6 @@ package whatsmeow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
@@ -66,9 +65,9 @@ func (cli *Client) SendPresence(state types.Presence) error {
|
||||
return ErrNoPushName
|
||||
}
|
||||
if state == types.PresenceAvailable {
|
||||
atomic.CompareAndSwapUint32(&cli.sendActiveReceipts, 0, 1)
|
||||
cli.sendActiveReceipts.CompareAndSwap(0, 1)
|
||||
} else {
|
||||
atomic.CompareAndSwapUint32(&cli.sendActiveReceipts, 1, 0)
|
||||
cli.sendActiveReceipts.CompareAndSwap(1, 0)
|
||||
}
|
||||
return cli.sendNode(waBinary.Node{
|
||||
Tag: "presence",
|
||||
|
||||
+82
-6
@@ -7,6 +7,9 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
@@ -39,6 +42,9 @@ func (cli *Client) TryFetchPrivacySettings(ignoreCache bool) (*types.PrivacySett
|
||||
// GetPrivacySettings will get the user's privacy settings. If an error occurs while fetching them, the error will be
|
||||
// logged, but the method will just return an empty struct.
|
||||
func (cli *Client) GetPrivacySettings() (settings types.PrivacySettings) {
|
||||
if cli.MessengerConfig != nil {
|
||||
return
|
||||
}
|
||||
settingsPtr, err := cli.TryFetchPrivacySettings(false)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to fetch privacy settings: %v", err)
|
||||
@@ -48,6 +54,69 @@ func (cli *Client) GetPrivacySettings() (settings types.PrivacySettings) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetPrivacySetting will set the given privacy setting to the given value.
|
||||
// The privacy settings will be fetched from the server after the change and the new settings will be returned.
|
||||
// If an error occurs while fetching the new settings, will return an empty struct.
|
||||
func (cli *Client) SetPrivacySetting(name types.PrivacySettingType, value types.PrivacySetting) (settings types.PrivacySettings, err error) {
|
||||
settingsPtr, err := cli.TryFetchPrivacySettings(false)
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
Namespace: "privacy",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "privacy",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "category",
|
||||
Attrs: waBinary.Attrs{
|
||||
"name": string(name),
|
||||
"value": string(value),
|
||||
},
|
||||
}},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
settings = *settingsPtr
|
||||
switch name {
|
||||
case types.PrivacySettingTypeGroupAdd:
|
||||
settings.GroupAdd = value
|
||||
case types.PrivacySettingTypeLastSeen:
|
||||
settings.LastSeen = value
|
||||
case types.PrivacySettingTypeStatus:
|
||||
settings.Status = value
|
||||
case types.PrivacySettingTypeProfile:
|
||||
settings.Profile = value
|
||||
case types.PrivacySettingTypeReadReceipts:
|
||||
settings.ReadReceipts = value
|
||||
case types.PrivacySettingTypeOnline:
|
||||
settings.Online = value
|
||||
case types.PrivacySettingTypeCallAdd:
|
||||
settings.CallAdd = value
|
||||
}
|
||||
cli.privacySettingsCache.Store(&settings)
|
||||
return
|
||||
}
|
||||
|
||||
// SetDefaultDisappearingTimer will set the default disappearing message timer.
|
||||
func (cli *Client) SetDefaultDisappearingTimer(timer time.Duration) (err error) {
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
Namespace: "disappearing_mode",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "disappearing_mode",
|
||||
Attrs: waBinary.Attrs{
|
||||
"duration": strconv.Itoa(int(timer.Seconds())),
|
||||
},
|
||||
}},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) parsePrivacySettings(privacyNode *waBinary.Node, settings *types.PrivacySettings) *events.PrivacySettings {
|
||||
var evt events.PrivacySettings
|
||||
for _, child := range privacyNode.GetChildren() {
|
||||
@@ -55,24 +124,30 @@ func (cli *Client) parsePrivacySettings(privacyNode *waBinary.Node, settings *ty
|
||||
continue
|
||||
}
|
||||
ag := child.AttrGetter()
|
||||
name := ag.String("name")
|
||||
name := types.PrivacySettingType(ag.String("name"))
|
||||
value := types.PrivacySetting(ag.String("value"))
|
||||
switch name {
|
||||
case "groupadd":
|
||||
case types.PrivacySettingTypeGroupAdd:
|
||||
settings.GroupAdd = value
|
||||
evt.GroupAddChanged = true
|
||||
case "last":
|
||||
case types.PrivacySettingTypeLastSeen:
|
||||
settings.LastSeen = value
|
||||
evt.LastSeenChanged = true
|
||||
case "status":
|
||||
case types.PrivacySettingTypeStatus:
|
||||
settings.Status = value
|
||||
evt.StatusChanged = true
|
||||
case "profile":
|
||||
case types.PrivacySettingTypeProfile:
|
||||
settings.Profile = value
|
||||
evt.ProfileChanged = true
|
||||
case "readreceipts":
|
||||
case types.PrivacySettingTypeReadReceipts:
|
||||
settings.ReadReceipts = value
|
||||
evt.ReadReceiptsChanged = true
|
||||
case types.PrivacySettingTypeOnline:
|
||||
settings.Online = value
|
||||
evt.OnlineChanged = true
|
||||
case types.PrivacySettingTypeCallAdd:
|
||||
settings.CallAdd = value
|
||||
evt.CallAddChanged = true
|
||||
}
|
||||
}
|
||||
return &evt
|
||||
@@ -83,6 +158,7 @@ func (cli *Client) handlePrivacySettingsNotification(privacyNode *waBinary.Node)
|
||||
settings, err := cli.TryFetchPrivacySettings(false)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to fetch privacy settings when handling change: %v", err)
|
||||
return
|
||||
}
|
||||
evt := cli.parsePrivacySettings(privacyNode, settings)
|
||||
// The data isn't be reliable if the fetch failed, so only cache if it didn't fail
|
||||
|
||||
+28
-13
@@ -8,7 +8,6 @@ package whatsmeow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
@@ -21,7 +20,7 @@ func (cli *Client) handleReceipt(node *waBinary.Node) {
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to parse receipt: %v", err)
|
||||
} else if receipt != nil {
|
||||
if receipt.Type == events.ReceiptTypeRetry {
|
||||
if receipt.Type == types.ReceiptTypeRetry {
|
||||
go func() {
|
||||
err := cli.handleRetryReceipt(receipt, node)
|
||||
if err != nil {
|
||||
@@ -63,7 +62,7 @@ func (cli *Client) parseReceipt(node *waBinary.Node) (*events.Receipt, error) {
|
||||
receipt := events.Receipt{
|
||||
MessageSource: source,
|
||||
Timestamp: ag.UnixTime("t"),
|
||||
Type: events.ReceiptType(ag.OptionalString("type")),
|
||||
Type: types.ReceiptType(ag.OptionalString("type")),
|
||||
}
|
||||
if source.IsGroup && source.Sender.IsEmpty() {
|
||||
participantTags := node.GetChildrenByTag("participants")
|
||||
@@ -127,20 +126,36 @@ func (cli *Client) sendAck(node *waBinary.Node) {
|
||||
//
|
||||
// You can mark multiple messages as read at the same time, but only if the messages were sent by the same user.
|
||||
// To mark messages by different users as read, you must call MarkRead multiple times (once for each user).
|
||||
func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, sender types.JID) error {
|
||||
//
|
||||
// To mark a voice message as played, specify types.ReceiptTypePlayed as the last parameter.
|
||||
// Providing more than one receipt type will panic: the parameter is only a vararg for backwards compatibility.
|
||||
func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, sender types.JID, receiptTypeExtra ...types.ReceiptType) error {
|
||||
if len(ids) == 0 {
|
||||
return fmt.Errorf("no message IDs specified")
|
||||
}
|
||||
receiptType := types.ReceiptTypeRead
|
||||
if len(receiptTypeExtra) == 1 {
|
||||
receiptType = receiptTypeExtra[0]
|
||||
} else if len(receiptTypeExtra) > 1 {
|
||||
panic(fmt.Errorf("too many receipt types specified"))
|
||||
}
|
||||
node := waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: waBinary.Attrs{
|
||||
"id": ids[0],
|
||||
"type": "read",
|
||||
"type": string(receiptType),
|
||||
"to": chat,
|
||||
"t": timestamp.Unix(),
|
||||
},
|
||||
}
|
||||
if cli.GetPrivacySettings().ReadReceipts == types.PrivacySettingNone {
|
||||
node.Attrs["type"] = "read-self"
|
||||
if chat.Server == types.NewsletterServer || cli.GetPrivacySettings().ReadReceipts == types.PrivacySettingNone {
|
||||
switch receiptType {
|
||||
case types.ReceiptTypeRead:
|
||||
node.Attrs["type"] = string(types.ReceiptTypeReadSelf)
|
||||
// TODO change played to played-self?
|
||||
}
|
||||
}
|
||||
if !sender.IsEmpty() && chat.Server != types.DefaultUserServer {
|
||||
if !sender.IsEmpty() && chat.Server != types.DefaultUserServer && chat.Server != types.MessengerServer {
|
||||
node.Attrs["participant"] = sender.ToNonAD()
|
||||
}
|
||||
if len(ids) > 1 {
|
||||
@@ -174,9 +189,9 @@ func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, se
|
||||
// receipts will act like the client is offline until SendPresence is called again.
|
||||
func (cli *Client) SetForceActiveDeliveryReceipts(active bool) {
|
||||
if active {
|
||||
atomic.StoreUint32(&cli.sendActiveReceipts, 2)
|
||||
cli.sendActiveReceipts.Store(2)
|
||||
} else {
|
||||
atomic.StoreUint32(&cli.sendActiveReceipts, 0)
|
||||
cli.sendActiveReceipts.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,9 +200,9 @@ func (cli *Client) sendMessageReceipt(info *types.MessageInfo) {
|
||||
"id": info.ID,
|
||||
}
|
||||
if info.IsFromMe {
|
||||
attrs["type"] = "sender"
|
||||
} else if atomic.LoadUint32(&cli.sendActiveReceipts) == 0 {
|
||||
attrs["type"] = "inactive"
|
||||
attrs["type"] = string(types.ReceiptTypeSender)
|
||||
} else if cli.sendActiveReceipts.Load() == 0 {
|
||||
attrs["type"] = string(types.ReceiptTypeInactive)
|
||||
}
|
||||
attrs["to"] = info.Chat
|
||||
if info.IsGroup {
|
||||
|
||||
+4
-3
@@ -10,7 +10,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
@@ -18,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
func (cli *Client) generateRequestID() string {
|
||||
return cli.uniqueID + strconv.FormatUint(uint64(atomic.AddUint32(&cli.idCounter, 1)), 10)
|
||||
return cli.uniqueID + strconv.FormatUint(cli.idCounter.Add(1), 10)
|
||||
}
|
||||
|
||||
var xmlStreamEndNode = &waBinary.Node{Tag: "xmlstreamend"}
|
||||
@@ -139,13 +138,15 @@ func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) {
|
||||
return ch, err
|
||||
}
|
||||
|
||||
const defaultRequestTimeout = 75 * time.Second
|
||||
|
||||
func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
|
||||
resChan, data, err := cli.sendIQAsyncAndGetData(&query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if query.Timeout == 0 {
|
||||
query.Timeout = 75 * time.Second
|
||||
query.Timeout = defaultRequestTimeout
|
||||
}
|
||||
if query.Context == nil {
|
||||
query.Context = context.Background()
|
||||
|
||||
Vendored
+125
-35
@@ -8,6 +8,8 @@ package whatsmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -19,6 +21,10 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waConsumerApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgTransport"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
@@ -32,19 +38,22 @@ type recentMessageKey struct {
|
||||
ID types.MessageID
|
||||
}
|
||||
|
||||
// RecentMessage contains the info needed to re-send a message when another device fails to decrypt it.
|
||||
type RecentMessage struct {
|
||||
Proto *waProto.Message
|
||||
Timestamp time.Time
|
||||
wa *waProto.Message
|
||||
fb *waMsgApplication.MessageApplication
|
||||
}
|
||||
|
||||
func (cli *Client) addRecentMessage(to types.JID, id types.MessageID, message *waProto.Message) {
|
||||
func (rm RecentMessage) IsEmpty() bool {
|
||||
return rm.wa == nil && rm.fb == nil
|
||||
}
|
||||
|
||||
func (cli *Client) addRecentMessage(to types.JID, id types.MessageID, wa *waProto.Message, fb *waMsgApplication.MessageApplication) {
|
||||
cli.recentMessagesLock.Lock()
|
||||
key := recentMessageKey{to, id}
|
||||
if cli.recentMessagesList[cli.recentMessagesPtr].ID != "" {
|
||||
delete(cli.recentMessagesMap, cli.recentMessagesList[cli.recentMessagesPtr])
|
||||
}
|
||||
cli.recentMessagesMap[key] = message
|
||||
cli.recentMessagesMap[key] = RecentMessage{wa: wa, fb: fb}
|
||||
cli.recentMessagesList[cli.recentMessagesPtr] = key
|
||||
cli.recentMessagesPtr++
|
||||
if cli.recentMessagesPtr >= len(cli.recentMessagesList) {
|
||||
@@ -53,26 +62,27 @@ func (cli *Client) addRecentMessage(to types.JID, id types.MessageID, message *w
|
||||
cli.recentMessagesLock.Unlock()
|
||||
}
|
||||
|
||||
func (cli *Client) getRecentMessage(to types.JID, id types.MessageID) *waProto.Message {
|
||||
func (cli *Client) getRecentMessage(to types.JID, id types.MessageID) RecentMessage {
|
||||
cli.recentMessagesLock.RLock()
|
||||
msg, _ := cli.recentMessagesMap[recentMessageKey{to, id}]
|
||||
cli.recentMessagesLock.RUnlock()
|
||||
return msg
|
||||
}
|
||||
|
||||
func (cli *Client) getMessageForRetry(receipt *events.Receipt, messageID types.MessageID) (*waProto.Message, error) {
|
||||
func (cli *Client) getMessageForRetry(receipt *events.Receipt, messageID types.MessageID) (RecentMessage, error) {
|
||||
msg := cli.getRecentMessage(receipt.Chat, messageID)
|
||||
if msg == nil {
|
||||
msg = cli.GetMessageForRetry(receipt.Sender, receipt.Chat, messageID)
|
||||
if msg == nil {
|
||||
return nil, fmt.Errorf("couldn't find message %s", messageID)
|
||||
if msg.IsEmpty() {
|
||||
waMsg := cli.GetMessageForRetry(receipt.Sender, receipt.Chat, messageID)
|
||||
if waMsg == nil {
|
||||
return RecentMessage{}, fmt.Errorf("couldn't find message %s", messageID)
|
||||
} else {
|
||||
cli.Log.Debugf("Found message in GetMessageForRetry to accept retry receipt for %s/%s from %s", receipt.Chat, messageID, receipt.Sender)
|
||||
}
|
||||
msg = RecentMessage{wa: waMsg}
|
||||
} else {
|
||||
cli.Log.Debugf("Found message in local cache to accept retry receipt for %s/%s from %s", receipt.Chat, messageID, receipt.Sender)
|
||||
}
|
||||
return proto.Clone(msg).(*waProto.Message), nil
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
const recreateSessionTimeout = 1 * time.Hour
|
||||
@@ -94,6 +104,11 @@ func (cli *Client) shouldRecreateSession(retryCount int, jid types.JID) (reason
|
||||
return "", false
|
||||
}
|
||||
|
||||
type incomingRetryKey struct {
|
||||
jid types.JID
|
||||
messageID types.MessageID
|
||||
}
|
||||
|
||||
// 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")
|
||||
@@ -111,40 +126,87 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var fbConsumerMsg *waConsumerApplication.ConsumerApplication
|
||||
if msg.fb != nil {
|
||||
subProto, ok := msg.fb.GetPayload().GetSubProtocol().GetSubProtocol().(*waMsgApplication.MessageApplication_SubProtocolPayload_ConsumerMessage)
|
||||
if ok {
|
||||
fbConsumerMsg, err = subProto.Decode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode consumer message for retry: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retryKey := incomingRetryKey{receipt.Sender, messageID}
|
||||
cli.incomingRetryRequestCounterLock.Lock()
|
||||
cli.incomingRetryRequestCounter[retryKey]++
|
||||
internalCounter := cli.incomingRetryRequestCounter[retryKey]
|
||||
cli.incomingRetryRequestCounterLock.Unlock()
|
||||
if internalCounter >= 10 {
|
||||
cli.Log.Warnf("Dropping retry request from %s for %s: internal retry counter is %d", messageID, receipt.Sender, internalCounter)
|
||||
return nil
|
||||
}
|
||||
|
||||
ownID := cli.getOwnID()
|
||||
if ownID.IsEmpty() {
|
||||
return ErrNotLoggedIn
|
||||
}
|
||||
|
||||
var fbSKDM *waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage
|
||||
var fbDSM *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage
|
||||
if receipt.IsGroup {
|
||||
builder := groups.NewGroupSessionBuilder(cli.Store, pbSerializer)
|
||||
senderKeyName := protocol.NewSenderKeyName(receipt.Chat.String(), ownID.SignalAddress())
|
||||
signalSKDMessage, err := builder.Create(senderKeyName)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to create sender key distribution message to include in retry of %s in %s to %s: %v", messageID, receipt.Chat, receipt.Sender, err)
|
||||
} else {
|
||||
msg.SenderKeyDistributionMessage = &waProto.SenderKeyDistributionMessage{
|
||||
}
|
||||
if msg.wa != nil {
|
||||
msg.wa.SenderKeyDistributionMessage = &waProto.SenderKeyDistributionMessage{
|
||||
GroupId: proto.String(receipt.Chat.String()),
|
||||
AxolotlSenderKeyDistributionMessage: signalSKDMessage.Serialize(),
|
||||
}
|
||||
} else {
|
||||
fbSKDM = &waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage{
|
||||
GroupID: receipt.Chat.String(),
|
||||
AxolotlSenderKeyDistributionMessage: signalSKDMessage.Serialize(),
|
||||
}
|
||||
}
|
||||
} else if receipt.IsFromMe {
|
||||
msg = &waProto.Message{
|
||||
DeviceSentMessage: &waProto.DeviceSentMessage{
|
||||
DestinationJid: proto.String(receipt.Chat.String()),
|
||||
Message: msg,
|
||||
},
|
||||
if msg.wa != nil {
|
||||
msg.wa = &waProto.Message{
|
||||
DeviceSentMessage: &waProto.DeviceSentMessage{
|
||||
DestinationJid: proto.String(receipt.Chat.String()),
|
||||
Message: msg.wa,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
fbDSM = &waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage{
|
||||
DestinationJID: receipt.Chat.String(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cli.PreRetryCallback != nil && !cli.PreRetryCallback(receipt, messageID, retryCount, msg) {
|
||||
// TODO pre-retry callback for fb
|
||||
if cli.PreRetryCallback != nil && !cli.PreRetryCallback(receipt, messageID, retryCount, msg.wa) {
|
||||
cli.Log.Debugf("Cancelled retry receipt in PreRetryCallback")
|
||||
return nil
|
||||
}
|
||||
|
||||
plaintext, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal message: %w", err)
|
||||
var plaintext, frankingTag []byte
|
||||
if msg.wa != nil {
|
||||
plaintext, err = proto.Marshal(msg.wa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal message: %w", err)
|
||||
}
|
||||
} else {
|
||||
plaintext, err = proto.Marshal(msg.fb)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal consumer message: %w", err)
|
||||
}
|
||||
frankingHash := hmac.New(sha256.New, msg.fb.GetMetadata().GetFrankingKey())
|
||||
frankingHash.Write(plaintext)
|
||||
frankingTag = frankingHash.Sum(nil)
|
||||
}
|
||||
_, hasKeys := node.GetOptionalChildByTag("keys")
|
||||
var bundle *prekey.Bundle
|
||||
@@ -160,20 +222,39 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
senderAD := receipt.Sender
|
||||
senderAD.AD = true
|
||||
bundle, err = keys[senderAD].bundle, keys[senderAD].err
|
||||
bundle, err = keys[receipt.Sender].bundle, keys[receipt.Sender].err
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch prekeys: %w", err)
|
||||
} else if bundle == nil {
|
||||
return fmt.Errorf("didn't get prekey bundle for %s (response size: %d)", senderAD, len(keys))
|
||||
return fmt.Errorf("didn't get prekey bundle for %s (response size: %d)", receipt.Sender, len(keys))
|
||||
}
|
||||
}
|
||||
encAttrs := waBinary.Attrs{}
|
||||
if mediaType := getMediaTypeFromMessage(msg); mediaType != "" {
|
||||
encAttrs["mediatype"] = mediaType
|
||||
var msgAttrs messageAttrs
|
||||
if msg.wa != nil {
|
||||
msgAttrs.MediaType = getMediaTypeFromMessage(msg.wa)
|
||||
msgAttrs.Type = getTypeFromMessage(msg.wa)
|
||||
} else if fbConsumerMsg != nil {
|
||||
msgAttrs = getAttrsFromFBMessage(fbConsumerMsg)
|
||||
} else {
|
||||
msgAttrs.Type = "text"
|
||||
}
|
||||
if msgAttrs.MediaType != "" {
|
||||
encAttrs["mediatype"] = msgAttrs.MediaType
|
||||
}
|
||||
var encrypted *waBinary.Node
|
||||
var includeDeviceIdentity bool
|
||||
if msg.wa != nil {
|
||||
encrypted, includeDeviceIdentity, err = cli.encryptMessageForDevice(plaintext, receipt.Sender, bundle, encAttrs)
|
||||
} else {
|
||||
encrypted, err = cli.encryptMessageForDeviceV3(&waMsgTransport.MessageTransport_Payload{
|
||||
ApplicationPayload: &waCommon.SubProtocol{
|
||||
Payload: plaintext,
|
||||
Version: FBMessageApplicationVersion,
|
||||
},
|
||||
FutureProof: waCommon.FutureProofBehavior_PLACEHOLDER,
|
||||
}, fbSKDM, fbDSM, receipt.Sender, bundle, encAttrs)
|
||||
}
|
||||
encrypted, includeDeviceIdentity, err := cli.encryptMessageForDevice(plaintext, receipt.Sender, bundle, encAttrs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt message for retry: %w", err)
|
||||
}
|
||||
@@ -181,7 +262,7 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
|
||||
attrs := waBinary.Attrs{
|
||||
"to": node.Attrs["from"],
|
||||
"type": getTypeFromMessage(msg),
|
||||
"type": msgAttrs.Type,
|
||||
"id": messageID,
|
||||
"t": timestamp.Unix(),
|
||||
}
|
||||
@@ -197,10 +278,19 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
if edit, ok := node.Attrs["edit"]; ok {
|
||||
attrs["edit"] = edit
|
||||
}
|
||||
var content []waBinary.Node
|
||||
if msg.wa != nil {
|
||||
content = cli.getMessageContent(*encrypted, msg.wa, attrs, includeDeviceIdentity)
|
||||
} else {
|
||||
content = []waBinary.Node{
|
||||
*encrypted,
|
||||
{Tag: "franking", Content: []waBinary.Node{{Tag: "franking_tag", Content: frankingTag}}},
|
||||
}
|
||||
}
|
||||
err = cli.sendNode(waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
Content: cli.getMessageContent(*encrypted, msg, attrs, includeDeviceIdentity),
|
||||
Content: content,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send retry message: %w", err)
|
||||
@@ -210,7 +300,7 @@ func (cli *Client) handleRetryReceipt(receipt *events.Receipt, node *waBinary.No
|
||||
}
|
||||
|
||||
func (cli *Client) cancelDelayedRequestFromPhone(msgID types.MessageID) {
|
||||
if !cli.AutomaticMessageRerequestFromPhone {
|
||||
if !cli.AutomaticMessageRerequestFromPhone || cli.MessengerConfig != nil {
|
||||
return
|
||||
}
|
||||
cli.pendingPhoneRerequestsLock.RLock()
|
||||
@@ -226,7 +316,7 @@ func (cli *Client) cancelDelayedRequestFromPhone(msgID types.MessageID) {
|
||||
var RequestFromPhoneDelay = 5 * time.Second
|
||||
|
||||
func (cli *Client) delayedRequestMessageFromPhone(info *types.MessageInfo) {
|
||||
if !cli.AutomaticMessageRerequestFromPhone {
|
||||
if !cli.AutomaticMessageRerequestFromPhone || cli.MessengerConfig != nil {
|
||||
return
|
||||
}
|
||||
cli.pendingPhoneRerequestsLock.Lock()
|
||||
@@ -253,7 +343,7 @@ func (cli *Client) delayedRequestMessageFromPhone(info *types.MessageInfo) {
|
||||
}
|
||||
_, err := cli.SendMessage(
|
||||
ctx,
|
||||
cli.Store.ID.ToNonAD(),
|
||||
cli.getOwnID().ToNonAD(),
|
||||
cli.BuildUnavailableMessageRequest(info.Chat, info.Sender, info.ID),
|
||||
SendRequestExtra{Peer: true},
|
||||
)
|
||||
|
||||
Vendored
+132
-29
@@ -19,19 +19,19 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/libsignal/signalerror"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/libsignal/groups"
|
||||
"go.mau.fi/libsignal/keys/prekey"
|
||||
"go.mau.fi/libsignal/protocol"
|
||||
"go.mau.fi/libsignal/session"
|
||||
"go.mau.fi/libsignal/signalerror"
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// GenerateMessageID generates a random string that can be used as a message ID on WhatsApp.
|
||||
@@ -39,6 +39,9 @@ import (
|
||||
// msgID := cli.GenerateMessageID()
|
||||
// cli.SendMessage(context.Background(), targetJID, &waProto.Message{...}, whatsmeow.SendRequestExtra{ID: msgID})
|
||||
func (cli *Client) GenerateMessageID() types.MessageID {
|
||||
if cli.MessengerConfig != nil {
|
||||
return types.MessageID(strconv.FormatInt(GenerateFacebookMessageID(), 10))
|
||||
}
|
||||
data := make([]byte, 8, 8+20+16)
|
||||
binary.BigEndian.PutUint64(data, uint64(time.Now().Unix()))
|
||||
ownID := cli.getOwnID()
|
||||
@@ -46,11 +49,16 @@ func (cli *Client) GenerateMessageID() types.MessageID {
|
||||
data = append(data, []byte(ownID.User)...)
|
||||
data = append(data, []byte("@c.us")...)
|
||||
}
|
||||
data = append(data, randbytes.Make(16)...)
|
||||
data = append(data, random.Bytes(16)...)
|
||||
hash := sha256.Sum256(data)
|
||||
return "3EB0" + strings.ToUpper(hex.EncodeToString(hash[:9]))
|
||||
}
|
||||
|
||||
func GenerateFacebookMessageID() int64 {
|
||||
const randomMask = (1 << 22) - 1
|
||||
return (time.Now().UnixMilli() << 22) | (int64(binary.BigEndian.Uint32(random.Bytes(4))) & randomMask)
|
||||
}
|
||||
|
||||
// GenerateMessageID generates a random string that can be used as a message ID on WhatsApp.
|
||||
//
|
||||
// msgID := whatsmeow.GenerateMessageID()
|
||||
@@ -58,7 +66,7 @@ func (cli *Client) GenerateMessageID() types.MessageID {
|
||||
//
|
||||
// Deprecated: WhatsApp web has switched to using a hash of the current timestamp, user id and random bytes. Use Client.GenerateMessageID instead.
|
||||
func GenerateMessageID() types.MessageID {
|
||||
return "3EB0" + strings.ToUpper(hex.EncodeToString(randbytes.Make(8)))
|
||||
return "3EB0" + strings.ToUpper(hex.EncodeToString(random.Bytes(8)))
|
||||
}
|
||||
|
||||
type MessageDebugTimings struct {
|
||||
@@ -75,6 +83,24 @@ type MessageDebugTimings struct {
|
||||
Retry time.Duration
|
||||
}
|
||||
|
||||
func (mdt MessageDebugTimings) MarshalZerologObject(evt *zerolog.Event) {
|
||||
evt.Dur("queue", mdt.Queue)
|
||||
evt.Dur("marshal", mdt.Marshal)
|
||||
if mdt.GetParticipants != 0 {
|
||||
evt.Dur("get_participants", mdt.GetParticipants)
|
||||
}
|
||||
evt.Dur("get_devices", mdt.GetDevices)
|
||||
if mdt.GroupEncrypt != 0 {
|
||||
evt.Dur("group_encrypt", mdt.GroupEncrypt)
|
||||
}
|
||||
evt.Dur("peer_encrypt", mdt.PeerEncrypt)
|
||||
evt.Dur("send", mdt.Send)
|
||||
evt.Dur("resp", mdt.Resp)
|
||||
if mdt.Retry != 0 {
|
||||
evt.Dur("retry", mdt.Retry)
|
||||
}
|
||||
}
|
||||
|
||||
type SendResponse struct {
|
||||
// The message timestamp returned by the server
|
||||
Timestamp time.Time
|
||||
@@ -82,6 +108,9 @@ type SendResponse struct {
|
||||
// The ID of the sent message
|
||||
ID types.MessageID
|
||||
|
||||
// The server-specified ID of the sent message. Only present for newsletter messages.
|
||||
ServerID types.MessageServerID
|
||||
|
||||
// Message handling duration, used for debugging
|
||||
DebugTimings MessageDebugTimings
|
||||
}
|
||||
@@ -102,6 +131,12 @@ type SendRequestExtra struct {
|
||||
ID types.MessageID
|
||||
// Should the message be sent as a peer message (protocol messages to your own devices, e.g. app state key requests)
|
||||
Peer bool
|
||||
// A timeout for the send request. Unlike timeouts using the context parameter, this only applies
|
||||
// to the actual response waiting and not preparing/encrypting the message.
|
||||
// Defaults to 75 seconds. The timeout can be disabled by using a negative value.
|
||||
Timeout time.Duration
|
||||
// When sending media to newsletters, the Handle field returned by the file upload.
|
||||
MediaHandle string
|
||||
}
|
||||
|
||||
// SendMessage sends the given message.
|
||||
@@ -136,7 +171,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
} else if len(extra) == 1 {
|
||||
req = extra[0]
|
||||
}
|
||||
if to.AD && !req.Peer {
|
||||
if to.Device > 0 && !req.Peer {
|
||||
err = ErrRecipientADJID
|
||||
return
|
||||
}
|
||||
@@ -146,9 +181,20 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
return
|
||||
}
|
||||
|
||||
if req.Timeout == 0 {
|
||||
req.Timeout = defaultRequestTimeout
|
||||
}
|
||||
if len(req.ID) == 0 {
|
||||
req.ID = cli.GenerateMessageID()
|
||||
}
|
||||
if to.Server == types.NewsletterServer {
|
||||
// TODO somehow deduplicate this with the code in sendNewsletter?
|
||||
if message.EditedMessage != nil {
|
||||
req.ID = types.MessageID(message.GetEditedMessage().GetMessage().GetProtocolMessage().GetKey().GetId())
|
||||
} else if message.ProtocolMessage != nil && message.ProtocolMessage.GetType() == waProto.ProtocolMessage_REVOKE {
|
||||
req.ID = types.MessageID(message.GetProtocolMessage().GetKey().GetId())
|
||||
}
|
||||
}
|
||||
resp.ID = req.ID
|
||||
|
||||
start := time.Now()
|
||||
@@ -160,7 +206,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
respChan := cli.waitResponse(req.ID)
|
||||
// Peer message retries aren't implemented yet
|
||||
if !req.Peer {
|
||||
cli.addRecentMessage(to, req.ID, message)
|
||||
cli.addRecentMessage(to, req.ID, message, nil)
|
||||
}
|
||||
if message.GetMessageContextInfo().GetMessageSecret() != nil {
|
||||
err = cli.Store.MsgSecrets.PutMessageSecret(to, ownID, req.ID, message.GetMessageContextInfo().GetMessageSecret())
|
||||
@@ -181,6 +227,8 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
} else {
|
||||
data, err = cli.sendDM(ctx, to, ownID, req.ID, message, &resp.DebugTimings)
|
||||
}
|
||||
case types.NewsletterServer:
|
||||
data, err = cli.sendNewsletter(to, req.ID, message, req.MediaHandle, &resp.DebugTimings)
|
||||
default:
|
||||
err = fmt.Errorf("%w %s", ErrUnknownServer, to.Server)
|
||||
}
|
||||
@@ -190,9 +238,20 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
return
|
||||
}
|
||||
var respNode *waBinary.Node
|
||||
var timeoutChan <-chan time.Time
|
||||
if req.Timeout > 0 {
|
||||
timeoutChan = time.After(req.Timeout)
|
||||
} else {
|
||||
timeoutChan = make(<-chan time.Time)
|
||||
}
|
||||
select {
|
||||
case respNode = <-respChan:
|
||||
case <-timeoutChan:
|
||||
cli.cancelResponse(req.ID, respChan)
|
||||
err = ErrMessageTimedOut
|
||||
return
|
||||
case <-ctx.Done():
|
||||
cli.cancelResponse(req.ID, respChan)
|
||||
err = ctx.Err()
|
||||
return
|
||||
}
|
||||
@@ -206,6 +265,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waPro
|
||||
}
|
||||
}
|
||||
ag := respNode.AttrGetter()
|
||||
resp.ServerID = types.MessageServerID(ag.OptionalInt("server_id"))
|
||||
resp.Timestamp = ag.UnixTime("t")
|
||||
if errorCode := ag.Int("error"); errorCode != 0 {
|
||||
err = fmt.Errorf("%w %d", ErrServerReturnedError, errorCode)
|
||||
@@ -241,7 +301,7 @@ func (cli *Client) BuildMessageKey(chat, sender types.JID, id types.MessageID) *
|
||||
}
|
||||
if !sender.IsEmpty() && sender.User != cli.getOwnID().User {
|
||||
key.FromMe = proto.Bool(false)
|
||||
if chat.Server != types.DefaultUserServer {
|
||||
if chat.Server != types.DefaultUserServer && chat.Server != types.MessengerServer {
|
||||
key.Participant = proto.String(sender.ToNonAD().String())
|
||||
}
|
||||
}
|
||||
@@ -271,6 +331,8 @@ func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waPr
|
||||
// The built message can be sent normally using Client.SendMessage.
|
||||
//
|
||||
// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildReaction(chat, senderJID, targetMessageID, "🐈️")
|
||||
//
|
||||
// Note that for newsletter messages, you need to use NewsletterSendReaction instead of BuildReaction + SendMessage.
|
||||
func (cli *Client) BuildReaction(chat, sender types.JID, id types.MessageID, reaction string) *waProto.Message {
|
||||
return &waProto.Message{
|
||||
ReactionMessage: &waProto.ReactionMessage{
|
||||
@@ -417,7 +479,7 @@ func (cli *Client) SetDisappearingTimer(chat types.JID, timer time.Duration) (er
|
||||
func participantListHashV2(participants []types.JID) string {
|
||||
participantsStrings := make([]string, len(participants))
|
||||
for i, part := range participants {
|
||||
participantsStrings[i] = part.String()
|
||||
participantsStrings[i] = part.ADString()
|
||||
}
|
||||
|
||||
sort.Strings(participantsStrings)
|
||||
@@ -425,6 +487,50 @@ func participantListHashV2(participants []types.JID) string {
|
||||
return fmt.Sprintf("2:%s", base64.RawStdEncoding.EncodeToString(hash[:6]))
|
||||
}
|
||||
|
||||
func (cli *Client) sendNewsletter(to types.JID, id types.MessageID, message *waProto.Message, mediaID string, timings *MessageDebugTimings) ([]byte, error) {
|
||||
attrs := waBinary.Attrs{
|
||||
"to": to,
|
||||
"id": id,
|
||||
"type": getTypeFromMessage(message),
|
||||
}
|
||||
if mediaID != "" {
|
||||
attrs["media_id"] = mediaID
|
||||
}
|
||||
if message.EditedMessage != nil {
|
||||
attrs["edit"] = string(types.EditAttributeAdminEdit)
|
||||
message = message.GetEditedMessage().GetMessage().GetProtocolMessage().GetEditedMessage()
|
||||
} else if message.ProtocolMessage != nil && message.ProtocolMessage.GetType() == waProto.ProtocolMessage_REVOKE {
|
||||
attrs["edit"] = string(types.EditAttributeAdminRevoke)
|
||||
message = nil
|
||||
}
|
||||
start := time.Now()
|
||||
plaintext, _, err := marshalMessage(to, message)
|
||||
timings.Marshal = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plaintextNode := waBinary.Node{
|
||||
Tag: "plaintext",
|
||||
Content: plaintext,
|
||||
Attrs: waBinary.Attrs{},
|
||||
}
|
||||
if mediaType := getMediaTypeFromMessage(message); mediaType != "" {
|
||||
plaintextNode.Attrs["mediatype"] = mediaType
|
||||
}
|
||||
node := waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
Content: []waBinary.Node{plaintextNode},
|
||||
}
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (cli *Client) sendGroup(ctx context.Context, to, ownID types.JID, id types.MessageID, message *waProto.Message, timings *MessageDebugTimings) (string, []byte, error) {
|
||||
var participants []types.JID
|
||||
var err error
|
||||
@@ -653,16 +759,9 @@ func getButtonAttributes(msg *waProto.Message) waBinary.Attrs {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
EditAttributeEmpty = ""
|
||||
EditAttributeMessageEdit = "1"
|
||||
EditAttributeSenderRevoke = "7"
|
||||
EditAttributeAdminRevoke = "8"
|
||||
)
|
||||
|
||||
const RemoveReactionText = ""
|
||||
|
||||
func getEditAttribute(msg *waProto.Message) string {
|
||||
func getEditAttribute(msg *waProto.Message) types.EditAttribute {
|
||||
switch {
|
||||
case msg.EditedMessage != nil && msg.EditedMessage.Message != nil:
|
||||
return getEditAttribute(msg.EditedMessage.Message)
|
||||
@@ -670,21 +769,21 @@ func getEditAttribute(msg *waProto.Message) string {
|
||||
switch msg.ProtocolMessage.GetType() {
|
||||
case waProto.ProtocolMessage_REVOKE:
|
||||
if msg.ProtocolMessage.GetKey().GetFromMe() {
|
||||
return EditAttributeSenderRevoke
|
||||
return types.EditAttributeSenderRevoke
|
||||
} else {
|
||||
return EditAttributeAdminRevoke
|
||||
return types.EditAttributeAdminRevoke
|
||||
}
|
||||
case waProto.ProtocolMessage_MESSAGE_EDIT:
|
||||
if msg.ProtocolMessage.EditedMessage != nil {
|
||||
return EditAttributeMessageEdit
|
||||
return types.EditAttributeMessageEdit
|
||||
}
|
||||
}
|
||||
case msg.ReactionMessage != nil && msg.ReactionMessage.GetText() == RemoveReactionText:
|
||||
return EditAttributeSenderRevoke
|
||||
return types.EditAttributeSenderRevoke
|
||||
case msg.KeepInChatMessage != nil && msg.KeepInChatMessage.GetKey().GetFromMe() && msg.KeepInChatMessage.GetKeepType() == waProto.KeepType_UNDO_KEEP_FOR_ALL:
|
||||
return EditAttributeSenderRevoke
|
||||
return types.EditAttributeSenderRevoke
|
||||
}
|
||||
return EditAttributeEmpty
|
||||
return types.EditAttributeEmpty
|
||||
}
|
||||
|
||||
func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, message *waProto.Message, timings *MessageDebugTimings) (*waBinary.Node, error) {
|
||||
@@ -711,7 +810,7 @@ func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, mess
|
||||
return nil, fmt.Errorf("failed to encrypt peer message for %s: %v", to, err)
|
||||
}
|
||||
content := []waBinary.Node{*encrypted}
|
||||
if isPreKey {
|
||||
if isPreKey && cli.MessengerConfig == nil {
|
||||
content = append(content, cli.makeDeviceIdentityNode())
|
||||
}
|
||||
return &waBinary.Node{
|
||||
@@ -770,10 +869,10 @@ func (cli *Client) prepareMessageNode(ctx context.Context, to, ownID types.JID,
|
||||
"to": to,
|
||||
}
|
||||
if editAttr := getEditAttribute(message); editAttr != "" {
|
||||
attrs["edit"] = editAttr
|
||||
attrs["edit"] = string(editAttr)
|
||||
encAttrs["decrypt-fail"] = string(events.DecryptFailHide)
|
||||
}
|
||||
if msgType == "reaction" {
|
||||
if msgType == "reaction" || message.GetPollUpdateMessage() != nil {
|
||||
encAttrs["decrypt-fail"] = string(events.DecryptFailHide)
|
||||
}
|
||||
|
||||
@@ -792,13 +891,16 @@ func (cli *Client) prepareMessageNode(ctx context.Context, to, ownID types.JID,
|
||||
}
|
||||
|
||||
func marshalMessage(to types.JID, message *waProto.Message) (plaintext, dsmPlaintext []byte, err error) {
|
||||
if message == nil && to.Server == types.NewsletterServer {
|
||||
return
|
||||
}
|
||||
plaintext, err = proto.Marshal(message)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to marshal message: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if to.Server != types.GroupServer {
|
||||
if to.Server != types.GroupServer && to.Server != types.NewsletterServer {
|
||||
dsmPlaintext, err = proto.Marshal(&waProto.Message{
|
||||
DeviceSentMessage: &waProto.DeviceSentMessage{
|
||||
DestinationJid: proto.String(to.String()),
|
||||
@@ -929,9 +1031,10 @@ func (cli *Client) encryptMessageForDevice(plaintext []byte, to types.JID, bundl
|
||||
}
|
||||
copyAttrs(extraAttrs, encAttrs)
|
||||
|
||||
includeDeviceIdentity := encAttrs["type"] == "pkmsg" && cli.MessengerConfig == nil
|
||||
return &waBinary.Node{
|
||||
Tag: "enc",
|
||||
Attrs: encAttrs,
|
||||
Content: ciphertext.Serialize(),
|
||||
}, encAttrs["type"] == "pkmsg", nil
|
||||
}, includeDeviceIdentity, nil
|
||||
}
|
||||
|
||||
Vendored
+606
@@ -0,0 +1,606 @@
|
||||
// Copyright (c) 2024 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 (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.mau.fi/libsignal/groups"
|
||||
"go.mau.fi/libsignal/keys/prekey"
|
||||
"go.mau.fi/libsignal/protocol"
|
||||
"go.mau.fi/libsignal/session"
|
||||
"go.mau.fi/libsignal/signalerror"
|
||||
"go.mau.fi/util/random"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waCommon"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waConsumerApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgTransport"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
|
||||
const FBMessageVersion = 3
|
||||
const FBMessageApplicationVersion = 2
|
||||
const FBConsumerMessageVersion = 1
|
||||
|
||||
// SendFBMessage sends the given v3 message to the given JID.
|
||||
func (cli *Client) SendFBMessage(
|
||||
ctx context.Context,
|
||||
to types.JID,
|
||||
message *waConsumerApplication.ConsumerApplication,
|
||||
metadata *waMsgApplication.MessageApplication_Metadata,
|
||||
extra ...SendRequestExtra,
|
||||
) (resp SendResponse, err error) {
|
||||
var req SendRequestExtra
|
||||
if len(extra) > 1 {
|
||||
err = errors.New("only one extra parameter may be provided to SendMessage")
|
||||
return
|
||||
} else if len(extra) == 1 {
|
||||
req = extra[0]
|
||||
}
|
||||
consumerMessage, err := proto.Marshal(message)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to marshal consumer message: %w", err)
|
||||
return
|
||||
}
|
||||
if metadata == nil {
|
||||
metadata = &waMsgApplication.MessageApplication_Metadata{}
|
||||
}
|
||||
metadata.FrankingVersion = 0
|
||||
metadata.FrankingKey = random.Bytes(32)
|
||||
msgAttrs := getAttrsFromFBMessage(message)
|
||||
messageAppProto := &waMsgApplication.MessageApplication{
|
||||
Payload: &waMsgApplication.MessageApplication_Payload{
|
||||
Content: &waMsgApplication.MessageApplication_Payload_SubProtocol{
|
||||
SubProtocol: &waMsgApplication.MessageApplication_SubProtocolPayload{
|
||||
SubProtocol: &waMsgApplication.MessageApplication_SubProtocolPayload_ConsumerMessage{
|
||||
ConsumerMessage: &waCommon.SubProtocol{
|
||||
Payload: consumerMessage,
|
||||
Version: FBConsumerMessageVersion,
|
||||
},
|
||||
},
|
||||
FutureProof: waCommon.FutureProofBehavior_PLACEHOLDER,
|
||||
},
|
||||
},
|
||||
},
|
||||
Metadata: metadata,
|
||||
}
|
||||
messageApp, err := proto.Marshal(messageAppProto)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("failed to marshal message application: %w", err)
|
||||
}
|
||||
frankingHash := hmac.New(sha256.New, metadata.FrankingKey)
|
||||
frankingHash.Write(messageApp)
|
||||
frankingTag := frankingHash.Sum(nil)
|
||||
if to.Device > 0 && !req.Peer {
|
||||
err = ErrRecipientADJID
|
||||
return
|
||||
}
|
||||
ownID := cli.getOwnID()
|
||||
if ownID.IsEmpty() {
|
||||
err = ErrNotLoggedIn
|
||||
return
|
||||
}
|
||||
|
||||
if req.Timeout == 0 {
|
||||
req.Timeout = defaultRequestTimeout
|
||||
}
|
||||
if len(req.ID) == 0 {
|
||||
req.ID = cli.GenerateMessageID()
|
||||
}
|
||||
resp.ID = req.ID
|
||||
|
||||
start := time.Now()
|
||||
// Sending multiple messages at a time can cause weird issues and makes it harder to retry safely
|
||||
cli.messageSendLock.Lock()
|
||||
resp.DebugTimings.Queue = time.Since(start)
|
||||
defer cli.messageSendLock.Unlock()
|
||||
|
||||
respChan := cli.waitResponse(req.ID)
|
||||
if !req.Peer {
|
||||
cli.addRecentMessage(to, req.ID, nil, messageAppProto)
|
||||
}
|
||||
var phash string
|
||||
var data []byte
|
||||
switch to.Server {
|
||||
case types.GroupServer:
|
||||
phash, data, err = cli.sendGroupV3(ctx, to, ownID, req.ID, messageApp, msgAttrs, frankingTag, &resp.DebugTimings)
|
||||
case types.DefaultUserServer, types.MessengerServer:
|
||||
if req.Peer {
|
||||
err = fmt.Errorf("peer messages to fb are not yet supported")
|
||||
//data, err = cli.sendPeerMessage(to, req.ID, message, &resp.DebugTimings)
|
||||
} else {
|
||||
data, phash, err = cli.sendDMV3(ctx, to, ownID, req.ID, messageApp, msgAttrs, frankingTag, &resp.DebugTimings)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("%w %s", ErrUnknownServer, to.Server)
|
||||
}
|
||||
start = time.Now()
|
||||
if err != nil {
|
||||
cli.cancelResponse(req.ID, respChan)
|
||||
return
|
||||
}
|
||||
var respNode *waBinary.Node
|
||||
var timeoutChan <-chan time.Time
|
||||
if req.Timeout > 0 {
|
||||
timeoutChan = time.After(req.Timeout)
|
||||
} else {
|
||||
timeoutChan = make(<-chan time.Time)
|
||||
}
|
||||
select {
|
||||
case respNode = <-respChan:
|
||||
case <-timeoutChan:
|
||||
cli.cancelResponse(req.ID, respChan)
|
||||
err = ErrMessageTimedOut
|
||||
return
|
||||
case <-ctx.Done():
|
||||
cli.cancelResponse(req.ID, respChan)
|
||||
err = ctx.Err()
|
||||
return
|
||||
}
|
||||
resp.DebugTimings.Resp = time.Since(start)
|
||||
if isDisconnectNode(respNode) {
|
||||
start = time.Now()
|
||||
respNode, err = cli.retryFrame("message send", req.ID, data, respNode, ctx, 0)
|
||||
resp.DebugTimings.Retry = time.Since(start)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
ag := respNode.AttrGetter()
|
||||
resp.ServerID = types.MessageServerID(ag.OptionalInt("server_id"))
|
||||
resp.Timestamp = ag.UnixTime("t")
|
||||
if errorCode := ag.Int("error"); errorCode != 0 {
|
||||
err = fmt.Errorf("%w %d", ErrServerReturnedError, errorCode)
|
||||
}
|
||||
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)
|
||||
// TODO also invalidate device list caches
|
||||
cli.groupParticipantsCacheLock.Lock()
|
||||
delete(cli.groupParticipantsCache, to)
|
||||
cli.groupParticipantsCacheLock.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) sendGroupV3(
|
||||
ctx context.Context,
|
||||
to,
|
||||
ownID types.JID,
|
||||
id types.MessageID,
|
||||
messageApp []byte,
|
||||
msgAttrs messageAttrs,
|
||||
frankingTag []byte,
|
||||
timings *MessageDebugTimings,
|
||||
) (string, []byte, error) {
|
||||
var participants []types.JID
|
||||
var err error
|
||||
start := time.Now()
|
||||
if to.Server == types.GroupServer {
|
||||
participants, err = cli.getGroupMembers(ctx, to)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get group members: %w", err)
|
||||
}
|
||||
}
|
||||
timings.GetParticipants = time.Since(start)
|
||||
|
||||
start = time.Now()
|
||||
builder := groups.NewGroupSessionBuilder(cli.Store, pbSerializer)
|
||||
senderKeyName := protocol.NewSenderKeyName(to.String(), ownID.SignalAddress())
|
||||
signalSKDMessage, err := builder.Create(senderKeyName)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to create sender key distribution message to send %s to %s: %w", id, to, err)
|
||||
}
|
||||
skdm := &waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage{
|
||||
GroupID: to.String(),
|
||||
AxolotlSenderKeyDistributionMessage: signalSKDMessage.Serialize(),
|
||||
}
|
||||
|
||||
cipher := groups.NewGroupCipher(builder, senderKeyName, cli.Store)
|
||||
plaintext, err := proto.Marshal(&waMsgTransport.MessageTransport{
|
||||
Payload: &waMsgTransport.MessageTransport_Payload{
|
||||
ApplicationPayload: &waCommon.SubProtocol{
|
||||
Payload: messageApp,
|
||||
Version: FBMessageApplicationVersion,
|
||||
},
|
||||
FutureProof: waCommon.FutureProofBehavior_PLACEHOLDER,
|
||||
},
|
||||
Protocol: &waMsgTransport.MessageTransport_Protocol{
|
||||
Integral: &waMsgTransport.MessageTransport_Protocol_Integral{
|
||||
Padding: padMessage(nil),
|
||||
DSM: nil,
|
||||
},
|
||||
Ancillary: &waMsgTransport.MessageTransport_Protocol_Ancillary{
|
||||
Skdm: nil,
|
||||
DeviceListMetadata: nil,
|
||||
Icdc: nil,
|
||||
BackupDirective: &waMsgTransport.MessageTransport_Protocol_Ancillary_BackupDirective{
|
||||
MessageID: id,
|
||||
ActionType: waMsgTransport.MessageTransport_Protocol_Ancillary_BackupDirective_UPSERT,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to marshal message transport: %w", err)
|
||||
}
|
||||
encrypted, err := cipher.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to encrypt group message to send %s to %s: %w", id, to, err)
|
||||
}
|
||||
ciphertext := encrypted.SignedSerialize()
|
||||
timings.GroupEncrypt = time.Since(start)
|
||||
|
||||
node, allDevices, err := cli.prepareMessageNodeV3(ctx, to, ownID, id, nil, skdm, msgAttrs, frankingTag, participants, timings)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
phash := participantListHashV2(allDevices)
|
||||
node.Attrs["phash"] = phash
|
||||
skMsg := waBinary.Node{
|
||||
Tag: "enc",
|
||||
Content: ciphertext,
|
||||
Attrs: waBinary.Attrs{"v": "3", "type": "skmsg"},
|
||||
}
|
||||
if msgAttrs.MediaType != "" {
|
||||
skMsg.Attrs["mediatype"] = msgAttrs.MediaType
|
||||
}
|
||||
node.Content = append(node.GetChildren(), skMsg)
|
||||
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
}
|
||||
return phash, data, nil
|
||||
}
|
||||
|
||||
func (cli *Client) sendDMV3(
|
||||
ctx context.Context,
|
||||
to,
|
||||
ownID types.JID,
|
||||
id types.MessageID,
|
||||
messageApp []byte,
|
||||
msgAttrs messageAttrs,
|
||||
frankingTag []byte,
|
||||
timings *MessageDebugTimings,
|
||||
) ([]byte, string, error) {
|
||||
payload := &waMsgTransport.MessageTransport_Payload{
|
||||
ApplicationPayload: &waCommon.SubProtocol{
|
||||
Payload: messageApp,
|
||||
Version: FBMessageApplicationVersion,
|
||||
},
|
||||
FutureProof: waCommon.FutureProofBehavior_PLACEHOLDER,
|
||||
}
|
||||
|
||||
node, allDevices, err := cli.prepareMessageNodeV3(ctx, to, ownID, id, payload, nil, msgAttrs, frankingTag, []types.JID{to, ownID.ToNonAD()}, timings)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
start := time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to send message node: %w", err)
|
||||
}
|
||||
return data, participantListHashV2(allDevices), nil
|
||||
}
|
||||
|
||||
type messageAttrs struct {
|
||||
Type string
|
||||
MediaType string
|
||||
Edit types.EditAttribute
|
||||
DecryptFail events.DecryptFailMode
|
||||
PollType string
|
||||
}
|
||||
|
||||
func getAttrsFromFBMessage(msg *waConsumerApplication.ConsumerApplication) (attrs messageAttrs) {
|
||||
switch payload := msg.GetPayload().GetPayload().(type) {
|
||||
case *waConsumerApplication.ConsumerApplication_Payload_Content:
|
||||
switch content := payload.Content.GetContent().(type) {
|
||||
case *waConsumerApplication.ConsumerApplication_Content_MessageText,
|
||||
*waConsumerApplication.ConsumerApplication_Content_ExtendedTextMessage:
|
||||
attrs.Type = "text"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_ImageMessage:
|
||||
attrs.MediaType = "image"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_StickerMessage:
|
||||
attrs.MediaType = "sticker"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_ViewOnceMessage:
|
||||
switch content.ViewOnceMessage.GetViewOnceContent().(type) {
|
||||
case *waConsumerApplication.ConsumerApplication_ViewOnceMessage_ImageMessage:
|
||||
attrs.MediaType = "image"
|
||||
case *waConsumerApplication.ConsumerApplication_ViewOnceMessage_VideoMessage:
|
||||
attrs.MediaType = "video"
|
||||
}
|
||||
case *waConsumerApplication.ConsumerApplication_Content_DocumentMessage:
|
||||
attrs.MediaType = "document"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_AudioMessage:
|
||||
if content.AudioMessage.GetPTT() {
|
||||
attrs.MediaType = "ptt"
|
||||
} else {
|
||||
attrs.MediaType = "audio"
|
||||
}
|
||||
case *waConsumerApplication.ConsumerApplication_Content_VideoMessage:
|
||||
// TODO gifPlayback?
|
||||
attrs.MediaType = "video"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_LocationMessage:
|
||||
attrs.MediaType = "location"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_LiveLocationMessage:
|
||||
attrs.MediaType = "location"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_ContactMessage:
|
||||
attrs.MediaType = "vcard"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_ContactsArrayMessage:
|
||||
attrs.MediaType = "contact_array"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_PollCreationMessage:
|
||||
attrs.PollType = "creation"
|
||||
attrs.Type = "poll"
|
||||
case *waConsumerApplication.ConsumerApplication_Content_PollUpdateMessage:
|
||||
attrs.PollType = "vote"
|
||||
attrs.Type = "poll"
|
||||
attrs.DecryptFail = events.DecryptFailHide
|
||||
case *waConsumerApplication.ConsumerApplication_Content_ReactionMessage:
|
||||
attrs.Type = "reaction"
|
||||
attrs.DecryptFail = events.DecryptFailHide
|
||||
case *waConsumerApplication.ConsumerApplication_Content_EditMessage:
|
||||
attrs.Edit = types.EditAttributeMessageEdit
|
||||
attrs.DecryptFail = events.DecryptFailHide
|
||||
}
|
||||
if attrs.MediaType != "" && attrs.Type == "" {
|
||||
attrs.Type = "media"
|
||||
}
|
||||
case *waConsumerApplication.ConsumerApplication_Payload_ApplicationData:
|
||||
switch content := payload.ApplicationData.GetApplicationContent().(type) {
|
||||
case *waConsumerApplication.ConsumerApplication_ApplicationData_Revoke:
|
||||
if content.Revoke.GetKey().GetFromMe() {
|
||||
attrs.Edit = types.EditAttributeSenderRevoke
|
||||
} else {
|
||||
attrs.Edit = types.EditAttributeAdminRevoke
|
||||
}
|
||||
attrs.DecryptFail = events.DecryptFailHide
|
||||
}
|
||||
case *waConsumerApplication.ConsumerApplication_Payload_Signal:
|
||||
case *waConsumerApplication.ConsumerApplication_Payload_SubProtocol:
|
||||
}
|
||||
if attrs.Type == "" {
|
||||
attrs.Type = "text"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) prepareMessageNodeV3(
|
||||
ctx context.Context,
|
||||
to,
|
||||
ownID types.JID,
|
||||
id types.MessageID,
|
||||
payload *waMsgTransport.MessageTransport_Payload,
|
||||
skdm *waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage,
|
||||
msgAttrs messageAttrs,
|
||||
frankingTag []byte,
|
||||
participants []types.JID,
|
||||
timings *MessageDebugTimings,
|
||||
) (*waBinary.Node, []types.JID, error) {
|
||||
start := time.Now()
|
||||
allDevices, err := cli.GetUserDevicesContext(ctx, participants)
|
||||
timings.GetDevices = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get device list: %w", err)
|
||||
}
|
||||
|
||||
encAttrs := waBinary.Attrs{}
|
||||
attrs := waBinary.Attrs{
|
||||
"id": id,
|
||||
"type": msgAttrs.Type,
|
||||
"to": to,
|
||||
}
|
||||
// Only include mediatype on DMs, for groups it's in the skmsg node
|
||||
if payload != nil && msgAttrs.MediaType != "" {
|
||||
encAttrs["mediatype"] = msgAttrs.MediaType
|
||||
}
|
||||
if msgAttrs.Edit != "" {
|
||||
attrs["edit"] = string(msgAttrs.Edit)
|
||||
}
|
||||
if msgAttrs.DecryptFail != "" {
|
||||
encAttrs["decrypt-fail"] = string(msgAttrs.DecryptFail)
|
||||
}
|
||||
|
||||
dsm := &waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage{
|
||||
DestinationJID: to.String(),
|
||||
Phash: "",
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
participantNodes := cli.encryptMessageForDevicesV3(ctx, allDevices, ownID, id, payload, skdm, dsm, encAttrs)
|
||||
timings.PeerEncrypt = time.Since(start)
|
||||
content := make([]waBinary.Node, 0, 4)
|
||||
content = append(content, waBinary.Node{
|
||||
Tag: "participants",
|
||||
Content: participantNodes,
|
||||
})
|
||||
metaAttrs := make(waBinary.Attrs)
|
||||
if msgAttrs.PollType != "" {
|
||||
metaAttrs["polltype"] = msgAttrs.PollType
|
||||
}
|
||||
if msgAttrs.DecryptFail != "" {
|
||||
metaAttrs["decrypt-fail"] = string(msgAttrs.DecryptFail)
|
||||
}
|
||||
if len(metaAttrs) > 0 {
|
||||
content = append(content, waBinary.Node{
|
||||
Tag: "meta",
|
||||
Attrs: metaAttrs,
|
||||
})
|
||||
}
|
||||
traceRequestID := uuid.New()
|
||||
content = append(content, waBinary.Node{
|
||||
Tag: "franking",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "franking_tag",
|
||||
Content: frankingTag,
|
||||
}},
|
||||
}, waBinary.Node{
|
||||
Tag: "trace",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "request_id",
|
||||
Content: traceRequestID[:],
|
||||
}},
|
||||
})
|
||||
return &waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
Content: content,
|
||||
}, allDevices, nil
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDevicesV3(
|
||||
ctx context.Context,
|
||||
allDevices []types.JID,
|
||||
ownID types.JID,
|
||||
id string,
|
||||
payload *waMsgTransport.MessageTransport_Payload,
|
||||
skdm *waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage,
|
||||
dsm *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage,
|
||||
encAttrs waBinary.Attrs,
|
||||
) []waBinary.Node {
|
||||
participantNodes := make([]waBinary.Node, 0, len(allDevices))
|
||||
var retryDevices []types.JID
|
||||
for _, jid := range allDevices {
|
||||
var dsmForDevice *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage
|
||||
if jid.User == ownID.User {
|
||||
if jid == ownID {
|
||||
continue
|
||||
}
|
||||
dsmForDevice = dsm
|
||||
}
|
||||
encrypted, err := cli.encryptMessageForDeviceAndWrapV3(payload, skdm, dsmForDevice, jid, nil, encAttrs)
|
||||
if errors.Is(err, ErrNoSession) {
|
||||
retryDevices = append(retryDevices, jid)
|
||||
continue
|
||||
} else if err != nil {
|
||||
cli.Log.Warnf("Failed to encrypt %s for %s: %v", id, jid, err)
|
||||
continue
|
||||
}
|
||||
participantNodes = append(participantNodes, *encrypted)
|
||||
}
|
||||
if len(retryDevices) > 0 {
|
||||
bundles, err := cli.fetchPreKeys(ctx, retryDevices)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to fetch prekeys for %v to retry encryption: %v", retryDevices, err)
|
||||
} else {
|
||||
for _, jid := range retryDevices {
|
||||
resp := bundles[jid]
|
||||
if resp.err != nil {
|
||||
cli.Log.Warnf("Failed to fetch prekey for %s: %v", jid, resp.err)
|
||||
continue
|
||||
}
|
||||
var dsmForDevice *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage
|
||||
if jid.User == ownID.User {
|
||||
dsmForDevice = dsm
|
||||
}
|
||||
encrypted, err := cli.encryptMessageForDeviceAndWrapV3(payload, skdm, dsmForDevice, jid, resp.bundle, encAttrs)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to encrypt %s for %s (retry): %v", id, jid, err)
|
||||
continue
|
||||
}
|
||||
participantNodes = append(participantNodes, *encrypted)
|
||||
}
|
||||
}
|
||||
}
|
||||
return participantNodes
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDeviceAndWrapV3(
|
||||
payload *waMsgTransport.MessageTransport_Payload,
|
||||
skdm *waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage,
|
||||
dsm *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage,
|
||||
to types.JID,
|
||||
bundle *prekey.Bundle,
|
||||
encAttrs waBinary.Attrs,
|
||||
) (*waBinary.Node, error) {
|
||||
node, err := cli.encryptMessageForDeviceV3(payload, skdm, dsm, to, bundle, encAttrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &waBinary.Node{
|
||||
Tag: "to",
|
||||
Attrs: waBinary.Attrs{"jid": to},
|
||||
Content: []waBinary.Node{*node},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cli *Client) encryptMessageForDeviceV3(
|
||||
payload *waMsgTransport.MessageTransport_Payload,
|
||||
skdm *waMsgTransport.MessageTransport_Protocol_Ancillary_SenderKeyDistributionMessage,
|
||||
dsm *waMsgTransport.MessageTransport_Protocol_Integral_DeviceSentMessage,
|
||||
to types.JID,
|
||||
bundle *prekey.Bundle,
|
||||
extraAttrs waBinary.Attrs,
|
||||
) (*waBinary.Node, error) {
|
||||
builder := session.NewBuilderFromSignal(cli.Store, to.SignalAddress(), pbSerializer)
|
||||
if bundle != nil {
|
||||
cli.Log.Debugf("Processing prekey bundle for %s", to)
|
||||
err := builder.ProcessBundle(bundle)
|
||||
if cli.AutoTrustIdentity && errors.Is(err, signalerror.ErrUntrustedIdentity) {
|
||||
cli.Log.Warnf("Got %v error while trying to process prekey bundle for %s, clearing stored identity and retrying", err, to)
|
||||
cli.clearUntrustedIdentity(to)
|
||||
err = builder.ProcessBundle(bundle)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process prekey bundle: %w", err)
|
||||
}
|
||||
} else if !cli.Store.ContainsSession(to.SignalAddress()) {
|
||||
return nil, ErrNoSession
|
||||
}
|
||||
cipher := session.NewCipher(builder, to.SignalAddress())
|
||||
plaintext, err := proto.Marshal(&waMsgTransport.MessageTransport{
|
||||
Payload: payload,
|
||||
Protocol: &waMsgTransport.MessageTransport_Protocol{
|
||||
Integral: &waMsgTransport.MessageTransport_Protocol_Integral{
|
||||
Padding: padMessage(nil),
|
||||
DSM: dsm,
|
||||
},
|
||||
Ancillary: &waMsgTransport.MessageTransport_Protocol_Ancillary{
|
||||
Skdm: skdm,
|
||||
DeviceListMetadata: nil,
|
||||
Icdc: nil,
|
||||
BackupDirective: nil,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal message transport: %w", err)
|
||||
}
|
||||
ciphertext, err := cipher.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cipher encryption failed: %w", err)
|
||||
}
|
||||
|
||||
encAttrs := waBinary.Attrs{
|
||||
"v": FBMessageVersion,
|
||||
"type": "msg",
|
||||
}
|
||||
if ciphertext.Type() == protocol.PREKEY_TYPE {
|
||||
encAttrs["type"] = "pkmsg"
|
||||
}
|
||||
copyAttrs(extraAttrs, encAttrs)
|
||||
|
||||
return &waBinary.Node{
|
||||
Tag: "enc",
|
||||
Attrs: encAttrs,
|
||||
Content: ciphertext.Serialize(),
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ const (
|
||||
const (
|
||||
NoiseStartPattern = "Noise_XX_25519_AESGCM_SHA256\x00\x00\x00\x00"
|
||||
|
||||
WAMagicValue = 5
|
||||
WAMagicValue = 6
|
||||
)
|
||||
|
||||
var WAConnHeader = []byte{'W', 'A', WAMagicValue, token.DictVersion}
|
||||
|
||||
+12
-13
@@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -20,8 +19,6 @@ import (
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
)
|
||||
|
||||
type Proxy = func(*http.Request) (*url.URL, error)
|
||||
|
||||
type FrameSocket struct {
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
@@ -29,12 +26,15 @@ type FrameSocket struct {
|
||||
log waLog.Logger
|
||||
lock sync.Mutex
|
||||
|
||||
URL string
|
||||
HTTPHeaders http.Header
|
||||
|
||||
Frames chan []byte
|
||||
OnDisconnect func(remote bool)
|
||||
WriteTimeout time.Duration
|
||||
|
||||
Header []byte
|
||||
Proxy Proxy
|
||||
Dialer websocket.Dialer
|
||||
|
||||
incomingLength int
|
||||
receivedLength int
|
||||
@@ -42,14 +42,17 @@ type FrameSocket struct {
|
||||
partialHeader []byte
|
||||
}
|
||||
|
||||
func NewFrameSocket(log waLog.Logger, header []byte, proxy Proxy) *FrameSocket {
|
||||
func NewFrameSocket(log waLog.Logger, dialer websocket.Dialer) *FrameSocket {
|
||||
return &FrameSocket{
|
||||
conn: nil,
|
||||
log: log,
|
||||
Header: header,
|
||||
Header: WAConnHeader,
|
||||
Frames: make(chan []byte),
|
||||
|
||||
Proxy: proxy,
|
||||
URL: URL,
|
||||
HTTPHeaders: http.Header{"Origin": {Origin}},
|
||||
|
||||
Dialer: dialer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,13 +101,9 @@ func (fs *FrameSocket) Connect() error {
|
||||
return ErrSocketAlreadyOpen
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
dialer := websocket.Dialer{
|
||||
Proxy: fs.Proxy,
|
||||
}
|
||||
|
||||
headers := http.Header{"Origin": []string{Origin}}
|
||||
fs.log.Debugf("Dialing %s", URL)
|
||||
conn, _, err := dialer.Dial(URL, headers)
|
||||
fs.log.Debugf("Dialing %s", fs.URL)
|
||||
conn, _, err := fs.Dialer.Dial(fs.URL, fs.HTTPHeaders)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return fmt.Errorf("couldn't dial whatsapp web websocket: %w", err)
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ type NoiseSocket struct {
|
||||
writeCounter uint32
|
||||
readCounter uint32
|
||||
writeLock sync.Mutex
|
||||
destroyed uint32
|
||||
destroyed atomic.Bool
|
||||
stopConsumer chan struct{}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func (ns *NoiseSocket) Context() context.Context {
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) Stop(disconnect bool) {
|
||||
if atomic.CompareAndSwapUint32(&ns.destroyed, 0, 1) {
|
||||
if ns.destroyed.CompareAndSwap(false, true) {
|
||||
close(ns.stopConsumer)
|
||||
ns.fs.OnDisconnect = nil
|
||||
if disconnect {
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ func (vc WAVersionContainer) ProtoAppVersion() *waProto.ClientPayload_UserAgent_
|
||||
}
|
||||
|
||||
// waVersion is the WhatsApp web client version
|
||||
var waVersion = WAVersionContainer{2, 2332, 15}
|
||||
var waVersion = WAVersionContainer{2, 2412, 50}
|
||||
|
||||
// waVersionHash is the md5 hash of a dot-separated waVersion
|
||||
var waVersionHash [16]byte
|
||||
|
||||
+19
-7
@@ -12,12 +12,14 @@ import (
|
||||
"fmt"
|
||||
mathRand "math/rand"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.mau.fi/util/random"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// Container is a wrapper for a SQL database that can contain multiple whatsmeow sessions.
|
||||
@@ -86,7 +88,7 @@ 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_account_sig_key, adv_device_sig,
|
||||
platform, business_name, push_name
|
||||
platform, business_name, push_name, facebook_uuid
|
||||
FROM whatsmeow_device
|
||||
`
|
||||
|
||||
@@ -103,12 +105,13 @@ func (c *Container) scanDevice(row scannable) (*store.Device, error) {
|
||||
device.SignedPreKey = &keys.PreKey{}
|
||||
var noisePriv, identityPriv, preKeyPriv, preKeySig []byte
|
||||
var account waProto.ADVSignedDeviceIdentity
|
||||
var fbUUID uuid.NullUUID
|
||||
|
||||
err := row.Scan(
|
||||
&device.ID, &device.RegistrationID, &noisePriv, &identityPriv,
|
||||
&preKeyPriv, &device.SignedPreKey.KeyID, &preKeySig,
|
||||
&device.AdvSecretKey, &account.Details, &account.AccountSignature, &account.AccountSignatureKey, &account.DeviceSignature,
|
||||
&device.Platform, &device.BusinessName, &device.PushName)
|
||||
&device.Platform, &device.BusinessName, &device.PushName, &fbUUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan session: %w", err)
|
||||
} else if len(noisePriv) != 32 || len(identityPriv) != 32 || len(preKeyPriv) != 32 || len(preKeySig) != 64 {
|
||||
@@ -120,6 +123,7 @@ func (c *Container) scanDevice(row scannable) (*store.Device, error) {
|
||||
device.SignedPreKey.KeyPair = *keys.NewKeyPairFromPrivateKey(*(*[32]byte)(preKeyPriv))
|
||||
device.SignedPreKey.Signature = (*[64]byte)(preKeySig)
|
||||
device.Account = &account
|
||||
device.FacebookUUID = fbUUID.UUID
|
||||
|
||||
innerStore := NewSQLStore(c, *device.ID)
|
||||
device.Identities = innerStore
|
||||
@@ -188,8 +192,8 @@ const (
|
||||
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_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, $15)
|
||||
platform, business_name, push_name, facebook_uuid)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||
ON CONFLICT (jid) DO UPDATE
|
||||
SET platform=excluded.platform, business_name=excluded.business_name, push_name=excluded.push_name
|
||||
`
|
||||
@@ -210,7 +214,7 @@ func (c *Container) NewDevice() *store.Device {
|
||||
NoiseKey: keys.NewKeyPair(),
|
||||
IdentityKey: keys.NewKeyPair(),
|
||||
RegistrationID: mathRand.Uint32(),
|
||||
AdvSecretKey: randbytes.Make(32),
|
||||
AdvSecretKey: random.Bytes(32),
|
||||
}
|
||||
device.SignedPreKey = device.IdentityKey.CreateSignedPreKey(1)
|
||||
return device
|
||||
@@ -219,6 +223,14 @@ func (c *Container) NewDevice() *store.Device {
|
||||
// ErrDeviceIDMustBeSet is the error returned by PutDevice if you try to save a device before knowing its JID.
|
||||
var ErrDeviceIDMustBeSet = errors.New("device JID must be known before accessing database")
|
||||
|
||||
// Close will close the container's database
|
||||
func (c *Container) Close() error {
|
||||
if c != nil && c.db != nil {
|
||||
return c.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutDevice stores the given device in this database. This should be called through Device.Save()
|
||||
// (which usually doesn't need to be called manually, as the library does that automatically when relevant).
|
||||
func (c *Container) PutDevice(device *store.Device) error {
|
||||
@@ -229,7 +241,7 @@ func (c *Container) PutDevice(device *store.Device) error {
|
||||
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.AccountSignatureKey, device.Account.DeviceSignature,
|
||||
device.Platform, device.BusinessName, device.PushName)
|
||||
device.Platform, device.BusinessName, device.PushName, uuid.NullUUID{UUID: device.FacebookUUID, Valid: device.FacebookUUID != uuid.Nil})
|
||||
|
||||
if !device.Initialized {
|
||||
innerStore := NewSQLStore(c, *device.ID)
|
||||
|
||||
+22
-1
@@ -8,6 +8,7 @@ package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type upgradeFunc func(*sql.Tx, *Container) error
|
||||
@@ -16,7 +17,7 @@ type upgradeFunc func(*sql.Tx, *Container) error
|
||||
//
|
||||
// This may be of use if you want to manage the database fully manually, but in most cases you
|
||||
// should just call Container.Upgrade to let the library handle everything.
|
||||
var Upgrades = [...]upgradeFunc{upgradeV1, upgradeV2, upgradeV3, upgradeV4}
|
||||
var Upgrades = [...]upgradeFunc{upgradeV1, upgradeV2, upgradeV3, upgradeV4, upgradeV5, upgradeV6}
|
||||
|
||||
func (c *Container) getVersion() (int, error) {
|
||||
_, err := c.db.Exec("CREATE TABLE IF NOT EXISTS whatsmeow_version (version INTEGER)")
|
||||
@@ -43,6 +44,16 @@ func (c *Container) setVersion(tx *sql.Tx, version int) error {
|
||||
|
||||
// Upgrade upgrades the database from the current to the latest version available.
|
||||
func (c *Container) Upgrade() error {
|
||||
if c.dialect == "sqlite" {
|
||||
var foreignKeysEnabled bool
|
||||
err := c.db.QueryRow("PRAGMA foreign_keys").Scan(&foreignKeysEnabled)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if foreign keys are enabled: %w", err)
|
||||
} else if !foreignKeysEnabled {
|
||||
return fmt.Errorf("foreign keys are not enabled")
|
||||
}
|
||||
}
|
||||
|
||||
version, err := c.getVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -271,3 +282,13 @@ func upgradeV4(tx *sql.Tx, container *Container) error {
|
||||
)`)
|
||||
return err
|
||||
}
|
||||
|
||||
func upgradeV5(tx *sql.Tx, container *Container) error {
|
||||
_, err := tx.Exec("UPDATE whatsmeow_device SET jid=REPLACE(jid, '.0', '')")
|
||||
return err
|
||||
}
|
||||
|
||||
func upgradeV6(tx *sql.Tx, container *Container) error {
|
||||
_, err := tx.Exec("ALTER TABLE whatsmeow_device ADD COLUMN facebook_uuid uuid")
|
||||
return err
|
||||
}
|
||||
|
||||
+4
@@ -11,6 +11,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/util/keys"
|
||||
@@ -139,6 +141,8 @@ type Device struct {
|
||||
BusinessName string
|
||||
PushName string
|
||||
|
||||
FacebookUUID uuid.UUID
|
||||
|
||||
Initialized bool
|
||||
Identities IdentityStore
|
||||
Sessions SessionStore
|
||||
|
||||
@@ -142,6 +142,36 @@ type UserStatusMute struct {
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// LabelEdit is emitted when a label is edited from any device.
|
||||
type LabelEdit struct {
|
||||
Timestamp time.Time // The time when the label was edited.
|
||||
LabelID string // The label id which was edited.
|
||||
|
||||
Action *waProto.LabelEditAction // The new label info.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// LabelAssociationChat is emitted when a chat is labeled or unlabeled from any device.
|
||||
type LabelAssociationChat struct {
|
||||
JID types.JID // The chat which was labeled or unlabeled.
|
||||
Timestamp time.Time // The time when the (un)labeling happened.
|
||||
LabelID string // The label id which was added or removed.
|
||||
|
||||
Action *waProto.LabelAssociationAction // The current label status of the chat.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// LabelAssociationMessage is emitted when a message is labeled or unlabeled from any device.
|
||||
type LabelAssociationMessage struct {
|
||||
JID types.JID // The chat which was labeled or unlabeled.
|
||||
Timestamp time.Time // The time when the (un)labeling happened.
|
||||
LabelID string // The label id which was added or removed.
|
||||
MessageID string // The message id which was labeled or unlabeled.
|
||||
|
||||
Action *waProto.LabelAssociationAction // The current label status of the message.
|
||||
FromFullSync bool // Whether the action is emitted because of a fullSync
|
||||
}
|
||||
|
||||
// AppState is emitted directly for new data received from app state syncing.
|
||||
// You should generally use the higher-level events like events.Contact and events.Mute.
|
||||
type AppState struct {
|
||||
|
||||
+14
@@ -27,6 +27,20 @@ type CallAccept struct {
|
||||
Data *waBinary.Node
|
||||
}
|
||||
|
||||
type CallPreAccept struct {
|
||||
types.BasicCallMeta
|
||||
types.CallRemoteMeta
|
||||
|
||||
Data *waBinary.Node
|
||||
}
|
||||
|
||||
type CallTransport struct {
|
||||
types.BasicCallMeta
|
||||
types.CallRemoteMeta
|
||||
|
||||
Data *waBinary.Node
|
||||
}
|
||||
|
||||
// CallOfferNotice is emitted when the user receives a notice of a call on WhatsApp.
|
||||
// This seems to be primarily for group calls (whereas CallOffer is for 1:1 calls).
|
||||
type CallOfferNotice struct {
|
||||
|
||||
+121
-42
@@ -9,9 +9,13 @@ package events
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgApplication"
|
||||
"go.mau.fi/whatsmeow/binary/armadillo/waMsgTransport"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
@@ -69,6 +73,22 @@ type KeepAliveTimeout struct {
|
||||
// Note that if the websocket disconnects before the pings start working, this event will not be emitted.
|
||||
type KeepAliveRestored struct{}
|
||||
|
||||
// PermanentDisconnect is a class of events emitted when the client will not auto-reconnect by default.
|
||||
type PermanentDisconnect interface {
|
||||
PermanentDisconnectDescription() string
|
||||
}
|
||||
|
||||
func (l *LoggedOut) PermanentDisconnectDescription() string { return l.Reason.String() }
|
||||
func (*StreamReplaced) PermanentDisconnectDescription() string { return "stream replaced" }
|
||||
func (*ClientOutdated) PermanentDisconnectDescription() string { return "client outdated" }
|
||||
func (*CATRefreshError) PermanentDisconnectDescription() string { return "CAT refresh failed" }
|
||||
func (tb *TemporaryBan) PermanentDisconnectDescription() string {
|
||||
return fmt.Sprintf("temporarily banned: %s", tb.String())
|
||||
}
|
||||
func (cf *ConnectFailure) PermanentDisconnectDescription() string {
|
||||
return fmt.Sprintf("connect failure: %s", cf.Reason.String())
|
||||
}
|
||||
|
||||
// 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).
|
||||
@@ -133,20 +153,22 @@ func (tb *TemporaryBan) String() string {
|
||||
type ConnectFailureReason int
|
||||
|
||||
const (
|
||||
ConnectFailureGeneric ConnectFailureReason = 400
|
||||
ConnectFailureLoggedOut ConnectFailureReason = 401
|
||||
ConnectFailureTempBanned ConnectFailureReason = 402
|
||||
ConnectFailureMainDeviceGone ConnectFailureReason = 403
|
||||
ConnectFailureUnknownLogout ConnectFailureReason = 406
|
||||
ConnectFailureMainDeviceGone ConnectFailureReason = 403 // this is now called LOCKED in the whatsapp web code
|
||||
ConnectFailureUnknownLogout ConnectFailureReason = 406 // this is now called BANNED in the whatsapp web code
|
||||
|
||||
ConnectFailureClientOutdated ConnectFailureReason = 405
|
||||
ConnectFailureBadUserAgent ConnectFailureReason = 409
|
||||
|
||||
// 400, 500 and 501 are also existing codes, but the meaning is unknown
|
||||
ConnectFailureCATExpired ConnectFailureReason = 413
|
||||
ConnectFailureCATInvalid ConnectFailureReason = 414
|
||||
ConnectFailureNotFound ConnectFailureReason = 415
|
||||
|
||||
// 503 doesn't seem to be included in the web app JS with the other codes, and it's very rare,
|
||||
// but does happen after a 503 stream error sometimes.
|
||||
|
||||
ConnectFailureServiceUnavailable ConnectFailureReason = 503
|
||||
ConnectFailureInternalServerError ConnectFailureReason = 500
|
||||
ConnectFailureExperimental ConnectFailureReason = 501
|
||||
ConnectFailureServiceUnavailable ConnectFailureReason = 503
|
||||
)
|
||||
|
||||
var connectFailureReasonMessage = map[ConnectFailureReason]string{
|
||||
@@ -156,6 +178,8 @@ var connectFailureReasonMessage = map[ConnectFailureReason]string{
|
||||
ConnectFailureUnknownLogout: "logged out for unknown reason",
|
||||
ConnectFailureClientOutdated: "client is out of date",
|
||||
ConnectFailureBadUserAgent: "client user agent was rejected",
|
||||
ConnectFailureCATExpired: "messenger crypto auth token has expired",
|
||||
ConnectFailureCATInvalid: "messenger crypto auth token is invalid",
|
||||
}
|
||||
|
||||
// IsLoggedOut returns true if the client should delete session data due to this connect failure.
|
||||
@@ -163,6 +187,10 @@ func (cfr ConnectFailureReason) IsLoggedOut() bool {
|
||||
return cfr == ConnectFailureLoggedOut || cfr == ConnectFailureMainDeviceGone || cfr == ConnectFailureUnknownLogout
|
||||
}
|
||||
|
||||
func (cfr ConnectFailureReason) NumberString() string {
|
||||
return strconv.Itoa(int(cfr))
|
||||
}
|
||||
|
||||
// String returns the reason code and a short human-readable description of the error.
|
||||
func (cfr ConnectFailureReason) String() string {
|
||||
msg, ok := connectFailureReasonMessage[cfr]
|
||||
@@ -184,6 +212,10 @@ type ConnectFailure struct {
|
||||
// ClientOutdated is emitted when the WhatsApp server rejects the connection with the ConnectFailureClientOutdated code.
|
||||
type ClientOutdated struct{}
|
||||
|
||||
type CATRefreshError struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
// StreamError is emitted when the WhatsApp server sends a <stream:error> node with an unknown code.
|
||||
//
|
||||
// Known codes are handled internally and emitted as different events (e.g. LoggedOut).
|
||||
@@ -223,6 +255,14 @@ type UndecryptableMessage struct {
|
||||
DecryptFailMode DecryptFailMode
|
||||
}
|
||||
|
||||
type NewsletterMessageMeta struct {
|
||||
// When a newsletter message is edited, the message isn't wrapped in an EditedMessage like normal messages.
|
||||
// Instead, the message is the new content, the ID is the original message ID, and the edit timestamp is here.
|
||||
EditTS time.Time
|
||||
// This is the timestamp of the original message for edits.
|
||||
OriginalTS time.Time
|
||||
}
|
||||
|
||||
// Message is emitted when receiving a new message.
|
||||
type Message struct {
|
||||
Info types.MessageInfo // Information about the message like the chat and sender IDs
|
||||
@@ -241,11 +281,24 @@ type Message struct {
|
||||
// If the message was re-requested from the sender, this is the number of retries it took.
|
||||
RetryCount int
|
||||
|
||||
NewsletterMeta *NewsletterMessageMeta
|
||||
|
||||
// The raw message struct. This is the raw unmodified data, which means the actual message might
|
||||
// be wrapped in DeviceSentMessage, EphemeralMessage or ViewOnceMessage.
|
||||
RawMessage *waProto.Message
|
||||
}
|
||||
|
||||
type FBMessage struct {
|
||||
Info types.MessageInfo // Information about the message like the chat and sender IDs
|
||||
Message armadillo.MessageApplicationSub // The actual message struct
|
||||
|
||||
// If the message was re-requested from the sender, this is the number of retries it took.
|
||||
RetryCount int
|
||||
|
||||
Transport *waMsgTransport.MessageTransport // The first level of wrapping the message was in
|
||||
Application *waMsgApplication.MessageApplication // The second level of wrapping the message was in
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -280,44 +333,19 @@ func (evt *Message) UnwrapRaw() *Message {
|
||||
return evt
|
||||
}
|
||||
|
||||
// ReceiptType represents the type of a Receipt event.
|
||||
type ReceiptType string
|
||||
// Deprecated: use types.ReceiptType directly
|
||||
type ReceiptType = types.ReceiptType
|
||||
|
||||
// Deprecated: use types.ReceiptType* constants directly
|
||||
const (
|
||||
// ReceiptTypeDelivered means the message was delivered to the device (but the user might not have noticed).
|
||||
ReceiptTypeDelivered ReceiptType = ""
|
||||
// ReceiptTypeSender is sent by your other devices when a message you sent is delivered to them.
|
||||
ReceiptTypeSender ReceiptType = "sender"
|
||||
// ReceiptTypeRetry means the message was delivered to the device, but decrypting the message failed.
|
||||
ReceiptTypeRetry ReceiptType = "retry"
|
||||
// ReceiptTypeRead means the user opened the chat and saw the message.
|
||||
ReceiptTypeRead ReceiptType = "read"
|
||||
// ReceiptTypeReadSelf means the current user read a message from a different device, and has read receipts disabled in privacy settings.
|
||||
ReceiptTypeReadSelf ReceiptType = "read-self"
|
||||
// ReceiptTypePlayed means the user opened a view-once media message.
|
||||
//
|
||||
// This is dispatched for both incoming and outgoing messages when played. If the current user opened the media,
|
||||
// it means the media should be removed from all devices. If a recipient opened the media, it's just a notification
|
||||
// for the sender that the media was viewed.
|
||||
ReceiptTypePlayed ReceiptType = "played"
|
||||
ReceiptTypeDelivered = types.ReceiptTypeDelivered
|
||||
ReceiptTypeSender = types.ReceiptTypeSender
|
||||
ReceiptTypeRetry = types.ReceiptTypeRetry
|
||||
ReceiptTypeRead = types.ReceiptTypeRead
|
||||
ReceiptTypeReadSelf = types.ReceiptTypeReadSelf
|
||||
ReceiptTypePlayed = types.ReceiptTypePlayed
|
||||
)
|
||||
|
||||
// GoString returns the name of the Go constant for the ReceiptType value.
|
||||
func (rt ReceiptType) GoString() string {
|
||||
switch rt {
|
||||
case ReceiptTypeRead:
|
||||
return "events.ReceiptTypeRead"
|
||||
case ReceiptTypeReadSelf:
|
||||
return "events.ReceiptTypeReadSelf"
|
||||
case ReceiptTypeDelivered:
|
||||
return "events.ReceiptTypeDelivered"
|
||||
case ReceiptTypePlayed:
|
||||
return "events.ReceiptTypePlayed"
|
||||
default:
|
||||
return fmt.Sprintf("events.ReceiptType(%#v)", string(rt))
|
||||
}
|
||||
}
|
||||
|
||||
// Receipt is emitted when an outgoing message is delivered to or read by another user, or when another device reads an incoming message.
|
||||
//
|
||||
// N.B. WhatsApp on Android sends message IDs from newest message to oldest, but WhatsApp on iOS sends them in the opposite order (oldest first).
|
||||
@@ -325,7 +353,7 @@ type Receipt struct {
|
||||
types.MessageSource
|
||||
MessageIDs []types.MessageID
|
||||
Timestamp time.Time
|
||||
Type ReceiptType
|
||||
Type types.ReceiptType
|
||||
}
|
||||
|
||||
// ChatPresence is emitted when a chat state update (also known as typing notification) is received.
|
||||
@@ -424,6 +452,8 @@ type PrivacySettings struct {
|
||||
StatusChanged bool
|
||||
ProfileChanged bool
|
||||
ReadReceiptsChanged bool
|
||||
OnlineChanged bool
|
||||
CallAddChanged bool
|
||||
}
|
||||
|
||||
// OfflineSyncPreview is emitted right after connecting if the server is going to send events that the client missed during downtime.
|
||||
@@ -460,3 +490,52 @@ type MediaRetry struct {
|
||||
SenderID types.JID // The user who sent the message. Only present in groups.
|
||||
FromMe bool // Whether the message was sent by the current user or someone else.
|
||||
}
|
||||
|
||||
type BlocklistAction string
|
||||
|
||||
const (
|
||||
BlocklistActionDefault BlocklistAction = ""
|
||||
BlocklistActionModify BlocklistAction = "modify"
|
||||
)
|
||||
|
||||
// Blocklist is emitted when the user's blocked user list is changed.
|
||||
type Blocklist struct {
|
||||
// Action specifies what happened. If it's empty, there should be a list of changes in the Changes list.
|
||||
// If it's "modify", then the Changes list will be empty and the whole blocklist should be re-requested.
|
||||
Action BlocklistAction
|
||||
DHash string
|
||||
PrevDHash string
|
||||
Changes []BlocklistChange
|
||||
}
|
||||
|
||||
type BlocklistChangeAction string
|
||||
|
||||
const (
|
||||
BlocklistChangeActionBlock BlocklistChangeAction = "block"
|
||||
BlocklistChangeActionUnblock BlocklistChangeAction = "unblock"
|
||||
)
|
||||
|
||||
type BlocklistChange struct {
|
||||
JID types.JID
|
||||
Action BlocklistChangeAction
|
||||
}
|
||||
|
||||
type NewsletterJoin struct {
|
||||
types.NewsletterMetadata
|
||||
}
|
||||
|
||||
type NewsletterLeave struct {
|
||||
ID types.JID `json:"id"`
|
||||
Role types.NewsletterRole `json:"role"`
|
||||
}
|
||||
|
||||
type NewsletterMuteChange struct {
|
||||
ID types.JID `json:"id"`
|
||||
Mute types.NewsletterMuteState `json:"mute"`
|
||||
}
|
||||
|
||||
type NewsletterLiveUpdate struct {
|
||||
JID types.JID
|
||||
Time time.Time
|
||||
Messages []*types.NewsletterMessage
|
||||
}
|
||||
|
||||
+9
@@ -26,6 +26,7 @@ type GroupInfo struct {
|
||||
GroupLocked
|
||||
GroupAnnounce
|
||||
GroupEphemeral
|
||||
GroupIncognito
|
||||
|
||||
GroupParent
|
||||
GroupLinkedParent
|
||||
@@ -79,12 +80,20 @@ type GroupAnnounce struct {
|
||||
AnnounceVersionID string
|
||||
}
|
||||
|
||||
type GroupIncognito struct {
|
||||
IsIncognito bool
|
||||
}
|
||||
|
||||
// GroupParticipant contains info about a participant of a WhatsApp group chat.
|
||||
type GroupParticipant struct {
|
||||
JID JID
|
||||
LID JID
|
||||
IsAdmin bool
|
||||
IsSuperAdmin bool
|
||||
|
||||
// This is only present for anonymous users in announcement groups, it's an obfuscated phone number
|
||||
DisplayName string
|
||||
|
||||
// When creating groups, adding some participants may fail.
|
||||
// In such cases, the error code will be here.
|
||||
Error int
|
||||
|
||||
+99
-54
@@ -24,6 +24,10 @@ const (
|
||||
LegacyUserServer = "c.us"
|
||||
BroadcastServer = "broadcast"
|
||||
HiddenUserServer = "lid"
|
||||
MessengerServer = "msgr"
|
||||
InteropServer = "interop"
|
||||
NewsletterServer = "newsletter"
|
||||
HostedServer = "hosted"
|
||||
)
|
||||
|
||||
// Some JIDs that are contacted often.
|
||||
@@ -40,17 +44,31 @@ var (
|
||||
// MessageID is the internal ID of a WhatsApp message.
|
||||
type MessageID = string
|
||||
|
||||
// MessageServerID is the server ID of a WhatsApp newsletter message.
|
||||
type MessageServerID = int
|
||||
|
||||
// JID represents a WhatsApp user ID.
|
||||
//
|
||||
// There are two types of JIDs: regular JID pairs (user and server) and AD-JIDs (user, agent and device).
|
||||
// AD JIDs are only used to refer to specific devices of users, so the server is always s.whatsapp.net (DefaultUserServer).
|
||||
// Regular JIDs can be used for entities on any servers (users, groups, broadcasts).
|
||||
type JID struct {
|
||||
User string
|
||||
Agent uint8
|
||||
Device uint8
|
||||
Server string
|
||||
AD bool
|
||||
User string
|
||||
RawAgent uint8
|
||||
Device uint16
|
||||
Integrator uint16
|
||||
Server string
|
||||
}
|
||||
|
||||
func (jid JID) ActualAgent() uint8 {
|
||||
switch jid.Server {
|
||||
case DefaultUserServer:
|
||||
return 0
|
||||
case HiddenUserServer:
|
||||
return 1
|
||||
default:
|
||||
return jid.RawAgent
|
||||
}
|
||||
}
|
||||
|
||||
// UserInt returns the user as an integer. This is only safe to run on normal users, not on groups or broadcast lists.
|
||||
@@ -61,23 +79,27 @@ func (jid JID) UserInt() uint64 {
|
||||
|
||||
// ToNonAD returns a version of the JID struct that doesn't have the agent and device set.
|
||||
func (jid JID) ToNonAD() JID {
|
||||
if jid.AD {
|
||||
return JID{
|
||||
User: jid.User,
|
||||
Server: DefaultUserServer,
|
||||
}
|
||||
} else {
|
||||
return jid
|
||||
return JID{
|
||||
User: jid.User,
|
||||
Server: jid.Server,
|
||||
Integrator: jid.Integrator,
|
||||
}
|
||||
}
|
||||
|
||||
// SignalAddress returns the Signal protocol address for the user.
|
||||
func (jid JID) SignalAddress() *signalProtocol.SignalAddress {
|
||||
user := jid.User
|
||||
if jid.Agent != 0 {
|
||||
user = fmt.Sprintf("%s_%d", jid.User, jid.Agent)
|
||||
agent := jid.ActualAgent()
|
||||
if agent != 0 {
|
||||
user = fmt.Sprintf("%s_%d", jid.User, agent)
|
||||
}
|
||||
return signalProtocol.NewSignalAddress(user, uint32(jid.Device))
|
||||
// TODO use @lid suffix instead of agent?
|
||||
//suffix := ""
|
||||
//if jid.Server == HiddenUserServer {
|
||||
// suffix = "@lid"
|
||||
//}
|
||||
//return signalProtocol.NewSignalAddress(user, uint32(jid.Device), suffix)
|
||||
}
|
||||
|
||||
// IsBroadcastList returns true if the JID is a broadcast list, but not the status broadcast.
|
||||
@@ -87,53 +109,70 @@ func (jid JID) IsBroadcastList() bool {
|
||||
|
||||
// NewADJID creates a new AD JID.
|
||||
func NewADJID(user string, agent, device uint8) JID {
|
||||
var server string
|
||||
switch agent {
|
||||
case 0:
|
||||
server = DefaultUserServer
|
||||
case 1:
|
||||
server = HiddenUserServer
|
||||
agent = 0
|
||||
default:
|
||||
if (agent&0x01) != 0 || (agent&0x80) == 0 { // agent % 2 == 0 || agent < 128?
|
||||
// TODO invalid JID?
|
||||
}
|
||||
server = HostedServer
|
||||
}
|
||||
return JID{
|
||||
User: user,
|
||||
Agent: agent,
|
||||
Device: device,
|
||||
Server: DefaultUserServer,
|
||||
AD: true,
|
||||
User: user,
|
||||
RawAgent: agent,
|
||||
Device: uint16(device),
|
||||
Server: server,
|
||||
}
|
||||
}
|
||||
|
||||
func parseADJID(user string) (JID, error) {
|
||||
var fullJID JID
|
||||
fullJID.AD = true
|
||||
fullJID.Server = DefaultUserServer
|
||||
|
||||
dotIndex := strings.IndexRune(user, '.')
|
||||
colonIndex := strings.IndexRune(user, ':')
|
||||
if dotIndex < 0 || colonIndex < 0 || colonIndex+1 <= dotIndex {
|
||||
return fullJID, fmt.Errorf("failed to parse ADJID: missing separators")
|
||||
}
|
||||
|
||||
fullJID.User = user[:dotIndex]
|
||||
agent, err := strconv.Atoi(user[dotIndex+1 : colonIndex])
|
||||
if err != nil {
|
||||
return fullJID, fmt.Errorf("failed to parse agent from JID: %w", err)
|
||||
} else if agent < 0 || agent > 255 {
|
||||
return fullJID, fmt.Errorf("failed to parse agent from JID: invalid value (%d)", agent)
|
||||
}
|
||||
device, err := strconv.Atoi(user[colonIndex+1:])
|
||||
if err != nil {
|
||||
return fullJID, fmt.Errorf("failed to parse device from JID: %w", err)
|
||||
} else if device < 0 || device > 255 {
|
||||
return fullJID, fmt.Errorf("failed to parse device from JID: invalid value (%d)", device)
|
||||
}
|
||||
fullJID.Agent = uint8(agent)
|
||||
fullJID.Device = uint8(device)
|
||||
return fullJID, nil
|
||||
}
|
||||
|
||||
// ParseJID parses a JID out of the given string. It supports both regular and AD JIDs.
|
||||
func ParseJID(jid string) (JID, error) {
|
||||
parts := strings.Split(jid, "@")
|
||||
if len(parts) == 1 {
|
||||
return NewJID("", parts[0]), nil
|
||||
} else if strings.ContainsRune(parts[0], ':') && strings.ContainsRune(parts[0], '.') && parts[1] == DefaultUserServer {
|
||||
return parseADJID(parts[0])
|
||||
}
|
||||
return NewJID(parts[0], parts[1]), nil
|
||||
parsedJID := JID{User: parts[0], Server: parts[1]}
|
||||
if strings.ContainsRune(parsedJID.User, '.') {
|
||||
parts = strings.Split(parsedJID.User, ".")
|
||||
if len(parts) != 2 {
|
||||
return parsedJID, fmt.Errorf("unexpected number of dots in JID")
|
||||
}
|
||||
parsedJID.User = parts[0]
|
||||
ad := parts[1]
|
||||
parts = strings.Split(ad, ":")
|
||||
if len(parts) > 2 {
|
||||
return parsedJID, fmt.Errorf("unexpected number of colons in JID")
|
||||
}
|
||||
agent, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return parsedJID, fmt.Errorf("failed to parse device from JID: %w", err)
|
||||
}
|
||||
parsedJID.RawAgent = uint8(agent)
|
||||
if len(parts) == 2 {
|
||||
device, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return parsedJID, fmt.Errorf("failed to parse device from JID: %w", err)
|
||||
}
|
||||
parsedJID.Device = uint16(device)
|
||||
}
|
||||
} else if strings.ContainsRune(parsedJID.User, ':') {
|
||||
parts = strings.Split(parsedJID.User, ":")
|
||||
if len(parts) != 2 {
|
||||
return parsedJID, fmt.Errorf("unexpected number of colons in JID")
|
||||
}
|
||||
parsedJID.User = parts[0]
|
||||
device, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return parsedJID, fmt.Errorf("failed to parse device from JID: %w", err)
|
||||
}
|
||||
parsedJID.Device = uint16(device)
|
||||
}
|
||||
return parsedJID, nil
|
||||
}
|
||||
|
||||
// NewJID creates a new regular JID.
|
||||
@@ -144,11 +183,17 @@ func NewJID(user, server string) JID {
|
||||
}
|
||||
}
|
||||
|
||||
func (jid JID) ADString() string {
|
||||
return fmt.Sprintf("%s.%d:%d@%s", jid.User, jid.RawAgent, jid.Device, jid.Server)
|
||||
}
|
||||
|
||||
// String converts the JID to a string representation.
|
||||
// The output string can be parsed with ParseJID, except for JIDs with no User part specified.
|
||||
// The output string can be parsed with ParseJID.
|
||||
func (jid JID) String() string {
|
||||
if jid.AD {
|
||||
return fmt.Sprintf("%s.%d:%d@%s", jid.User, jid.Agent, jid.Device, jid.Server)
|
||||
if jid.RawAgent > 0 {
|
||||
return fmt.Sprintf("%s.%d:%d@%s", jid.User, jid.RawAgent, jid.Device, jid.Server)
|
||||
} else if jid.Device > 0 {
|
||||
return fmt.Sprintf("%s:%d@%s", jid.User, jid.Device, jid.Server)
|
||||
} else if len(jid.User) > 0 {
|
||||
return fmt.Sprintf("%s@%s", jid.User, jid.Server)
|
||||
} else {
|
||||
|
||||
+14
-1
@@ -36,16 +36,29 @@ type DeviceSentMeta struct {
|
||||
Phash string
|
||||
}
|
||||
|
||||
type EditAttribute string
|
||||
|
||||
const (
|
||||
EditAttributeEmpty EditAttribute = ""
|
||||
EditAttributeMessageEdit EditAttribute = "1"
|
||||
EditAttributePinInChat EditAttribute = "2"
|
||||
EditAttributeAdminEdit EditAttribute = "3" // only used in newsletters
|
||||
EditAttributeSenderRevoke EditAttribute = "7"
|
||||
EditAttributeAdminRevoke EditAttribute = "8"
|
||||
)
|
||||
|
||||
// MessageInfo contains metadata about an incoming message.
|
||||
type MessageInfo struct {
|
||||
MessageSource
|
||||
ID string
|
||||
ID MessageID
|
||||
ServerID MessageServerID
|
||||
Type string
|
||||
PushName string
|
||||
Timestamp time.Time
|
||||
Category string
|
||||
Multicast bool
|
||||
MediaType string
|
||||
Edit EditAttribute
|
||||
|
||||
VerifiedName *VerifiedName
|
||||
DeviceSentMeta *DeviceSentMeta // Metadata for direct messages sent from another one of the user's own devices.
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2023 Tulir Asokan
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"go.mau.fi/util/jsontime"
|
||||
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
)
|
||||
|
||||
type NewsletterVerificationState string
|
||||
|
||||
func (nvs *NewsletterVerificationState) UnmarshalText(text []byte) error {
|
||||
*nvs = NewsletterVerificationState(bytes.ToLower(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
NewsletterVerificationStateVerified NewsletterVerificationState = "verified"
|
||||
NewsletterVerificationStateUnverified NewsletterVerificationState = "unverified"
|
||||
)
|
||||
|
||||
type NewsletterPrivacy string
|
||||
|
||||
func (np *NewsletterPrivacy) UnmarshalText(text []byte) error {
|
||||
*np = NewsletterPrivacy(bytes.ToLower(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
NewsletterPrivacyPrivate NewsletterPrivacy = "private"
|
||||
NewsletterPrivacyPublic NewsletterPrivacy = "public"
|
||||
)
|
||||
|
||||
type NewsletterReactionsMode string
|
||||
|
||||
const (
|
||||
NewsletterReactionsModeAll NewsletterReactionsMode = "all"
|
||||
NewsletterReactionsModeBasic NewsletterReactionsMode = "basic"
|
||||
NewsletterReactionsModeNone NewsletterReactionsMode = "none"
|
||||
NewsletterReactionsModeBlocklist NewsletterReactionsMode = "blocklist"
|
||||
)
|
||||
|
||||
type NewsletterState string
|
||||
|
||||
func (ns *NewsletterState) UnmarshalText(text []byte) error {
|
||||
*ns = NewsletterState(bytes.ToLower(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
NewsletterStateActive NewsletterState = "active"
|
||||
NewsletterStateSuspended NewsletterState = "suspended"
|
||||
NewsletterStateGeoSuspended NewsletterState = "geosuspended"
|
||||
)
|
||||
|
||||
type NewsletterMuted struct {
|
||||
Muted bool
|
||||
}
|
||||
|
||||
type WrappedNewsletterState struct {
|
||||
Type NewsletterState `json:"type"`
|
||||
}
|
||||
|
||||
type NewsletterMuteState string
|
||||
|
||||
func (nms *NewsletterMuteState) UnmarshalText(text []byte) error {
|
||||
*nms = NewsletterMuteState(bytes.ToLower(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
NewsletterMuteOn NewsletterMuteState = "on"
|
||||
NewsletterMuteOff NewsletterMuteState = "off"
|
||||
)
|
||||
|
||||
type NewsletterRole string
|
||||
|
||||
func (nr *NewsletterRole) UnmarshalText(text []byte) error {
|
||||
*nr = NewsletterRole(bytes.ToLower(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
NewsletterRoleSubscriber NewsletterRole = "subscriber"
|
||||
NewsletterRoleGuest NewsletterRole = "guest"
|
||||
NewsletterRoleAdmin NewsletterRole = "admin"
|
||||
NewsletterRoleOwner NewsletterRole = "owner"
|
||||
)
|
||||
|
||||
type NewsletterMetadata struct {
|
||||
ID JID `json:"id"`
|
||||
State WrappedNewsletterState `json:"state"`
|
||||
ThreadMeta NewsletterThreadMetadata `json:"thread_metadata"`
|
||||
ViewerMeta *NewsletterViewerMetadata `json:"viewer_metadata"`
|
||||
}
|
||||
|
||||
type NewsletterViewerMetadata struct {
|
||||
Mute NewsletterMuteState `json:"mute"`
|
||||
Role NewsletterRole `json:"role"`
|
||||
}
|
||||
|
||||
type NewsletterKeyType string
|
||||
|
||||
const (
|
||||
NewsletterKeyTypeJID NewsletterKeyType = "JID"
|
||||
NewsletterKeyTypeInvite NewsletterKeyType = "INVITE"
|
||||
)
|
||||
|
||||
type NewsletterReactionSettings struct {
|
||||
Value NewsletterReactionsMode `json:"value"`
|
||||
}
|
||||
|
||||
type NewsletterSettings struct {
|
||||
ReactionCodes NewsletterReactionSettings `json:"reaction_codes"`
|
||||
}
|
||||
|
||||
type NewsletterThreadMetadata struct {
|
||||
CreationTime jsontime.UnixString `json:"creation_time"`
|
||||
InviteCode string `json:"invite"`
|
||||
Name NewsletterText `json:"name"`
|
||||
Description NewsletterText `json:"description"`
|
||||
SubscriberCount int `json:"subscribers_count,string"`
|
||||
VerificationState NewsletterVerificationState `json:"verification"`
|
||||
Picture *ProfilePictureInfo `json:"picture"`
|
||||
Preview ProfilePictureInfo `json:"preview"`
|
||||
Settings NewsletterSettings `json:"settings"`
|
||||
|
||||
//NewsletterMuted `json:"-"`
|
||||
//PrivacyType NewsletterPrivacy `json:"-"`
|
||||
//ReactionsMode NewsletterReactionsMode `json:"-"`
|
||||
//State NewsletterState `json:"-"`
|
||||
}
|
||||
|
||||
type NewsletterText struct {
|
||||
Text string `json:"text"`
|
||||
ID string `json:"id"`
|
||||
UpdateTime jsontime.UnixMicroString `json:"update_time"`
|
||||
}
|
||||
|
||||
type NewsletterMessage struct {
|
||||
MessageServerID MessageServerID
|
||||
ViewsCount int
|
||||
ReactionCounts map[string]int
|
||||
|
||||
// This is only present when fetching messages, not in live updates
|
||||
Message *waProto.Message
|
||||
}
|
||||
|
||||
type GraphQLErrorExtensions struct {
|
||||
ErrorCode int `json:"error_code"`
|
||||
IsRetryable bool `json:"is_retryable"`
|
||||
Severity string `json:"severity"`
|
||||
}
|
||||
|
||||
type GraphQLError struct {
|
||||
Extensions GraphQLErrorExtensions `json:"extensions"`
|
||||
Message string `json:"message"`
|
||||
Path []string `json:"path"`
|
||||
}
|
||||
|
||||
func (gqle GraphQLError) Error() string {
|
||||
return fmt.Sprintf("%d %s (%s)", gqle.Extensions.ErrorCode, gqle.Message, gqle.Extensions.Severity)
|
||||
}
|
||||
|
||||
type GraphQLErrors []GraphQLError
|
||||
|
||||
func (gqles GraphQLErrors) Unwrap() []error {
|
||||
errs := make([]error, len(gqles))
|
||||
for i, gqle := range gqles {
|
||||
errs[i] = gqle
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func (gqles GraphQLErrors) Error() string {
|
||||
if len(gqles) == 0 {
|
||||
return ""
|
||||
} else if len(gqles) == 1 {
|
||||
return gqles[0].Error()
|
||||
} else {
|
||||
return fmt.Sprintf("%v (and %d other errors)", gqles[0], len(gqles)-1)
|
||||
}
|
||||
}
|
||||
|
||||
type GraphQLResponse struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
Errors GraphQLErrors `json:"errors"`
|
||||
}
|
||||
+50
@@ -6,6 +6,10 @@
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Presence string
|
||||
|
||||
const (
|
||||
@@ -26,3 +30,49 @@ const (
|
||||
ChatPresenceMediaText ChatPresenceMedia = ""
|
||||
ChatPresenceMediaAudio ChatPresenceMedia = "audio"
|
||||
)
|
||||
|
||||
// ReceiptType represents the type of a Receipt event.
|
||||
type ReceiptType string
|
||||
|
||||
const (
|
||||
// ReceiptTypeDelivered means the message was delivered to the device (but the user might not have noticed).
|
||||
ReceiptTypeDelivered ReceiptType = ""
|
||||
// ReceiptTypeSender is sent by your other devices when a message you sent is delivered to them.
|
||||
ReceiptTypeSender ReceiptType = "sender"
|
||||
// ReceiptTypeRetry means the message was delivered to the device, but decrypting the message failed.
|
||||
ReceiptTypeRetry ReceiptType = "retry"
|
||||
// ReceiptTypeRead means the user opened the chat and saw the message.
|
||||
ReceiptTypeRead ReceiptType = "read"
|
||||
// ReceiptTypeReadSelf means the current user read a message from a different device, and has read receipts disabled in privacy settings.
|
||||
ReceiptTypeReadSelf ReceiptType = "read-self"
|
||||
// ReceiptTypePlayed means the user opened a view-once media message.
|
||||
//
|
||||
// This is dispatched for both incoming and outgoing messages when played. If the current user opened the media,
|
||||
// it means the media should be removed from all devices. If a recipient opened the media, it's just a notification
|
||||
// for the sender that the media was viewed.
|
||||
ReceiptTypePlayed ReceiptType = "played"
|
||||
// ReceiptTypePlayedSelf probably means the current user opened a view-once media message from a different device,
|
||||
// and has read receipts disabled in privacy settings.
|
||||
ReceiptTypePlayedSelf ReceiptType = "played-self"
|
||||
|
||||
ReceiptTypeServerError ReceiptType = "server-error"
|
||||
ReceiptTypeInactive ReceiptType = "inactive"
|
||||
ReceiptTypePeerMsg ReceiptType = "peer_msg"
|
||||
ReceiptTypeHistorySync ReceiptType = "hist_sync"
|
||||
)
|
||||
|
||||
// GoString returns the name of the Go constant for the ReceiptType value.
|
||||
func (rt ReceiptType) GoString() string {
|
||||
switch rt {
|
||||
case ReceiptTypeRead:
|
||||
return "types.ReceiptTypeRead"
|
||||
case ReceiptTypeReadSelf:
|
||||
return "types.ReceiptTypeReadSelf"
|
||||
case ReceiptTypeDelivered:
|
||||
return "types.ReceiptTypeDelivered"
|
||||
case ReceiptTypePlayed:
|
||||
return "types.ReceiptTypePlayed"
|
||||
default:
|
||||
return fmt.Sprintf("types.ReceiptType(%#v)", string(rt))
|
||||
}
|
||||
}
|
||||
|
||||
+62
-13
@@ -28,11 +28,11 @@ type UserInfo struct {
|
||||
|
||||
// ProfilePictureInfo contains the ID and URL for a WhatsApp user's profile picture or group's photo.
|
||||
type ProfilePictureInfo struct {
|
||||
URL string // The full URL for the image, can be downloaded with a simple HTTP request.
|
||||
ID string // The ID of the image. This is the same as UserInfo.PictureID.
|
||||
Type string // The type of image. Known types include "image" (full res) and "preview" (thumbnail).
|
||||
URL string `json:"url"` // The full URL for the image, can be downloaded with a simple HTTP request.
|
||||
ID string `json:"id"` // The ID of the image. This is the same as UserInfo.PictureID.
|
||||
Type string `json:"type"` // The type of image. Known types include "image" (full res) and "preview" (thumbnail).
|
||||
|
||||
DirectPath string // The path to the image, probably not very useful
|
||||
DirectPath string `json:"direct_path"` // The path to the image, probably not very useful
|
||||
}
|
||||
|
||||
// ContactInfo contains the cached names of a WhatsApp user.
|
||||
@@ -87,19 +87,37 @@ type PrivacySetting string
|
||||
|
||||
// Possible privacy setting values.
|
||||
const (
|
||||
PrivacySettingUndefined PrivacySetting = ""
|
||||
PrivacySettingAll PrivacySetting = "all"
|
||||
PrivacySettingContacts PrivacySetting = "contacts"
|
||||
PrivacySettingNone PrivacySetting = "none"
|
||||
PrivacySettingUndefined PrivacySetting = ""
|
||||
PrivacySettingAll PrivacySetting = "all"
|
||||
PrivacySettingContacts PrivacySetting = "contacts"
|
||||
PrivacySettingContactBlacklist PrivacySetting = "contact_blacklist"
|
||||
PrivacySettingMatchLastSeen PrivacySetting = "match_last_seen"
|
||||
PrivacySettingKnown PrivacySetting = "known"
|
||||
PrivacySettingNone PrivacySetting = "none"
|
||||
)
|
||||
|
||||
// PrivacySettingType is the type of privacy setting.
|
||||
type PrivacySettingType string
|
||||
|
||||
const (
|
||||
PrivacySettingTypeGroupAdd PrivacySettingType = "groupadd" // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
PrivacySettingTypeLastSeen PrivacySettingType = "last" // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
PrivacySettingTypeStatus PrivacySettingType = "status" // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
PrivacySettingTypeProfile PrivacySettingType = "profile" // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
PrivacySettingTypeReadReceipts PrivacySettingType = "readreceipts" // Valid values: PrivacySettingAll, PrivacySettingNone
|
||||
PrivacySettingTypeOnline PrivacySettingType = "online" // Valid values: PrivacySettingAll, PrivacySettingMatchLastSeen
|
||||
PrivacySettingTypeCallAdd PrivacySettingType = "calladd" // Valid values: PrivacySettingAll, PrivacySettingKnown
|
||||
)
|
||||
|
||||
// PrivacySettings contains the user's privacy settings.
|
||||
type PrivacySettings struct {
|
||||
GroupAdd PrivacySetting
|
||||
LastSeen PrivacySetting
|
||||
Status PrivacySetting
|
||||
Profile PrivacySetting
|
||||
ReadReceipts PrivacySetting
|
||||
GroupAdd PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
LastSeen PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
Status PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
Profile PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingContacts, PrivacySettingContactBlacklist, PrivacySettingNone
|
||||
ReadReceipts PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingNone
|
||||
CallAdd PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingKnown
|
||||
Online PrivacySetting // Valid values: PrivacySettingAll, PrivacySettingMatchLastSeen
|
||||
}
|
||||
|
||||
// StatusPrivacyType is the type of list in StatusPrivacy.
|
||||
@@ -121,3 +139,34 @@ type StatusPrivacy struct {
|
||||
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
// Blocklist contains the user's current list of blocked users.
|
||||
type Blocklist struct {
|
||||
DHash string // TODO is this just a timestamp?
|
||||
JIDs []JID
|
||||
}
|
||||
|
||||
// BusinessHoursConfig contains business operating hours of a WhatsApp business.
|
||||
type BusinessHoursConfig struct {
|
||||
DayOfWeek string
|
||||
Mode string
|
||||
OpenTime string
|
||||
CloseTime string
|
||||
}
|
||||
|
||||
// Category contains a WhatsApp business category.
|
||||
type Category struct {
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
// BusinessProfile contains the profile information of a WhatsApp business.
|
||||
type BusinessProfile struct {
|
||||
JID JID
|
||||
Address string
|
||||
Email string
|
||||
Categories []Category
|
||||
ProfileOptions map[string]string
|
||||
BusinessHoursTimeZone string
|
||||
BusinessHours []BusinessHoursConfig
|
||||
}
|
||||
|
||||
Vendored
+79
-18
@@ -17,15 +17,18 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.mau.fi/util/random"
|
||||
|
||||
"go.mau.fi/whatsmeow/socket"
|
||||
"go.mau.fi/whatsmeow/util/cbcutil"
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
// UploadResponse contains the data from the attachment upload, which can be put into a message to send the attachment.
|
||||
type UploadResponse struct {
|
||||
URL string `json:"url"`
|
||||
DirectPath string `json:"direct_path"`
|
||||
Handle string `json:"handle"`
|
||||
ObjectID string `json:"object_id"`
|
||||
|
||||
MediaKey []byte `json:"-"`
|
||||
FileEncSHA256 []byte `json:"-"`
|
||||
@@ -62,7 +65,7 @@ type UploadResponse struct {
|
||||
// The same applies to the other message types like DocumentMessage, just replace the struct type and Message field name.
|
||||
func (cli *Client) Upload(ctx context.Context, plaintext []byte, appInfo MediaType) (resp UploadResponse, err error) {
|
||||
resp.FileLength = uint64(len(plaintext))
|
||||
resp.MediaKey = randbytes.Make(32)
|
||||
resp.MediaKey = random.Bytes(32)
|
||||
|
||||
plaintextSHA256 := sha256.Sum256(plaintext)
|
||||
resp.FileSHA256 = plaintextSHA256[:]
|
||||
@@ -81,41 +84,99 @@ func (cli *Client) Upload(ctx context.Context, plaintext []byte, appInfo MediaTy
|
||||
h.Write(ciphertext)
|
||||
dataToUpload := append(ciphertext, h.Sum(nil)[:10]...)
|
||||
|
||||
fileEncSHA256 := sha256.Sum256(dataToUpload)
|
||||
resp.FileEncSHA256 = fileEncSHA256[:]
|
||||
dataHash := sha256.Sum256(dataToUpload)
|
||||
resp.FileEncSHA256 = dataHash[:]
|
||||
|
||||
var mediaConn *MediaConn
|
||||
mediaConn, err = cli.refreshMediaConn(false)
|
||||
err = cli.rawUpload(ctx, dataToUpload, resp.FileEncSHA256, appInfo, false, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// UploadNewsletter uploads the given attachment to WhatsApp servers without encrypting it first.
|
||||
//
|
||||
// Newsletter media works mostly the same way as normal media, with a few differences:
|
||||
// * Since it's unencrypted, there's no MediaKey or FileEncSha256 fields.
|
||||
// * There's a "media handle" that needs to be passed in SendRequestExtra.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// resp, err := cli.UploadNewsletter(context.Background(), yourImageBytes, whatsmeow.MediaImage)
|
||||
// // handle error
|
||||
//
|
||||
// imageMsg := &waProto.ImageMessage{
|
||||
// // Caption, mime type and other such fields work like normal
|
||||
// Caption: proto.String("Hello, world!"),
|
||||
// Mimetype: proto.String("image/png"),
|
||||
//
|
||||
// // URL and direct path are also there like normal media
|
||||
// Url: &resp.URL,
|
||||
// DirectPath: &resp.DirectPath,
|
||||
// FileSha256: resp.FileSha256,
|
||||
// FileLength: &resp.FileLength,
|
||||
// // Newsletter media isn't encrypted, so the media key and file enc sha fields are not applicable
|
||||
// }
|
||||
// _, err = cli.SendMessage(context.Background(), newsletterJID, &waProto.Message{
|
||||
// ImageMessage: imageMsg,
|
||||
// }, whatsmeow.SendRequestExtra{
|
||||
// // Unlike normal media, newsletters also include a "media handle" in the send request.
|
||||
// MediaHandle: resp.Handle,
|
||||
// })
|
||||
// // handle error again
|
||||
func (cli *Client) UploadNewsletter(ctx context.Context, data []byte, appInfo MediaType) (resp UploadResponse, err error) {
|
||||
resp.FileLength = uint64(len(data))
|
||||
hash := sha256.Sum256(data)
|
||||
resp.FileSHA256 = hash[:]
|
||||
err = cli.rawUpload(ctx, data, resp.FileSHA256, appInfo, true, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) rawUpload(ctx context.Context, dataToUpload, fileHash []byte, appInfo MediaType, newsletter bool, resp *UploadResponse) error {
|
||||
mediaConn, err := cli.refreshMediaConn(false)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to refresh media connections: %w", err)
|
||||
return
|
||||
return fmt.Errorf("failed to refresh media connections: %w", err)
|
||||
}
|
||||
|
||||
token := base64.URLEncoding.EncodeToString(resp.FileEncSHA256)
|
||||
token := base64.URLEncoding.EncodeToString(fileHash)
|
||||
q := url.Values{
|
||||
"auth": []string{mediaConn.Auth},
|
||||
"token": []string{token},
|
||||
}
|
||||
mmsType := mediaTypeToMMSType[appInfo]
|
||||
uploadPrefix := "mms"
|
||||
if cli.MessengerConfig != nil {
|
||||
uploadPrefix = "wa-msgr/mms"
|
||||
// Messenger upload only allows voice messages, not audio files
|
||||
if mmsType == "audio" {
|
||||
mmsType = "ptt"
|
||||
}
|
||||
}
|
||||
if newsletter {
|
||||
mmsType = fmt.Sprintf("newsletter-%s", mmsType)
|
||||
uploadPrefix = "newsletter"
|
||||
}
|
||||
var host string
|
||||
// Hacky hack to prefer last option (rupload.facebook.com) for messenger uploads.
|
||||
// For some reason, the primary host doesn't work, even though it has the <upload/> tag.
|
||||
if cli.MessengerConfig != nil {
|
||||
host = mediaConn.Hosts[len(mediaConn.Hosts)-1].Hostname
|
||||
} else {
|
||||
host = mediaConn.Hosts[0].Hostname
|
||||
}
|
||||
uploadURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: mediaConn.Hosts[0].Hostname,
|
||||
Path: fmt.Sprintf("/mms/%s/%s", mmsType, token),
|
||||
Host: host,
|
||||
Path: fmt.Sprintf("/%s/%s/%s", uploadPrefix, mmsType, token),
|
||||
RawQuery: q.Encode(),
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
req, err = http.NewRequestWithContext(ctx, http.MethodPost, uploadURL.String(), bytes.NewReader(dataToUpload))
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadURL.String(), bytes.NewReader(dataToUpload))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to prepare request: %w", err)
|
||||
return
|
||||
return fmt.Errorf("failed to prepare request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Origin", socket.Origin)
|
||||
req.Header.Set("Referer", socket.Origin+"/")
|
||||
|
||||
var httpResp *http.Response
|
||||
httpResp, err = cli.http.Do(req)
|
||||
httpResp, err := cli.http.Do(req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to execute request: %w", err)
|
||||
} else if httpResp.StatusCode != http.StatusOK {
|
||||
@@ -126,5 +187,5 @@ func (cli *Client) Upload(ctx context.Context, plaintext []byte, appInfo MediaTy
|
||||
if httpResp != nil {
|
||||
_ = httpResp.Body.Close()
|
||||
}
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
Vendored
+224
-24
@@ -24,6 +24,7 @@ const BusinessMessageLinkPrefix = "https://wa.me/message/"
|
||||
const ContactQRLinkPrefix = "https://wa.me/qr/"
|
||||
const BusinessMessageLinkDirectPrefix = "https://api.whatsapp.com/message/"
|
||||
const ContactQRLinkDirectPrefix = "https://api.whatsapp.com/qr/"
|
||||
const NewsletterLinkPrefix = "https://whatsapp.com/channel/"
|
||||
|
||||
// ResolveBusinessMessageLink resolves a business message short link and returns the target JID, business name and
|
||||
// text to prefill in the input field (if any).
|
||||
@@ -226,6 +227,91 @@ func (cli *Client) GetUserInfo(jids []types.JID) (map[types.JID]types.UserInfo,
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
func (cli *Client) parseBusinessProfile(node *waBinary.Node) (*types.BusinessProfile, error) {
|
||||
profileNode := node.GetChildByTag("profile")
|
||||
jid, ok := profileNode.AttrGetter().GetJID("jid", true)
|
||||
if !ok {
|
||||
return nil, errors.New("missing jid in business profile")
|
||||
}
|
||||
address := string(profileNode.GetChildByTag("address").Content.([]byte))
|
||||
email := string(profileNode.GetChildByTag("email").Content.([]byte))
|
||||
businessHour := profileNode.GetChildByTag("business_hours")
|
||||
businessHourTimezone := businessHour.AttrGetter().String("timezone")
|
||||
businessHoursConfigs := businessHour.GetChildren()
|
||||
businessHours := make([]types.BusinessHoursConfig, 0)
|
||||
for _, config := range businessHoursConfigs {
|
||||
if config.Tag != "business_hours_config" {
|
||||
continue
|
||||
}
|
||||
dow := config.AttrGetter().String("dow")
|
||||
mode := config.AttrGetter().String("mode")
|
||||
openTime := config.AttrGetter().String("open_time")
|
||||
closeTime := config.AttrGetter().String("close_time")
|
||||
businessHours = append(businessHours, types.BusinessHoursConfig{
|
||||
DayOfWeek: dow,
|
||||
Mode: mode,
|
||||
OpenTime: openTime,
|
||||
CloseTime: closeTime,
|
||||
})
|
||||
}
|
||||
categoriesNode := profileNode.GetChildByTag("categories")
|
||||
categories := make([]types.Category, 0)
|
||||
for _, category := range categoriesNode.GetChildren() {
|
||||
if category.Tag != "category" {
|
||||
continue
|
||||
}
|
||||
id := category.AttrGetter().String("id")
|
||||
name := string(category.Content.([]byte))
|
||||
categories = append(categories, types.Category{
|
||||
ID: id,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
profileOptionsNode := profileNode.GetChildByTag("profile_options")
|
||||
profileOptions := make(map[string]string)
|
||||
for _, option := range profileOptionsNode.GetChildren() {
|
||||
profileOptions[option.Tag] = string(option.Content.([]byte))
|
||||
}
|
||||
return &types.BusinessProfile{
|
||||
JID: jid,
|
||||
Email: email,
|
||||
Address: address,
|
||||
Categories: categories,
|
||||
ProfileOptions: profileOptions,
|
||||
BusinessHoursTimeZone: businessHourTimezone,
|
||||
BusinessHours: businessHours,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetBusinessProfile gets the profile info of a WhatsApp business account
|
||||
func (cli *Client) GetBusinessProfile(jid types.JID) (*types.BusinessProfile, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Namespace: "w:biz",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "business_profile",
|
||||
Attrs: waBinary.Attrs{
|
||||
"v": "244",
|
||||
},
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "profile",
|
||||
Attrs: waBinary.Attrs{
|
||||
"jid": jid,
|
||||
},
|
||||
}},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, ok := resp.GetOptionalChildByTag("business_profile")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "business_profile", In: "response to business profile query"}
|
||||
}
|
||||
return cli.parseBusinessProfile(&node)
|
||||
}
|
||||
|
||||
// GetUserDevices gets the list of devices that the given user has. The input should be a list of
|
||||
// regular JIDs, and the output will be a list of AD JIDs. The local device will not be included in
|
||||
// the output even if the user's JID is included in the input. All other devices will be included.
|
||||
@@ -237,34 +323,50 @@ func (cli *Client) GetUserDevicesContext(ctx context.Context, jids []types.JID)
|
||||
cli.userDevicesCacheLock.Lock()
|
||||
defer cli.userDevicesCacheLock.Unlock()
|
||||
|
||||
var devices, jidsToSync []types.JID
|
||||
var devices, jidsToSync, fbJIDsToSync []types.JID
|
||||
for _, jid := range jids {
|
||||
cached, ok := cli.userDevicesCache[jid]
|
||||
if ok && len(cached) > 0 {
|
||||
devices = append(devices, cached...)
|
||||
if ok && len(cached.devices) > 0 {
|
||||
devices = append(devices, cached.devices...)
|
||||
} else if jid.Server == types.MessengerServer {
|
||||
fbJIDsToSync = append(fbJIDsToSync, jid)
|
||||
} else {
|
||||
jidsToSync = append(jidsToSync, jid)
|
||||
}
|
||||
}
|
||||
if len(jidsToSync) == 0 {
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
list, err := cli.usync(ctx, jidsToSync, "query", "message", []waBinary.Node{
|
||||
{Tag: "devices", Attrs: waBinary.Attrs{"version": "2"}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range list.GetChildren() {
|
||||
jid, jidOK := user.Attrs["jid"].(types.JID)
|
||||
if user.Tag != "user" || !jidOK {
|
||||
continue
|
||||
if len(jidsToSync) > 0 {
|
||||
list, err := cli.usync(ctx, jidsToSync, "query", "message", []waBinary.Node{
|
||||
{Tag: "devices", Attrs: waBinary.Attrs{"version": "2"}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range list.GetChildren() {
|
||||
jid, jidOK := user.Attrs["jid"].(types.JID)
|
||||
if user.Tag != "user" || !jidOK {
|
||||
continue
|
||||
}
|
||||
userDevices := parseDeviceList(jid.User, user.GetChildByTag("devices"))
|
||||
cli.userDevicesCache[jid] = deviceCache{devices: userDevices, dhash: participantListHashV2(userDevices)}
|
||||
devices = append(devices, userDevices...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fbJIDsToSync) > 0 {
|
||||
list, err := cli.getFBIDDevices(ctx, fbJIDsToSync)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, user := range list.GetChildren() {
|
||||
jid, jidOK := user.Attrs["jid"].(types.JID)
|
||||
if user.Tag != "user" || !jidOK {
|
||||
continue
|
||||
}
|
||||
userDevices := parseFBDeviceList(jid, user.GetChildByTag("devices"))
|
||||
cli.userDevicesCache[jid] = userDevices
|
||||
devices = append(devices, userDevices.devices...)
|
||||
}
|
||||
userDevices := parseDeviceList(jid.User, user.GetChildByTag("devices"))
|
||||
cli.userDevicesCache[jid] = userDevices
|
||||
devices = append(devices, userDevices...)
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
@@ -472,13 +574,56 @@ func parseDeviceList(user string, deviceNode waBinary.Node) []types.JID {
|
||||
return devices
|
||||
}
|
||||
|
||||
func parseFBDeviceList(user types.JID, deviceList waBinary.Node) deviceCache {
|
||||
children := deviceList.GetChildren()
|
||||
devices := make([]types.JID, 0, len(children))
|
||||
for _, device := range children {
|
||||
deviceID, ok := device.AttrGetter().GetInt64("id", true)
|
||||
if device.Tag != "device" || !ok {
|
||||
continue
|
||||
}
|
||||
user.Device = uint16(deviceID)
|
||||
devices = append(devices, user)
|
||||
// TODO take identities here too?
|
||||
}
|
||||
// TODO do something with the icdc blob?
|
||||
return deviceCache{
|
||||
devices: devices,
|
||||
dhash: deviceList.AttrGetter().String("dhash"),
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) getFBIDDevices(ctx context.Context, jids []types.JID) (*waBinary.Node, error) {
|
||||
users := make([]waBinary.Node, len(jids))
|
||||
for i, jid := range jids {
|
||||
users[i].Tag = "user"
|
||||
users[i].Attrs = waBinary.Attrs{"jid": jid}
|
||||
// TODO include dhash for users
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
Namespace: "fbid:devices",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "users",
|
||||
Content: users,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send usync query: %w", err)
|
||||
} else if list, ok := resp.GetOptionalChildByTag("users"); !ok {
|
||||
return nil, &ElementMissingError{Tag: "users", In: "response to fbid devices query"}
|
||||
} else {
|
||||
return &list, err
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) usync(ctx context.Context, jids []types.JID, mode, context string, query []waBinary.Node) (*waBinary.Node, error) {
|
||||
userList := make([]waBinary.Node, len(jids))
|
||||
for i, jid := range jids {
|
||||
userList[i].Tag = "user"
|
||||
if jid.AD {
|
||||
jid.AD = false
|
||||
}
|
||||
jid = jid.ToNonAD()
|
||||
switch jid.Server {
|
||||
case types.LegacyUserServer:
|
||||
userList[i].Content = []waBinary.Node{{
|
||||
@@ -519,3 +664,58 @@ func (cli *Client) usync(ctx context.Context, jids []types.JID, mode, context st
|
||||
return &list, err
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) parseBlocklist(node *waBinary.Node) *types.Blocklist {
|
||||
output := &types.Blocklist{
|
||||
DHash: node.AttrGetter().String("dhash"),
|
||||
}
|
||||
for _, child := range node.GetChildren() {
|
||||
ag := child.AttrGetter()
|
||||
blockedJID := ag.JID("jid")
|
||||
if !ag.OK() {
|
||||
cli.Log.Debugf("Ignoring contact blocked data with unexpected attributes: %v", ag.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
output.JIDs = append(output.JIDs, blockedJID)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// GetBlocklist gets the list of users that this user has blocked.
|
||||
func (cli *Client) GetBlocklist() (*types.Blocklist, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "blocklist",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, ok := resp.GetOptionalChildByTag("list")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "list", In: "response to blocklist query"}
|
||||
}
|
||||
return cli.parseBlocklist(&list), nil
|
||||
}
|
||||
|
||||
// UpdateBlocklist updates the user's block list and returns the updated list.
|
||||
func (cli *Client) UpdateBlocklist(jid types.JID, action events.BlocklistChangeAction) (*types.Blocklist, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Namespace: "blocklist",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "item",
|
||||
Attrs: waBinary.Attrs{
|
||||
"jid": jid,
|
||||
"action": string(action),
|
||||
},
|
||||
}},
|
||||
})
|
||||
list, ok := resp.GetOptionalChildByTag("list")
|
||||
if !ok {
|
||||
return nil, &ElementMissingError{Tag: "list", In: "response to blocklist update"}
|
||||
}
|
||||
return cli.parseBlocklist(&list), err
|
||||
}
|
||||
|
||||
+2
-3
@@ -9,9 +9,8 @@ package keys
|
||||
|
||||
import (
|
||||
"go.mau.fi/libsignal/ecc"
|
||||
"go.mau.fi/util/random"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
|
||||
"go.mau.fi/whatsmeow/util/randbytes"
|
||||
)
|
||||
|
||||
type KeyPair struct {
|
||||
@@ -31,7 +30,7 @@ func NewKeyPairFromPrivateKey(priv [32]byte) *KeyPair {
|
||||
}
|
||||
|
||||
func NewKeyPair() *KeyPair {
|
||||
priv := *(*[32]byte)(randbytes.Make(32))
|
||||
priv := *(*[32]byte)(random.Bytes(32))
|
||||
|
||||
priv[0] &= 248
|
||||
priv[31] &= 127
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2024 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 waLog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type zeroLogger struct {
|
||||
mod string
|
||||
zerolog.Logger
|
||||
}
|
||||
|
||||
// Zerolog wraps a [zerolog.Logger] to implement the [Logger] interface.
|
||||
//
|
||||
// Subloggers will be created by setting the `sublogger` field in the log context.
|
||||
func Zerolog(log zerolog.Logger) Logger {
|
||||
return &zeroLogger{Logger: log}
|
||||
}
|
||||
|
||||
func (z *zeroLogger) Warnf(msg string, args ...any) { z.Warn().Msgf(msg, args...) }
|
||||
func (z *zeroLogger) Errorf(msg string, args ...any) { z.Error().Msgf(msg, args...) }
|
||||
func (z *zeroLogger) Infof(msg string, args ...any) { z.Info().Msgf(msg, args...) }
|
||||
func (z *zeroLogger) Debugf(msg string, args ...any) { z.Debug().Msgf(msg, args...) }
|
||||
func (z *zeroLogger) Sub(module string) Logger {
|
||||
if z.mod != "" {
|
||||
module = fmt.Sprintf("%s/%s", z.mod, module)
|
||||
}
|
||||
return &zeroLogger{mod: module, Logger: z.Logger.With().Str("sublogger", module).Logger()}
|
||||
}
|
||||
|
||||
var _ Logger = &zeroLogger{}
|
||||
@@ -1,15 +0,0 @@
|
||||
package randbytes
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Make(length int) []byte {
|
||||
random := make([]byte, length)
|
||||
_, err := rand.Read(random)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to get random bytes: %w", err))
|
||||
}
|
||||
return random
|
||||
}
|
||||
Reference in New Issue
Block a user