2022-01-31 00:27:37 +01:00
|
|
|
// Copyright (c) 2021 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"
|
2022-05-09 23:00:23 +02:00
|
|
|
"encoding/base64"
|
2022-01-31 00:27:37 +01:00
|
|
|
"strconv"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
waBinary "go.mau.fi/whatsmeow/binary"
|
|
|
|
"go.mau.fi/whatsmeow/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (cli *Client) generateRequestID() string {
|
|
|
|
return cli.uniqueID + strconv.FormatUint(uint64(atomic.AddUint32(&cli.idCounter, 1)), 10)
|
|
|
|
}
|
|
|
|
|
2022-05-09 23:00:23 +02:00
|
|
|
var xmlStreamEndNode = &waBinary.Node{Tag: "xmlstreamend"}
|
2022-01-31 00:27:37 +01:00
|
|
|
|
2022-05-09 23:00:23 +02:00
|
|
|
func isDisconnectNode(node *waBinary.Node) bool {
|
|
|
|
return node == xmlStreamEndNode || node.Tag == "stream:error"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *Client) clearResponseWaiters(node *waBinary.Node) {
|
2022-01-31 00:27:37 +01:00
|
|
|
cli.responseWaitersLock.Lock()
|
|
|
|
for _, waiter := range cli.responseWaiters {
|
|
|
|
select {
|
2022-05-09 23:00:23 +02:00
|
|
|
case waiter <- node:
|
2022-01-31 00:27:37 +01:00
|
|
|
default:
|
|
|
|
close(waiter)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cli.responseWaiters = make(map[string]chan<- *waBinary.Node)
|
|
|
|
cli.responseWaitersLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *Client) waitResponse(reqID string) chan *waBinary.Node {
|
|
|
|
ch := make(chan *waBinary.Node, 1)
|
|
|
|
cli.responseWaitersLock.Lock()
|
|
|
|
cli.responseWaiters[reqID] = ch
|
|
|
|
cli.responseWaitersLock.Unlock()
|
|
|
|
return ch
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *Client) cancelResponse(reqID string, ch chan *waBinary.Node) {
|
|
|
|
cli.responseWaitersLock.Lock()
|
|
|
|
close(ch)
|
|
|
|
delete(cli.responseWaiters, reqID)
|
|
|
|
cli.responseWaitersLock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *Client) receiveResponse(data *waBinary.Node) bool {
|
|
|
|
id, ok := data.Attrs["id"].(string)
|
|
|
|
if !ok || (data.Tag != "iq" && data.Tag != "ack") {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
cli.responseWaitersLock.Lock()
|
|
|
|
waiter, ok := cli.responseWaiters[id]
|
|
|
|
if !ok {
|
|
|
|
cli.responseWaitersLock.Unlock()
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
delete(cli.responseWaiters, id)
|
|
|
|
cli.responseWaitersLock.Unlock()
|
|
|
|
waiter <- data
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type infoQueryType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
iqSet infoQueryType = "set"
|
|
|
|
iqGet infoQueryType = "get"
|
|
|
|
)
|
|
|
|
|
|
|
|
type infoQuery struct {
|
|
|
|
Namespace string
|
|
|
|
Type infoQueryType
|
|
|
|
To types.JID
|
2022-03-12 23:02:04 +01:00
|
|
|
Target types.JID
|
2022-01-31 00:27:37 +01:00
|
|
|
ID string
|
|
|
|
Content interface{}
|
|
|
|
|
|
|
|
Timeout time.Duration
|
|
|
|
Context context.Context
|
|
|
|
}
|
|
|
|
|
2022-05-09 23:00:23 +02:00
|
|
|
func (cli *Client) sendIQAsyncDebug(query infoQuery) (<-chan *waBinary.Node, []byte, error) {
|
2022-01-31 00:27:37 +01:00
|
|
|
if len(query.ID) == 0 {
|
|
|
|
query.ID = cli.generateRequestID()
|
|
|
|
}
|
|
|
|
waiter := cli.waitResponse(query.ID)
|
|
|
|
attrs := waBinary.Attrs{
|
|
|
|
"id": query.ID,
|
|
|
|
"xmlns": query.Namespace,
|
|
|
|
"type": string(query.Type),
|
|
|
|
}
|
|
|
|
if !query.To.IsEmpty() {
|
|
|
|
attrs["to"] = query.To
|
|
|
|
}
|
2022-03-12 23:02:04 +01:00
|
|
|
if !query.Target.IsEmpty() {
|
|
|
|
attrs["target"] = query.Target
|
|
|
|
}
|
2022-05-09 23:00:23 +02:00
|
|
|
data, err := cli.sendNodeDebug(waBinary.Node{
|
2022-01-31 00:27:37 +01:00
|
|
|
Tag: "iq",
|
|
|
|
Attrs: attrs,
|
|
|
|
Content: query.Content,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
cli.cancelResponse(query.ID, waiter)
|
2022-05-09 23:00:23 +02:00
|
|
|
return nil, data, err
|
2022-01-31 00:27:37 +01:00
|
|
|
}
|
2022-05-09 23:00:23 +02:00
|
|
|
return waiter, data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) {
|
|
|
|
ch, _, err := cli.sendIQAsyncDebug(query)
|
|
|
|
return ch, err
|
2022-01-31 00:27:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
|
2022-05-09 23:00:23 +02:00
|
|
|
resChan, data, err := cli.sendIQAsyncDebug(query)
|
2022-01-31 00:27:37 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if query.Timeout == 0 {
|
2022-03-12 23:02:04 +01:00
|
|
|
query.Timeout = 75 * time.Second
|
2022-01-31 00:27:37 +01:00
|
|
|
}
|
|
|
|
if query.Context == nil {
|
|
|
|
query.Context = context.Background()
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case res := <-resChan:
|
2022-05-09 23:00:23 +02:00
|
|
|
if isDisconnectNode(res) {
|
|
|
|
if cli.DebugDecodeBeforeSend && res.Tag == "stream:error" && res.GetChildByTag("xml-not-well-formed").Tag != "" {
|
|
|
|
cli.Log.Debugf("Info query that was interrupted by xml-not-well-formed: %s", base64.URLEncoding.EncodeToString(data))
|
|
|
|
}
|
|
|
|
return nil, &DisconnectedError{Action: "info query", Node: res}
|
2022-01-31 00:27:37 +01:00
|
|
|
}
|
|
|
|
resType, _ := res.Attrs["type"].(string)
|
|
|
|
if res.Tag != "iq" || (resType != "result" && resType != "error") {
|
|
|
|
return res, &IQError{RawNode: res}
|
|
|
|
} else if resType == "error" {
|
|
|
|
return res, parseIQError(res)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
case <-query.Context.Done():
|
|
|
|
return nil, query.Context.Err()
|
|
|
|
case <-time.After(query.Timeout):
|
|
|
|
return nil, ErrIQTimedOut
|
|
|
|
}
|
|
|
|
}
|