summaryrefslogtreecommitdiffstats
path: root/vendor/go.mau.fi/whatsmeow/send.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/send.go')
-rw-r--r--vendor/go.mau.fi/whatsmeow/send.go115
1 files changed, 75 insertions, 40 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/send.go b/vendor/go.mau.fi/whatsmeow/send.go
index ab754f8f..96d888be 100644
--- a/vendor/go.mau.fi/whatsmeow/send.go
+++ b/vendor/go.mau.fi/whatsmeow/send.go
@@ -35,7 +35,7 @@ import (
// GenerateMessageID generates a random string that can be used as a message ID on WhatsApp.
//
// msgID := whatsmeow.GenerateMessageID()
-// cli.SendMessage(context.Background(), targetJID, msgID, &waProto.Message{...})
+// cli.SendMessage(context.Background(), targetJID, &waProto.Message{...}, whatsmeow.SendRequestExtra{ID: msgID})
func GenerateMessageID() types.MessageID {
id := make([]byte, 8)
_, err := rand.Read(id)
@@ -71,17 +71,36 @@ type SendResponse struct {
DebugTimings MessageDebugTimings
}
-// SendMessage sends the given message.
+// SendRequestExtra contains the optional parameters for SendMessage.
+//
+// By default, optional parameters don't have to be provided at all, e.g.
+//
+// cli.SendMessage(ctx, to, message)
+//
+// When providing optional parameters, add a single instance of this struct as the last parameter:
//
-// If the message ID is not provided, a random message ID will be generated.
+// cli.SendMessage(ctx, to, message, whatsmeow.SendRequestExtra{...})
+//
+// Trying to add multiple extra parameters will return an error.
+type SendRequestExtra struct {
+ // The message ID to use when sending. If this is not provided, a random message ID will be generated
+ 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
+}
+
+// SendMessage sends the given message.
//
// This method will wait for the server to acknowledge the message before returning.
// The return value is the timestamp of the message from the server.
//
+// Optional parameters like the message ID can be specified with the SendRequestExtra struct.
+// Only one extra parameter is allowed, put all necessary parameters in the same struct.
+//
// The message itself can contain anything you want (within the protobuf schema).
// e.g. for a simple text message, use the Conversation field:
//
-// cli.SendMessage(context.Background(), targetJID, "", &waProto.Message{
+// cli.SendMessage(context.Background(), targetJID, &waProto.Message{
// Conversation: proto.String("Hello, World!"),
// })
//
@@ -91,18 +110,31 @@ type SendResponse struct {
// For uploading and sending media/attachments, see the Upload method.
//
// For other message types, you'll have to figure it out yourself. Looking at the protobuf schema
-// in binary/proto/def.proto may be useful to find out all the allowed fields.
-func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.MessageID, message *waProto.Message) (resp SendResponse, err error) {
- isPeerMessage := to.User == cli.Store.ID.User
- if to.AD && !isPeerMessage {
+// in binary/proto/def.proto may be useful to find out all the allowed fields. Printing the RawMessage
+// field in incoming message events to figure out what it contains is also a good way to learn how to
+// send the same kind of message.
+func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waProto.Message, 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]
+ }
+ if to.AD && !req.Peer {
err = ErrRecipientADJID
return
}
+ ownID := cli.getOwnID()
+ if ownID.IsEmpty() {
+ err = ErrNotLoggedIn
+ return
+ }
- if len(id) == 0 {
- id = GenerateMessageID()
+ if len(req.ID) == 0 {
+ req.ID = GenerateMessageID()
}
- resp.ID = id
+ resp.ID = req.ID
start := time.Now()
// Sending multiple messages at a time can cause weird issues and makes it harder to retry safely
@@ -110,36 +142,36 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.Messa
resp.DebugTimings.Queue = time.Since(start)
defer cli.messageSendLock.Unlock()
- respChan := cli.waitResponse(id)
+ respChan := cli.waitResponse(req.ID)
// Peer message retries aren't implemented yet
- if !isPeerMessage {
- cli.addRecentMessage(to, id, message)
+ if !req.Peer {
+ cli.addRecentMessage(to, req.ID, message)
}
if message.GetMessageContextInfo().GetMessageSecret() != nil {
- err = cli.Store.MsgSecrets.PutMessageSecret(to, *cli.Store.ID, id, message.GetMessageContextInfo().GetMessageSecret())
+ err = cli.Store.MsgSecrets.PutMessageSecret(to, ownID, req.ID, message.GetMessageContextInfo().GetMessageSecret())
if err != nil {
- cli.Log.Warnf("Failed to store message secret key for outgoing message %s: %v", id, err)
+ cli.Log.Warnf("Failed to store message secret key for outgoing message %s: %v", req.ID, err)
} else {
- cli.Log.Debugf("Stored message secret key for outgoing message %s", id)
+ cli.Log.Debugf("Stored message secret key for outgoing message %s", req.ID)
}
}
var phash string
var data []byte
switch to.Server {
case types.GroupServer, types.BroadcastServer:
- phash, data, err = cli.sendGroup(ctx, to, id, message, &resp.DebugTimings)
+ phash, data, err = cli.sendGroup(ctx, to, ownID, req.ID, message, &resp.DebugTimings)
case types.DefaultUserServer:
- if isPeerMessage {
- data, err = cli.sendPeerMessage(to, id, message, &resp.DebugTimings)
+ if req.Peer {
+ data, err = cli.sendPeerMessage(to, req.ID, message, &resp.DebugTimings)
} else {
- data, err = cli.sendDM(ctx, to, id, message, &resp.DebugTimings)
+ data, err = cli.sendDM(ctx, to, ownID, req.ID, message, &resp.DebugTimings)
}
default:
err = fmt.Errorf("%w %s", ErrUnknownServer, to.Server)
}
start = time.Now()
if err != nil {
- cli.cancelResponse(id, respChan)
+ cli.cancelResponse(req.ID, respChan)
return
}
var respNode *waBinary.Node
@@ -152,7 +184,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.Messa
resp.DebugTimings.Resp = time.Since(start)
if isDisconnectNode(respNode) {
start = time.Now()
- respNode, err = cli.retryFrame("message send", id, data, respNode, ctx, 0)
+ respNode, err = cli.retryFrame("message send", req.ID, data, respNode, ctx, 0)
resp.DebugTimings.Retry = time.Since(start)
if err != nil {
return
@@ -160,6 +192,9 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.Messa
}
ag := respNode.AttrGetter()
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)
@@ -178,7 +213,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, id types.Messa
//
// Deprecated: This method is deprecated in favor of BuildRevoke
func (cli *Client) RevokeMessage(chat types.JID, id types.MessageID) (SendResponse, error) {
- return cli.SendMessage(context.TODO(), chat, "", cli.BuildRevoke(chat, types.EmptyJID, id))
+ return cli.SendMessage(context.TODO(), chat, cli.BuildRevoke(chat, types.EmptyJID, id))
}
// BuildRevoke builds a message revocation message using the given variables.
@@ -186,18 +221,18 @@ func (cli *Client) RevokeMessage(chat types.JID, id types.MessageID) (SendRespon
//
// To revoke your own messages, pass your JID or an empty JID as the second parameter (sender).
//
-// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildRevoke(chat, types.EmptyJID, originalMessageID)
+// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildRevoke(chat, types.EmptyJID, originalMessageID)
//
// To revoke someone else's messages when you are group admin, pass the message sender's JID as the second parameter.
//
-// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildRevoke(chat, senderJID, originalMessageID)
+// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildRevoke(chat, senderJID, originalMessageID)
func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waProto.Message {
key := &waProto.MessageKey{
FromMe: proto.Bool(true),
Id: proto.String(id),
RemoteJid: proto.String(chat.String()),
}
- if !sender.IsEmpty() && sender.User != cli.Store.ID.User {
+ if !sender.IsEmpty() && sender.User != cli.getOwnID().User {
key.FromMe = proto.Bool(false)
if chat.Server != types.DefaultUserServer {
key.Participant = proto.String(sender.ToNonAD().String())
@@ -214,7 +249,7 @@ func (cli *Client) BuildRevoke(chat, sender types.JID, id types.MessageID) *waPr
// BuildEdit builds a message edit message using the given variables.
// The built message can be sent normally using Client.SendMessage.
//
-// resp, err := cli.SendMessage(context.Background(), chat, "", cli.BuildEdit(chat, originalMessageID, &waProto.Message{
+// resp, err := cli.SendMessage(context.Background(), chat, cli.BuildEdit(chat, originalMessageID, &waProto.Message{
// Conversation: proto.String("edited message"),
// })
func (cli *Client) BuildEdit(chat types.JID, id types.MessageID, newContent *waProto.Message) *waProto.Message {
@@ -270,7 +305,7 @@ func ParseDisappearingTimerString(val string) (time.Duration, bool) {
func (cli *Client) SetDisappearingTimer(chat types.JID, timer time.Duration) (err error) {
switch chat.Server {
case types.DefaultUserServer:
- _, err = cli.SendMessage(context.TODO(), chat, "", &waProto.Message{
+ _, err = cli.SendMessage(context.TODO(), chat, &waProto.Message{
ProtocolMessage: &waProto.ProtocolMessage{
Type: waProto.ProtocolMessage_EPHEMERAL_SETTING.Enum(),
EphemeralExpiration: proto.Uint32(uint32(timer.Seconds())),
@@ -307,7 +342,7 @@ func participantListHashV2(participants []types.JID) string {
return fmt.Sprintf("2:%s", base64.RawStdEncoding.EncodeToString(hash[:6]))
}
-func (cli *Client) sendGroup(ctx context.Context, to types.JID, id types.MessageID, message *waProto.Message, timings *MessageDebugTimings) (string, []byte, error) {
+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
start := time.Now()
@@ -333,7 +368,7 @@ func (cli *Client) sendGroup(ctx context.Context, to types.JID, id types.Message
start = time.Now()
builder := groups.NewGroupSessionBuilder(cli.Store, pbSerializer)
- senderKeyName := protocol.NewSenderKeyName(to.String(), cli.Store.ID.SignalAddress())
+ 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)
@@ -357,7 +392,7 @@ func (cli *Client) sendGroup(ctx context.Context, to types.JID, id types.Message
ciphertext := encrypted.SignedSerialize()
timings.GroupEncrypt = time.Since(start)
- node, allDevices, err := cli.prepareMessageNode(ctx, to, id, message, participants, skdPlaintext, nil, timings)
+ node, allDevices, err := cli.prepareMessageNode(ctx, to, ownID, id, message, participants, skdPlaintext, nil, timings)
if err != nil {
return "", nil, err
}
@@ -393,7 +428,7 @@ func (cli *Client) sendPeerMessage(to types.JID, id types.MessageID, message *wa
return data, nil
}
-func (cli *Client) sendDM(ctx context.Context, to types.JID, id types.MessageID, message *waProto.Message, timings *MessageDebugTimings) ([]byte, error) {
+func (cli *Client) sendDM(ctx context.Context, to, ownID types.JID, id types.MessageID, message *waProto.Message, timings *MessageDebugTimings) ([]byte, error) {
start := time.Now()
messagePlaintext, deviceSentMessagePlaintext, err := marshalMessage(to, message)
timings.Marshal = time.Since(start)
@@ -401,7 +436,7 @@ func (cli *Client) sendDM(ctx context.Context, to types.JID, id types.MessageID,
return nil, err
}
- node, _, err := cli.prepareMessageNode(ctx, to, id, message, []types.JID{to, cli.Store.ID.ToNonAD()}, messagePlaintext, deviceSentMessagePlaintext, timings)
+ node, _, err := cli.prepareMessageNode(ctx, to, ownID, id, message, []types.JID{to, ownID.ToNonAD()}, messagePlaintext, deviceSentMessagePlaintext, timings)
if err != nil {
return nil, err
}
@@ -504,7 +539,7 @@ func (cli *Client) preparePeerMessageNode(to types.JID, id types.MessageID, mess
}, nil
}
-func (cli *Client) prepareMessageNode(ctx context.Context, to types.JID, id types.MessageID, message *waProto.Message, participants []types.JID, plaintext, dsmPlaintext []byte, timings *MessageDebugTimings) (*waBinary.Node, []types.JID, error) {
+func (cli *Client) prepareMessageNode(ctx context.Context, to, ownID types.JID, id types.MessageID, message *waProto.Message, participants []types.JID, plaintext, dsmPlaintext []byte, timings *MessageDebugTimings) (*waBinary.Node, []types.JID, error) {
start := time.Now()
allDevices, err := cli.GetUserDevicesContext(ctx, participants)
timings.GetDevices = time.Since(start)
@@ -522,7 +557,7 @@ func (cli *Client) prepareMessageNode(ctx context.Context, to types.JID, id type
}
start = time.Now()
- participantNodes, includeIdentity := cli.encryptMessageForDevices(ctx, allDevices, id, plaintext, dsmPlaintext)
+ participantNodes, includeIdentity := cli.encryptMessageForDevices(ctx, allDevices, ownID, id, plaintext, dsmPlaintext)
timings.PeerEncrypt = time.Since(start)
content := []waBinary.Node{{
Tag: "participants",
@@ -584,14 +619,14 @@ func (cli *Client) makeDeviceIdentityNode() waBinary.Node {
}
}
-func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []types.JID, id string, msgPlaintext, dsmPlaintext []byte) ([]waBinary.Node, bool) {
+func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []types.JID, ownID types.JID, id string, msgPlaintext, dsmPlaintext []byte) ([]waBinary.Node, bool) {
includeIdentity := false
participantNodes := make([]waBinary.Node, 0, len(allDevices))
var retryDevices []types.JID
for _, jid := range allDevices {
plaintext := msgPlaintext
- if jid.User == cli.Store.ID.User && dsmPlaintext != nil {
- if jid == *cli.Store.ID {
+ if jid.User == ownID.User && dsmPlaintext != nil {
+ if jid == ownID {
continue
}
plaintext = dsmPlaintext
@@ -621,7 +656,7 @@ func (cli *Client) encryptMessageForDevices(ctx context.Context, allDevices []ty
continue
}
plaintext := msgPlaintext
- if jid.User == cli.Store.ID.User && dsmPlaintext != nil {
+ if jid.User == ownID.User && dsmPlaintext != nil {
plaintext = dsmPlaintext
}
encrypted, isPreKey, err := cli.encryptMessageForDeviceAndWrap(plaintext, jid, resp.bundle)