BREAKING CHANGE: switch to coder/websocket and use contexts everywhere
Closes #896
This commit is contained in:
+2
-4
@@ -289,8 +289,7 @@ func (cli *Client) fetchAppStatePatches(ctx context.Context, name appstate.WAPat
|
||||
if !snapshot {
|
||||
attrs["version"] = fromVersion
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:sync:app:state",
|
||||
Type: "set",
|
||||
To: types.ServerJID,
|
||||
@@ -384,8 +383,7 @@ func (cli *Client) sendAppState(ctx context.Context, patch appstate.PatchInfo, w
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:sync:app:state",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ func (cli *Client) getBroadcastListParticipants(ctx context.Context, jid types.J
|
||||
}
|
||||
|
||||
func (cli *Client) getStatusBroadcastRecipients(ctx context.Context) ([]types.JID, error) {
|
||||
statusPrivacyOptions, err := cli.GetStatusPrivacy()
|
||||
statusPrivacyOptions, err := cli.GetStatusPrivacy(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get status privacy: %w", err)
|
||||
}
|
||||
@@ -90,8 +90,8 @@ var DefaultStatusPrivacy = []types.StatusPrivacy{{
|
||||
// GetStatusPrivacy gets the user's status privacy settings (who to send status broadcasts to).
|
||||
//
|
||||
// There can be multiple different stored settings, the first one is always the default.
|
||||
func (cli *Client) GetStatusPrivacy() ([]types.StatusPrivacy, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
func (cli *Client) GetStatusPrivacy(ctx context.Context) ([]types.StatusPrivacy, error) {
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "status",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
|
||||
@@ -7,13 +7,15 @@
|
||||
package whatsmeow
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
|
||||
func (cli *Client) handleCallEvent(node *waBinary.Node) {
|
||||
defer cli.maybeDeferredAck(cli.BackgroundEventCtx, node)()
|
||||
func (cli *Client) handleCallEvent(ctx context.Context, node *waBinary.Node) {
|
||||
defer cli.maybeDeferredAck(ctx, node)()
|
||||
|
||||
if len(node.GetChildren()) != 1 {
|
||||
cli.dispatchEvent(&events.UnknownCallEvent{Node: node})
|
||||
@@ -101,13 +103,13 @@ func (cli *Client) handleCallEvent(node *waBinary.Node) {
|
||||
}
|
||||
|
||||
// RejectCall reject an incoming call.
|
||||
func (cli *Client) RejectCall(callFrom types.JID, callID string) error {
|
||||
func (cli *Client) RejectCall(ctx context.Context, callFrom types.JID, callID string) error {
|
||||
ownID := cli.getOwnID()
|
||||
if ownID.IsEmpty() {
|
||||
return ErrNotLoggedIn
|
||||
}
|
||||
ownID, callFrom = ownID.ToNonAD(), callFrom.ToNonAD()
|
||||
return cli.sendNode(waBinary.Node{
|
||||
return cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "call",
|
||||
Attrs: waBinary.Attrs{"id": cli.GenerateMessageID(), "from": ownID, "to": callFrom},
|
||||
Content: []waBinary.Node{{
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime/debug"
|
||||
@@ -19,7 +20,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"go.mau.fi/util/exhttp"
|
||||
"go.mau.fi/util/exsync"
|
||||
"go.mau.fi/util/random"
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
// EventHandler is a function that can handle events from WhatsApp.
|
||||
type EventHandler func(evt any)
|
||||
type EventHandlerWithSuccessStatus func(evt any) bool
|
||||
type nodeHandler func(node *waBinary.Node)
|
||||
type nodeHandler func(ctx context.Context, node *waBinary.Node)
|
||||
|
||||
var nextHandlerID uint32
|
||||
|
||||
@@ -65,7 +65,6 @@ type Client struct {
|
||||
socket *socket.NoiseSocket
|
||||
socketLock sync.RWMutex
|
||||
socketWait chan struct{}
|
||||
wsDialer *websocket.Dialer
|
||||
|
||||
isLoggedIn atomic.Bool
|
||||
expectedDisconnect *exsync.Event
|
||||
@@ -171,10 +170,9 @@ type Client struct {
|
||||
uniqueID string
|
||||
idCounter atomic.Uint64
|
||||
|
||||
proxy Proxy
|
||||
socksProxy proxy.Dialer
|
||||
proxyOnlyLogin bool
|
||||
http *http.Client
|
||||
mediaHTTP *http.Client
|
||||
websocketHTTP *http.Client
|
||||
preLoginHTTP *http.Client
|
||||
|
||||
// This field changes the client to act like a Messenger client instead of a WhatsApp one.
|
||||
//
|
||||
@@ -222,11 +220,13 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
|
||||
log = waLog.Noop
|
||||
}
|
||||
uniqueIDPrefix := random.Bytes(2)
|
||||
baseHTTPClient := &http.Client{
|
||||
Transport: (http.DefaultTransport.(*http.Transport)).Clone(),
|
||||
}
|
||||
cli := &Client{
|
||||
http: &http.Client{
|
||||
Transport: (http.DefaultTransport.(*http.Transport)).Clone(),
|
||||
},
|
||||
proxy: http.ProxyFromEnvironment,
|
||||
mediaHTTP: baseHTTPClient,
|
||||
websocketHTTP: baseHTTPClient,
|
||||
preLoginHTTP: baseHTTPClient,
|
||||
Store: deviceStore,
|
||||
Log: log,
|
||||
recvLog: log.Sub("Recv"),
|
||||
@@ -292,7 +292,10 @@ func (cli *Client) SetProxyAddress(addr string, opts ...SetProxyOptions) error {
|
||||
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)
|
||||
px, err := proxy.FromURL(parsed, &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -330,21 +333,16 @@ func (cli *Client) SetProxy(proxy Proxy, opts ...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
|
||||
}
|
||||
transport := (http.DefaultTransport.(*http.Transport)).Clone()
|
||||
transport.Proxy = proxy
|
||||
cli.setTransport(transport, opt)
|
||||
}
|
||||
|
||||
type SetProxyOptions struct {
|
||||
// If NoWebsocket is true, the proxy won't be used for the websocket
|
||||
NoWebsocket bool
|
||||
// If OnlyLogin is true, the proxy will be used for the pre-login websocket, but not the post-login one
|
||||
OnlyLogin bool
|
||||
// If NoMedia is true, the proxy won't be used for media uploads/downloads
|
||||
NoMedia bool
|
||||
}
|
||||
@@ -357,27 +355,40 @@ func (cli *Client) SetSOCKSProxy(px proxy.Dialer, opts ...SetProxyOptions) {
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
transport := (http.DefaultTransport.(*http.Transport)).Clone()
|
||||
pxc := px.(proxy.ContextDialer)
|
||||
transport.DialContext = pxc.DialContext
|
||||
cli.setTransport(transport, opt)
|
||||
}
|
||||
|
||||
func (cli *Client) setTransport(transport *http.Transport, opt SetProxyOptions) {
|
||||
if !opt.NoWebsocket {
|
||||
cli.socksProxy = px
|
||||
cli.proxy = nil
|
||||
cli.preLoginHTTP.Transport = transport
|
||||
if !opt.OnlyLogin {
|
||||
cli.websocketHTTP.Transport = transport
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
cli.mediaHTTP.Transport = transport
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// SetMediaHTTPClient sets the HTTP client used to download media.
|
||||
// This will overwrite any set proxy calls.
|
||||
func (cli *Client) SetMediaHTTPClient(h *http.Client) {
|
||||
cli.mediaHTTP = h
|
||||
}
|
||||
|
||||
// SetWebsocketHTTPClient sets the HTTP client used to establish the websocket connection for logged-in sessions.
|
||||
// This will overwrite any set proxy calls.
|
||||
func (cli *Client) SetWebsocketHTTPClient(h *http.Client) {
|
||||
cli.websocketHTTP = h
|
||||
}
|
||||
|
||||
// SetPreLoginHTTPClient sets the HTTP client used to establish the websocket connection before login.
|
||||
// This will overwrite any set proxy calls.
|
||||
func (cli *Client) SetPreLoginHTTPClient(h *http.Client) {
|
||||
cli.preLoginHTTP = h
|
||||
}
|
||||
|
||||
func (cli *Client) getSocketWaitChan() <-chan struct{} {
|
||||
@@ -430,13 +441,13 @@ func (cli *Client) WaitForConnection(timeout time.Duration) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cli *Client) SetWSDialer(dialer *websocket.Dialer) {
|
||||
cli.wsDialer = dialer
|
||||
}
|
||||
|
||||
// Connect connects the client to the WhatsApp web websocket. After connection, it will either
|
||||
// authenticate if there's data in the device store, or emit a QREvent to set up a new link.
|
||||
func (cli *Client) Connect() error {
|
||||
return cli.ConnectContext(cli.BackgroundEventCtx)
|
||||
}
|
||||
|
||||
func (cli *Client) ConnectContext(ctx context.Context) error {
|
||||
if cli == nil {
|
||||
return ErrClientIsNil
|
||||
}
|
||||
@@ -444,24 +455,24 @@ func (cli *Client) Connect() error {
|
||||
cli.socketLock.Lock()
|
||||
defer cli.socketLock.Unlock()
|
||||
|
||||
err := cli.unlockedConnect()
|
||||
err := cli.unlockedConnect(ctx)
|
||||
if exhttp.IsNetworkError(err) && cli.InitialAutoReconnect && cli.EnableAutoReconnect {
|
||||
cli.Log.Errorf("Initial connection failed but reconnecting in background (%v)", err)
|
||||
go cli.dispatchEvent(&events.Disconnected{})
|
||||
go cli.autoReconnect()
|
||||
go cli.autoReconnect(ctx)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *Client) connect() error {
|
||||
func (cli *Client) connect(ctx context.Context) error {
|
||||
cli.socketLock.Lock()
|
||||
defer cli.socketLock.Unlock()
|
||||
|
||||
return cli.unlockedConnect()
|
||||
return cli.unlockedConnect(ctx)
|
||||
}
|
||||
|
||||
func (cli *Client) unlockedConnect() error {
|
||||
func (cli *Client) unlockedConnect(ctx context.Context) error {
|
||||
if cli.socket != nil {
|
||||
if !cli.socket.IsConnected() {
|
||||
cli.unlockedDisconnect()
|
||||
@@ -471,21 +482,11 @@ func (cli *Client) unlockedConnect() error {
|
||||
}
|
||||
|
||||
cli.resetExpectedDisconnect()
|
||||
var wsDialer websocket.Dialer
|
||||
if cli.wsDialer != nil {
|
||||
wsDialer = *cli.wsDialer
|
||||
} else 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
|
||||
}
|
||||
}
|
||||
client := cli.websocketHTTP
|
||||
if cli.Store.ID == nil {
|
||||
client = cli.preLoginHTTP
|
||||
}
|
||||
fs := socket.NewFrameSocket(cli.Log.Sub("Socket"), wsDialer)
|
||||
fs := socket.NewFrameSocket(cli.Log.Sub("Socket"), client)
|
||||
if cli.MessengerConfig != nil {
|
||||
fs.URL = cli.MessengerConfig.WebsocketURL
|
||||
fs.HTTPHeaders.Set("Origin", cli.MessengerConfig.BaseURL)
|
||||
@@ -496,15 +497,15 @@ func (cli *Client) unlockedConnect() error {
|
||||
//fs.HTTPHeaders.Set("Sec-Fetch-Mode", "websocket")
|
||||
//fs.HTTPHeaders.Set("Sec-Fetch-Site", "cross-site")
|
||||
}
|
||||
if err := fs.Connect(); err != nil {
|
||||
if err := fs.Connect(ctx); err != nil {
|
||||
fs.Close(0)
|
||||
return err
|
||||
} else if err = cli.doHandshake(fs, *keys.NewKeyPair()); err != nil {
|
||||
} else if err = cli.doHandshake(ctx, fs, *keys.NewKeyPair()); err != nil {
|
||||
fs.Close(0)
|
||||
return fmt.Errorf("noise handshake failed: %w", err)
|
||||
}
|
||||
go cli.keepAliveLoop(cli.socket.Context())
|
||||
go cli.handlerQueueLoop(cli.socket.Context())
|
||||
go cli.keepAliveLoop(ctx, fs.Context())
|
||||
go cli.handlerQueueLoop(ctx, fs.Context())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -513,7 +514,7 @@ func (cli *Client) IsLoggedIn() bool {
|
||||
return cli != nil && cli.isLoggedIn.Load()
|
||||
}
|
||||
|
||||
func (cli *Client) onDisconnect(ns *socket.NoiseSocket, remote bool) {
|
||||
func (cli *Client) onDisconnect(ctx context.Context, ns *socket.NoiseSocket, remote bool) {
|
||||
ns.Stop(false)
|
||||
cli.socketLock.Lock()
|
||||
defer cli.socketLock.Unlock()
|
||||
@@ -523,7 +524,7 @@ func (cli *Client) onDisconnect(ns *socket.NoiseSocket, remote bool) {
|
||||
if !cli.isExpectedDisconnect() && remote {
|
||||
cli.Log.Debugf("Emitting Disconnected event")
|
||||
go cli.dispatchEvent(&events.Disconnected{})
|
||||
go cli.autoReconnect()
|
||||
go cli.autoReconnect(ctx)
|
||||
} else if remote {
|
||||
cli.Log.Debugf("OnDisconnect() called, but it was expected, so not emitting event")
|
||||
} else {
|
||||
@@ -546,7 +547,7 @@ func (cli *Client) isExpectedDisconnect() bool {
|
||||
return cli.expectedDisconnect.IsSet()
|
||||
}
|
||||
|
||||
func (cli *Client) autoReconnect() {
|
||||
func (cli *Client) autoReconnect(ctx context.Context) {
|
||||
if !cli.EnableAutoReconnect || cli.Store.ID == nil {
|
||||
return
|
||||
}
|
||||
@@ -554,15 +555,20 @@ func (cli *Client) autoReconnect() {
|
||||
autoReconnectDelay := time.Duration(cli.AutoReconnectErrors) * 2 * time.Second
|
||||
cli.Log.Debugf("Automatically reconnecting after %v", autoReconnectDelay)
|
||||
cli.AutoReconnectErrors++
|
||||
if cli.expectedDisconnect.WaitTimeout(autoReconnectDelay) {
|
||||
if cli.expectedDisconnect.WaitTimeoutCtx(ctx, autoReconnectDelay) == nil {
|
||||
cli.Log.Debugf("Cancelling automatic reconnect due to expected disconnect")
|
||||
return
|
||||
} else if ctx.Err() != nil {
|
||||
cli.Log.Debugf("Cancelling automatic reconnect due to context cancellation")
|
||||
return
|
||||
}
|
||||
err := cli.connect()
|
||||
err := cli.connect(ctx)
|
||||
if errors.Is(err, ErrAlreadyConnected) {
|
||||
cli.Log.Debugf("Connect() said we're already connected after autoreconnect sleep")
|
||||
return
|
||||
} else if err != nil {
|
||||
if cli.expectedDisconnect.IsSet() {
|
||||
cli.Log.Debugf("Autoreconnect failed, but disconnect was expected, not reconnecting")
|
||||
return
|
||||
}
|
||||
cli.Log.Errorf("Error reconnecting after autoreconnect sleep: %v", err)
|
||||
@@ -629,7 +635,7 @@ func (cli *Client) Logout(ctx context.Context) error {
|
||||
if ownID.IsEmpty() {
|
||||
return ErrNotLoggedIn
|
||||
}
|
||||
_, err := cli.sendIQ(infoQuery{
|
||||
_, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "md",
|
||||
Type: "set",
|
||||
To: types.ServerJID,
|
||||
@@ -737,7 +743,7 @@ func (cli *Client) RemoveEventHandlers() {
|
||||
cli.eventHandlersLock.Unlock()
|
||||
}
|
||||
|
||||
func (cli *Client) handleFrame(data []byte) {
|
||||
func (cli *Client) handleFrame(ctx context.Context, data []byte) {
|
||||
decompressed, err := waBinary.Unpack(data)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to decompress frame: %v", err)
|
||||
@@ -756,15 +762,19 @@ func (cli *Client) handleFrame(data []byte) {
|
||||
cli.Log.Warnf("Received stream end frame")
|
||||
}
|
||||
// TODO should we do something else?
|
||||
} else if cli.receiveResponse(node) {
|
||||
} else if cli.receiveResponse(ctx, node) {
|
||||
// handled
|
||||
} else if _, ok := cli.nodeHandlers[node.Tag]; ok {
|
||||
select {
|
||||
case cli.handlerQueue <- node:
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
cli.Log.Warnf("Handler queue is full, message ordering is no longer guaranteed")
|
||||
go func() {
|
||||
cli.handlerQueue <- node
|
||||
select {
|
||||
case cli.handlerQueue <- node:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else if node.Tag != "ack" {
|
||||
@@ -772,7 +782,7 @@ func (cli *Client) handleFrame(data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handlerQueueLoop(ctx context.Context) {
|
||||
func (cli *Client) handlerQueueLoop(evtCtx, connCtx context.Context) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
ticker.Stop()
|
||||
cli.Log.Debugf("Starting handler queue loop")
|
||||
@@ -783,7 +793,7 @@ Loop:
|
||||
doneChan := make(chan struct{}, 1)
|
||||
start := time.Now()
|
||||
go func() {
|
||||
cli.nodeHandlers[node.Tag](node)
|
||||
cli.nodeHandlers[node.Tag](evtCtx, node)
|
||||
duration := time.Since(start)
|
||||
doneChan <- struct{}{}
|
||||
if duration > 5*time.Second {
|
||||
@@ -802,14 +812,14 @@ Loop:
|
||||
}
|
||||
cli.Log.Warnf("Continuing handling of %s in background as it's taking too long", node.XMLString())
|
||||
ticker.Stop()
|
||||
case <-ctx.Done():
|
||||
case <-connCtx.Done():
|
||||
cli.Log.Debugf("Closing handler queue loop")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) sendNodeAndGetData(node waBinary.Node) ([]byte, error) {
|
||||
func (cli *Client) sendNodeAndGetData(ctx context.Context, node waBinary.Node) ([]byte, error) {
|
||||
if cli == nil {
|
||||
return nil, ErrClientIsNil
|
||||
}
|
||||
@@ -826,11 +836,11 @@ func (cli *Client) sendNodeAndGetData(node waBinary.Node) ([]byte, error) {
|
||||
}
|
||||
|
||||
cli.sendLog.Debugf("%s", node.XMLString())
|
||||
return payload, sock.SendFrame(payload)
|
||||
return payload, sock.SendFrame(ctx, payload)
|
||||
}
|
||||
|
||||
func (cli *Client) sendNode(node waBinary.Node) error {
|
||||
_, err := cli.sendNodeAndGetData(node)
|
||||
func (cli *Client) sendNode(ctx context.Context, node waBinary.Node) error {
|
||||
_, err := cli.sendNodeAndGetData(ctx, node)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
+6
-10
@@ -16,8 +16,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
|
||||
func (cli *Client) handleStreamError(node *waBinary.Node) {
|
||||
ctx := cli.BackgroundEventCtx
|
||||
func (cli *Client) handleStreamError(ctx context.Context, node *waBinary.Node) {
|
||||
cli.isLoggedIn.Store(false)
|
||||
cli.clearResponseWaiters(node)
|
||||
code, _ := node.Attrs["code"].(string)
|
||||
@@ -33,7 +32,7 @@ func (cli *Client) handleStreamError(node *waBinary.Node) {
|
||||
cli.Log.Infof("Got 515 code, reconnecting...")
|
||||
go func() {
|
||||
cli.Disconnect()
|
||||
err := cli.connect()
|
||||
err := cli.connect(ctx)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to reconnect after 515 code: %v", err)
|
||||
}
|
||||
@@ -70,7 +69,7 @@ func (cli *Client) handleStreamError(node *waBinary.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleIB(node *waBinary.Node) {
|
||||
func (cli *Client) handleIB(ctx context.Context, node *waBinary.Node) {
|
||||
children := node.GetChildren()
|
||||
for _, child := range children {
|
||||
ag := child.AttrGetter()
|
||||
@@ -93,8 +92,7 @@ func (cli *Client) handleIB(node *waBinary.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleConnectFailure(node *waBinary.Node) {
|
||||
ctx := cli.BackgroundEventCtx
|
||||
func (cli *Client) handleConnectFailure(ctx context.Context, node *waBinary.Node) {
|
||||
ag := node.AttrGetter()
|
||||
reason := events.ConnectFailureReason(ag.Int("reason"))
|
||||
message := ag.OptionalString("message")
|
||||
@@ -150,8 +148,7 @@ func (cli *Client) handleConnectFailure(node *waBinary.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handleConnectSuccess(node *waBinary.Node) {
|
||||
ctx := cli.BackgroundEventCtx
|
||||
func (cli *Client) handleConnectSuccess(ctx context.Context, node *waBinary.Node) {
|
||||
cli.Log.Infof("Successfully authenticated")
|
||||
cli.LastSuccessfulConnect = time.Now()
|
||||
cli.AutoReconnectErrors = 0
|
||||
@@ -205,11 +202,10 @@ func (cli *Client) SetPassive(ctx context.Context, passive bool) error {
|
||||
if passive {
|
||||
tag = "passive"
|
||||
}
|
||||
_, err := cli.sendIQ(infoQuery{
|
||||
_, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "passive",
|
||||
Type: "set",
|
||||
To: types.ServerJID,
|
||||
Context: ctx,
|
||||
Content: []waBinary.Node{{Tag: tag}},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -368,7 +368,7 @@ func (cli *Client) doMediaDownloadRequest(ctx context.Context, url string) (*htt
|
||||
req.Header.Set("User-Agent", cli.MessengerConfig.UserAgent)
|
||||
}
|
||||
// TODO user agent for whatsapp downloads?
|
||||
resp, err := cli.http.Do(req)
|
||||
resp, err := cli.mediaHTTP.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ toolchain go1.25.3
|
||||
|
||||
require (
|
||||
github.com/beeper/argo-go v1.1.2
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
go.mau.fi/libsignal v0.2.1
|
||||
go.mau.fi/util v0.9.2
|
||||
|
||||
@@ -8,6 +8,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs=
|
||||
github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -18,8 +20,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
|
||||
@@ -21,8 +21,7 @@ import (
|
||||
const InviteLinkPrefix = "https://chat.whatsapp.com/"
|
||||
|
||||
func (cli *Client) sendGroupIQ(ctx context.Context, iqType infoQueryType, jid types.JID, content waBinary.Node) (*waBinary.Node, error) {
|
||||
return cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
return cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:g2",
|
||||
Type: iqType,
|
||||
To: jid,
|
||||
@@ -135,8 +134,8 @@ func (cli *Client) CreateGroup(ctx context.Context, req ReqCreateGroup) (*types.
|
||||
}
|
||||
|
||||
// UnlinkGroup removes a child group from a parent community.
|
||||
func (cli *Client) UnlinkGroup(parent, child types.JID) error {
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, parent, waBinary.Node{
|
||||
func (cli *Client) UnlinkGroup(ctx context.Context, parent, child types.JID) error {
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, parent, waBinary.Node{
|
||||
Tag: "unlink",
|
||||
Attrs: waBinary.Attrs{"unlink_type": string(types.GroupLinkChangeTypeSub)},
|
||||
Content: []waBinary.Node{{
|
||||
@@ -150,8 +149,8 @@ func (cli *Client) UnlinkGroup(parent, child types.JID) error {
|
||||
// LinkGroup adds an existing group as a child group in a community.
|
||||
//
|
||||
// To create a new group within a community, set LinkedParentJID in the CreateGroup request.
|
||||
func (cli *Client) LinkGroup(parent, child types.JID) error {
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, parent, waBinary.Node{
|
||||
func (cli *Client) LinkGroup(ctx context.Context, parent, child types.JID) error {
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, parent, waBinary.Node{
|
||||
Tag: "links",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "link",
|
||||
@@ -166,8 +165,8 @@ func (cli *Client) LinkGroup(parent, child types.JID) error {
|
||||
}
|
||||
|
||||
// LeaveGroup leaves the specified group on WhatsApp.
|
||||
func (cli *Client) LeaveGroup(jid types.JID) error {
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, types.GroupServerJID, waBinary.Node{
|
||||
func (cli *Client) LeaveGroup(ctx context.Context, jid types.JID) error {
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, types.GroupServerJID, waBinary.Node{
|
||||
Tag: "leave",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "group",
|
||||
@@ -187,7 +186,7 @@ const (
|
||||
)
|
||||
|
||||
// UpdateGroupParticipants can be used to add, remove, promote and demote members in a WhatsApp group.
|
||||
func (cli *Client) UpdateGroupParticipants(jid types.JID, participantChanges []types.JID, action ParticipantChange) ([]types.GroupParticipant, error) {
|
||||
func (cli *Client) UpdateGroupParticipants(ctx context.Context, jid types.JID, participantChanges []types.JID, action ParticipantChange) ([]types.GroupParticipant, error) {
|
||||
content := make([]waBinary.Node, len(participantChanges))
|
||||
for i, participantJID := range participantChanges {
|
||||
content[i] = waBinary.Node{
|
||||
@@ -195,7 +194,7 @@ func (cli *Client) UpdateGroupParticipants(jid types.JID, participantChanges []t
|
||||
Attrs: waBinary.Attrs{"jid": participantJID},
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
resp, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{
|
||||
Tag: string(action),
|
||||
Content: content,
|
||||
})
|
||||
@@ -215,8 +214,8 @@ func (cli *Client) UpdateGroupParticipants(jid types.JID, participantChanges []t
|
||||
}
|
||||
|
||||
// GetGroupRequestParticipants gets the list of participants that have requested to join the group.
|
||||
func (cli *Client) GetGroupRequestParticipants(jid types.JID) ([]types.GroupParticipantRequest, error) {
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqGet, jid, waBinary.Node{
|
||||
func (cli *Client) GetGroupRequestParticipants(ctx context.Context, jid types.JID) ([]types.GroupParticipantRequest, error) {
|
||||
resp, err := cli.sendGroupIQ(ctx, iqGet, jid, waBinary.Node{
|
||||
Tag: "membership_approval_requests",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -245,7 +244,7 @@ const (
|
||||
)
|
||||
|
||||
// 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) {
|
||||
func (cli *Client) UpdateGroupRequestParticipants(ctx context.Context, 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{
|
||||
@@ -253,7 +252,7 @@ func (cli *Client) UpdateGroupRequestParticipants(jid types.JID, participantChan
|
||||
Attrs: waBinary.Attrs{"jid": participantJID},
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
resp, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{
|
||||
Tag: "membership_requests_action",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: string(action),
|
||||
@@ -282,7 +281,7 @@ func (cli *Client) UpdateGroupRequestParticipants(jid types.JID, participantChan
|
||||
// SetGroupPhoto updates the group picture/icon of the given group on WhatsApp.
|
||||
// The avatar should be a JPEG photo, other formats may be rejected with ErrInvalidImageFormat.
|
||||
// The bytes can be nil to remove the photo. Returns the new picture ID.
|
||||
func (cli *Client) SetGroupPhoto(jid types.JID, avatar []byte) (string, error) {
|
||||
func (cli *Client) SetGroupPhoto(ctx context.Context, jid types.JID, avatar []byte) (string, error) {
|
||||
var content interface{}
|
||||
if avatar != nil {
|
||||
content = []waBinary.Node{{
|
||||
@@ -291,7 +290,7 @@ func (cli *Client) SetGroupPhoto(jid types.JID, avatar []byte) (string, error) {
|
||||
Content: avatar,
|
||||
}}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:profile:picture",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
@@ -314,8 +313,8 @@ func (cli *Client) SetGroupPhoto(jid types.JID, avatar []byte) (string, error) {
|
||||
}
|
||||
|
||||
// SetGroupName updates the name (subject) of the given group on WhatsApp.
|
||||
func (cli *Client) SetGroupName(jid types.JID, name string) error {
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
func (cli *Client) SetGroupName(ctx context.Context, jid types.JID, name string) error {
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{
|
||||
Tag: "subject",
|
||||
Content: []byte(name),
|
||||
})
|
||||
@@ -327,9 +326,9 @@ func (cli *Client) SetGroupName(jid types.JID, name string) error {
|
||||
// The previousID and newID fields are optional. If the previous ID is not specified, this will
|
||||
// automatically fetch the current group info to find the previous topic ID. If the new ID is not
|
||||
// specified, one will be generated with Client.GenerateMessageID().
|
||||
func (cli *Client) SetGroupTopic(jid types.JID, previousID, newID, topic string) error {
|
||||
func (cli *Client) SetGroupTopic(ctx context.Context, jid types.JID, previousID, newID, topic string) error {
|
||||
if previousID == "" {
|
||||
oldInfo, err := cli.GetGroupInfo(jid)
|
||||
oldInfo, err := cli.GetGroupInfo(ctx, jid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get old group info to update topic: %v", err)
|
||||
}
|
||||
@@ -352,7 +351,7 @@ func (cli *Client) SetGroupTopic(jid types.JID, previousID, newID, topic string)
|
||||
attrs["delete"] = "true"
|
||||
content = nil
|
||||
}
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{
|
||||
Tag: "description",
|
||||
Attrs: attrs,
|
||||
Content: content,
|
||||
@@ -361,34 +360,34 @@ func (cli *Client) SetGroupTopic(jid types.JID, previousID, newID, topic string)
|
||||
}
|
||||
|
||||
// SetGroupLocked changes whether the group is locked (i.e. whether only admins can modify group info).
|
||||
func (cli *Client) SetGroupLocked(jid types.JID, locked bool) error {
|
||||
func (cli *Client) SetGroupLocked(ctx context.Context, jid types.JID, locked bool) error {
|
||||
tag := "locked"
|
||||
if !locked {
|
||||
tag = "unlocked"
|
||||
}
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{Tag: tag})
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{Tag: tag})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetGroupAnnounce changes whether the group is in announce mode (i.e. whether only admins can send messages).
|
||||
func (cli *Client) SetGroupAnnounce(jid types.JID, announce bool) error {
|
||||
func (cli *Client) SetGroupAnnounce(ctx context.Context, jid types.JID, announce bool) error {
|
||||
tag := "announcement"
|
||||
if !announce {
|
||||
tag = "not_announcement"
|
||||
}
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{Tag: tag})
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{Tag: tag})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetGroupInviteLink requests the invite link to the group from the WhatsApp servers.
|
||||
//
|
||||
// If reset is true, then the old invite link will be revoked and a new one generated.
|
||||
func (cli *Client) GetGroupInviteLink(jid types.JID, reset bool) (string, error) {
|
||||
func (cli *Client) GetGroupInviteLink(ctx context.Context, jid types.JID, reset bool) (string, error) {
|
||||
iqType := iqGet
|
||||
if reset {
|
||||
iqType = iqSet
|
||||
}
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqType, jid, waBinary.Node{Tag: "invite"})
|
||||
resp, err := cli.sendGroupIQ(ctx, iqType, jid, waBinary.Node{Tag: "invite"})
|
||||
if errors.Is(err, ErrIQNotAuthorized) {
|
||||
return "", wrapIQError(ErrGroupInviteLinkUnauthorized, err)
|
||||
} else if errors.Is(err, ErrIQNotFound) {
|
||||
@@ -408,8 +407,8 @@ func (cli *Client) GetGroupInviteLink(jid types.JID, reset bool) (string, error)
|
||||
// GetGroupInfoFromInvite gets the group info from an invite message.
|
||||
//
|
||||
// Note that this is specifically for invite messages, not invite links. Use GetGroupInfoFromLink for resolving chat.whatsapp.com links.
|
||||
func (cli *Client) GetGroupInfoFromInvite(jid, inviter types.JID, code string, expiration int64) (*types.GroupInfo, error) {
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqGet, jid, waBinary.Node{
|
||||
func (cli *Client) GetGroupInfoFromInvite(ctx context.Context, jid, inviter types.JID, code string, expiration int64) (*types.GroupInfo, error) {
|
||||
resp, err := cli.sendGroupIQ(ctx, iqGet, jid, waBinary.Node{
|
||||
Tag: "query",
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "add_request",
|
||||
@@ -433,8 +432,8 @@ func (cli *Client) GetGroupInfoFromInvite(jid, inviter types.JID, code string, e
|
||||
// JoinGroupWithInvite joins a group using an invite message.
|
||||
//
|
||||
// Note that this is specifically for invite messages, not invite links. Use JoinGroupWithLink for joining with chat.whatsapp.com links.
|
||||
func (cli *Client) JoinGroupWithInvite(jid, inviter types.JID, code string, expiration int64) error {
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, waBinary.Node{
|
||||
func (cli *Client) JoinGroupWithInvite(ctx context.Context, jid, inviter types.JID, code string, expiration int64) error {
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, waBinary.Node{
|
||||
Tag: "accept",
|
||||
Attrs: waBinary.Attrs{
|
||||
"code": code,
|
||||
@@ -447,9 +446,9 @@ func (cli *Client) JoinGroupWithInvite(jid, inviter types.JID, code string, expi
|
||||
|
||||
// GetGroupInfoFromLink resolves the given invite link and asks the WhatsApp servers for info about the group.
|
||||
// This will not cause the user to join the group.
|
||||
func (cli *Client) GetGroupInfoFromLink(code string) (*types.GroupInfo, error) {
|
||||
func (cli *Client) GetGroupInfoFromLink(ctx context.Context, code string) (*types.GroupInfo, error) {
|
||||
code = strings.TrimPrefix(code, InviteLinkPrefix)
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqGet, types.GroupServerJID, waBinary.Node{
|
||||
resp, err := cli.sendGroupIQ(ctx, iqGet, types.GroupServerJID, waBinary.Node{
|
||||
Tag: "invite",
|
||||
Attrs: waBinary.Attrs{"code": code},
|
||||
})
|
||||
@@ -468,9 +467,9 @@ func (cli *Client) GetGroupInfoFromLink(code string) (*types.GroupInfo, error) {
|
||||
}
|
||||
|
||||
// JoinGroupWithLink joins the group using the given invite link.
|
||||
func (cli *Client) JoinGroupWithLink(code string) (types.JID, error) {
|
||||
func (cli *Client) JoinGroupWithLink(ctx context.Context, code string) (types.JID, error) {
|
||||
code = strings.TrimPrefix(code, InviteLinkPrefix)
|
||||
resp, err := cli.sendGroupIQ(context.TODO(), iqSet, types.GroupServerJID, waBinary.Node{
|
||||
resp, err := cli.sendGroupIQ(ctx, iqSet, types.GroupServerJID, waBinary.Node{
|
||||
Tag: "invite",
|
||||
Attrs: waBinary.Attrs{"code": code},
|
||||
})
|
||||
@@ -538,8 +537,8 @@ func (cli *Client) GetJoinedGroups(ctx context.Context) ([]*types.GroupInfo, err
|
||||
}
|
||||
|
||||
// GetSubGroups gets the subgroups of the given community.
|
||||
func (cli *Client) GetSubGroups(community types.JID) ([]*types.GroupLinkTarget, error) {
|
||||
res, err := cli.sendGroupIQ(context.TODO(), iqGet, community, waBinary.Node{Tag: "sub_groups"})
|
||||
func (cli *Client) GetSubGroups(ctx context.Context, community types.JID) ([]*types.GroupLinkTarget, error) {
|
||||
res, err := cli.sendGroupIQ(ctx, iqGet, community, waBinary.Node{Tag: "sub_groups"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -561,8 +560,8 @@ func (cli *Client) GetSubGroups(community types.JID) ([]*types.GroupLinkTarget,
|
||||
}
|
||||
|
||||
// GetLinkedGroupsParticipants gets all the participants in the groups of the given community.
|
||||
func (cli *Client) GetLinkedGroupsParticipants(community types.JID) ([]types.JID, error) {
|
||||
res, err := cli.sendGroupIQ(context.TODO(), iqGet, community, waBinary.Node{Tag: "linked_groups_participants"})
|
||||
func (cli *Client) GetLinkedGroupsParticipants(ctx context.Context, community types.JID) ([]types.JID, error) {
|
||||
res, err := cli.sendGroupIQ(ctx, iqGet, community, waBinary.Node{Tag: "linked_groups_participants"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -572,7 +571,7 @@ func (cli *Client) GetLinkedGroupsParticipants(community types.JID) ([]types.JID
|
||||
}
|
||||
members, lidPairs := parseParticipantList(&participants)
|
||||
if len(lidPairs) > 0 {
|
||||
err = cli.Store.LIDs.PutManyLIDMappings(context.TODO(), lidPairs)
|
||||
err = cli.Store.LIDs.PutManyLIDMappings(ctx, lidPairs)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to store LID mappings for community participants: %v", err)
|
||||
}
|
||||
@@ -581,8 +580,8 @@ func (cli *Client) GetLinkedGroupsParticipants(community types.JID) ([]types.JID
|
||||
}
|
||||
|
||||
// GetGroupInfo requests basic info about a group chat from the WhatsApp servers.
|
||||
func (cli *Client) GetGroupInfo(jid types.JID) (*types.GroupInfo, error) {
|
||||
return cli.getGroupInfo(context.TODO(), jid, true)
|
||||
func (cli *Client) GetGroupInfo(ctx context.Context, jid types.JID) (*types.GroupInfo, error) {
|
||||
return cli.getGroupInfo(ctx, jid, true)
|
||||
}
|
||||
|
||||
func (cli *Client) cacheGroupInfo(groupInfo *types.GroupInfo, lock bool) ([]store.LIDMapping, []store.RedactedPhoneEntry) {
|
||||
@@ -1001,7 +1000,7 @@ func (cli *Client) parseGroupNotification(node *waBinary.Node) (any, []store.LID
|
||||
}
|
||||
|
||||
// SetGroupJoinApprovalMode sets the group join approval mode to 'on' or 'off'.
|
||||
func (cli *Client) SetGroupJoinApprovalMode(jid types.JID, mode bool) error {
|
||||
func (cli *Client) SetGroupJoinApprovalMode(ctx context.Context, jid types.JID, mode bool) error {
|
||||
modeStr := "off"
|
||||
if mode {
|
||||
modeStr = "on"
|
||||
@@ -1017,12 +1016,12 @@ func (cli *Client) SetGroupJoinApprovalMode(jid types.JID, mode bool) error {
|
||||
},
|
||||
}
|
||||
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, content)
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, content)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetGroupMemberAddMode sets the group member add mode to 'admin_add' or 'all_member_add'.
|
||||
func (cli *Client) SetGroupMemberAddMode(jid types.JID, mode types.GroupMemberAddMode) error {
|
||||
func (cli *Client) SetGroupMemberAddMode(ctx context.Context, jid types.JID, mode types.GroupMemberAddMode) error {
|
||||
if mode != types.GroupMemberAddModeAdmin && mode != types.GroupMemberAddModeAllMember {
|
||||
return errors.New("invalid mode, must be 'admin_add' or 'all_member_add'")
|
||||
}
|
||||
@@ -1032,12 +1031,12 @@ func (cli *Client) SetGroupMemberAddMode(jid types.JID, mode types.GroupMemberAd
|
||||
Content: []byte(mode),
|
||||
}
|
||||
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, content)
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, content)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetGroupDescription updates the group description.
|
||||
func (cli *Client) SetGroupDescription(jid types.JID, description string) error {
|
||||
func (cli *Client) SetGroupDescription(ctx context.Context, jid types.JID, description string) error {
|
||||
content := waBinary.Node{
|
||||
Tag: "description",
|
||||
Content: []waBinary.Node{
|
||||
@@ -1048,6 +1047,6 @@ func (cli *Client) SetGroupDescription(jid types.JID, description string) error
|
||||
},
|
||||
}
|
||||
|
||||
_, err := cli.sendGroupIQ(context.TODO(), iqSet, jid, content)
|
||||
_, err := cli.sendGroupIQ(ctx, iqSet, jid, content)
|
||||
return err
|
||||
}
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ package whatsmeow
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -26,7 +27,7 @@ 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 {
|
||||
func (cli *Client) doHandshake(ctx context.Context, fs *socket.FrameSocket, ephemeralKP keys.KeyPair) error {
|
||||
nh := socket.NewNoiseHandshake()
|
||||
nh.Start(socket.NoiseStartPattern, fs.Header)
|
||||
nh.Authenticate(ephemeralKP.Pub[:])
|
||||
@@ -117,7 +118,7 @@ func (cli *Client) doHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair)
|
||||
return fmt.Errorf("failed to send handshake finish message: %w", err)
|
||||
}
|
||||
|
||||
ns, err := nh.Finish(fs, cli.handleFrame, cli.onDisconnect)
|
||||
ns, err := nh.Finish(ctx, fs, cli.handleFrame, cli.onDisconnect)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create noise socket: %w", err)
|
||||
}
|
||||
|
||||
Generated
+72
-68
@@ -83,8 +83,12 @@ func (int *DangerousInternalClient) GetStatusBroadcastRecipients(ctx context.Con
|
||||
return int.c.getStatusBroadcastRecipients(ctx)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleCallEvent(node *waBinary.Node) {
|
||||
int.c.handleCallEvent(node)
|
||||
func (int *DangerousInternalClient) HandleCallEvent(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleCallEvent(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SetTransport(transport *http.Transport, opt SetProxyOptions) {
|
||||
int.c.setTransport(transport, opt)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) GetSocketWaitChan() <-chan struct{} {
|
||||
@@ -103,16 +107,16 @@ func (int *DangerousInternalClient) GetOwnLID() types.JID {
|
||||
return int.c.getOwnLID()
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) Connect() error {
|
||||
return int.c.connect()
|
||||
func (int *DangerousInternalClient) Connect(ctx context.Context) error {
|
||||
return int.c.connect(ctx)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) UnlockedConnect() error {
|
||||
return int.c.unlockedConnect()
|
||||
func (int *DangerousInternalClient) UnlockedConnect(ctx context.Context) error {
|
||||
return int.c.unlockedConnect(ctx)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) OnDisconnect(ns *socket.NoiseSocket, remote bool) {
|
||||
int.c.onDisconnect(ns, remote)
|
||||
func (int *DangerousInternalClient) OnDisconnect(ctx context.Context, ns *socket.NoiseSocket, remote bool) {
|
||||
int.c.onDisconnect(ctx, ns, remote)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) ExpectDisconnect() {
|
||||
@@ -127,48 +131,48 @@ func (int *DangerousInternalClient) IsExpectedDisconnect() bool {
|
||||
return int.c.isExpectedDisconnect()
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) AutoReconnect() {
|
||||
int.c.autoReconnect()
|
||||
func (int *DangerousInternalClient) AutoReconnect(ctx context.Context) {
|
||||
int.c.autoReconnect(ctx)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) UnlockedDisconnect() {
|
||||
int.c.unlockedDisconnect()
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleFrame(data []byte) {
|
||||
int.c.handleFrame(data)
|
||||
func (int *DangerousInternalClient) HandleFrame(ctx context.Context, data []byte) {
|
||||
int.c.handleFrame(ctx, data)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandlerQueueLoop(ctx context.Context) {
|
||||
int.c.handlerQueueLoop(ctx)
|
||||
func (int *DangerousInternalClient) HandlerQueueLoop(evtCtx, connCtx context.Context) {
|
||||
int.c.handlerQueueLoop(evtCtx, connCtx)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendNodeAndGetData(node waBinary.Node) ([]byte, error) {
|
||||
return int.c.sendNodeAndGetData(node)
|
||||
func (int *DangerousInternalClient) SendNodeAndGetData(ctx context.Context, node waBinary.Node) ([]byte, error) {
|
||||
return int.c.sendNodeAndGetData(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendNode(node waBinary.Node) error {
|
||||
return int.c.sendNode(node)
|
||||
func (int *DangerousInternalClient) SendNode(ctx context.Context, node waBinary.Node) error {
|
||||
return int.c.sendNode(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DispatchEvent(evt any) (handlerFailed bool) {
|
||||
return int.c.dispatchEvent(evt)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleStreamError(node *waBinary.Node) {
|
||||
int.c.handleStreamError(node)
|
||||
func (int *DangerousInternalClient) HandleStreamError(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleStreamError(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleIB(node *waBinary.Node) {
|
||||
int.c.handleIB(node)
|
||||
func (int *DangerousInternalClient) HandleIB(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleIB(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleConnectFailure(node *waBinary.Node) {
|
||||
int.c.handleConnectFailure(node)
|
||||
func (int *DangerousInternalClient) HandleConnectFailure(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleConnectFailure(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleConnectSuccess(node *waBinary.Node) {
|
||||
int.c.handleConnectSuccess(node)
|
||||
func (int *DangerousInternalClient) HandleConnectSuccess(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleConnectSuccess(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DownloadAndDecrypt(ctx context.Context, url string, mediaKey []byte, appInfo MediaType, fileLength int, fileEncSHA256, fileSHA256 []byte) (data []byte, err error) {
|
||||
@@ -243,12 +247,12 @@ func (int *DangerousInternalClient) ParseGroupNotification(node *waBinary.Node)
|
||||
return int.c.parseGroupNotification(node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DoHandshake(fs *socket.FrameSocket, ephemeralKP keys.KeyPair) error {
|
||||
return int.c.doHandshake(fs, ephemeralKP)
|
||||
func (int *DangerousInternalClient) DoHandshake(ctx context.Context, fs *socket.FrameSocket, ephemeralKP keys.KeyPair) error {
|
||||
return int.c.doHandshake(ctx, fs, ephemeralKP)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) KeepAliveLoop(ctx context.Context) {
|
||||
int.c.keepAliveLoop(ctx)
|
||||
func (int *DangerousInternalClient) KeepAliveLoop(ctx, connCtx context.Context) {
|
||||
int.c.keepAliveLoop(ctx, connCtx)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendKeepAlive(ctx context.Context) (isSuccess, shouldContinue bool) {
|
||||
@@ -267,8 +271,8 @@ func (int *DangerousInternalClient) HandleMediaRetryNotification(ctx context.Con
|
||||
int.c.handleMediaRetryNotification(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleEncryptedMessage(node *waBinary.Node) {
|
||||
int.c.handleEncryptedMessage(node)
|
||||
func (int *DangerousInternalClient) HandleEncryptedMessage(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleEncryptedMessage(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) ParseMessageSource(node *waBinary.Node, requireParticipant bool) (source types.MessageSource, err error) {
|
||||
@@ -363,8 +367,8 @@ func (int *DangerousInternalClient) HandleDecryptedMessage(ctx context.Context,
|
||||
return int.c.handleDecryptedMessage(ctx, info, msg, retryCount)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendProtocolMessageReceipt(id types.MessageID, msgType types.ReceiptType) {
|
||||
int.c.sendProtocolMessageReceipt(id, msgType)
|
||||
func (int *DangerousInternalClient) SendProtocolMessageReceipt(ctx context.Context, id types.MessageID, msgType types.ReceiptType) {
|
||||
int.c.sendProtocolMessageReceipt(ctx, id, msgType)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DecryptMsgSecret(ctx context.Context, msg *events.Message, useCase MsgSecretType, encrypted messageEncryptedSecret, origMsgKey *waCommon.MessageKey) ([]byte, error) {
|
||||
@@ -383,8 +387,8 @@ func (int *DangerousInternalClient) SendMexIQ(ctx context.Context, queryID strin
|
||||
return int.c.sendMexIQ(ctx, queryID, variables)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) GetNewsletterInfo(input map[string]any, fetchViewerMeta bool) (*types.NewsletterMetadata, error) {
|
||||
return int.c.getNewsletterInfo(input, fetchViewerMeta)
|
||||
func (int *DangerousInternalClient) GetNewsletterInfo(ctx context.Context, input map[string]any, fetchViewerMeta bool) (*types.NewsletterMetadata, error) {
|
||||
return int.c.getNewsletterInfo(ctx, input, fetchViewerMeta)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleEncryptNotification(ctx context.Context, node *waBinary.Node) {
|
||||
@@ -439,8 +443,8 @@ func (int *DangerousInternalClient) HandleStatusNotification(ctx context.Context
|
||||
int.c.handleStatusNotification(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleNotification(node *waBinary.Node) {
|
||||
int.c.handleNotification(node)
|
||||
func (int *DangerousInternalClient) HandleNotification(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleNotification(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) TryHandleCodePairNotification(ctx context.Context, parentNode *waBinary.Node) {
|
||||
@@ -451,28 +455,28 @@ func (int *DangerousInternalClient) HandleCodePairNotification(ctx context.Conte
|
||||
return int.c.handleCodePairNotification(ctx, parentNode)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleIQ(node *waBinary.Node) {
|
||||
int.c.handleIQ(node)
|
||||
func (int *DangerousInternalClient) HandleIQ(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleIQ(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandlePairDevice(node *waBinary.Node) {
|
||||
int.c.handlePairDevice(node)
|
||||
func (int *DangerousInternalClient) HandlePairDevice(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handlePairDevice(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) MakeQRData(ref string) string {
|
||||
return int.c.makeQRData(ref)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandlePairSuccess(node *waBinary.Node) {
|
||||
int.c.handlePairSuccess(node)
|
||||
func (int *DangerousInternalClient) HandlePairSuccess(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handlePairSuccess(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandlePair(ctx context.Context, deviceIdentityBytes []byte, reqID, businessName, platform string, jid, lid types.JID) error {
|
||||
return int.c.handlePair(ctx, deviceIdentityBytes, reqID, businessName, platform, jid, lid)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendPairError(id string, code int, text string) {
|
||||
int.c.sendPairError(id, code, text)
|
||||
func (int *DangerousInternalClient) SendPairError(ctx context.Context, id string, code int, text string) {
|
||||
int.c.sendPairError(ctx, id, code, text)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) GetServerPreKeyCount(ctx context.Context) (int, error) {
|
||||
@@ -491,12 +495,12 @@ func (int *DangerousInternalClient) FetchPreKeys(ctx context.Context, users []ty
|
||||
return int.c.fetchPreKeys(ctx, users)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleChatState(node *waBinary.Node) {
|
||||
int.c.handleChatState(node)
|
||||
func (int *DangerousInternalClient) HandleChatState(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleChatState(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandlePresence(node *waBinary.Node) {
|
||||
int.c.handlePresence(node)
|
||||
func (int *DangerousInternalClient) HandlePresence(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handlePresence(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) ParsePrivacySettings(privacyNode *waBinary.Node, settings *types.PrivacySettings) *events.PrivacySettings {
|
||||
@@ -507,8 +511,8 @@ func (int *DangerousInternalClient) HandlePrivacySettingsNotification(ctx contex
|
||||
int.c.handlePrivacySettingsNotification(ctx, privacyNode)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleReceipt(node *waBinary.Node) {
|
||||
int.c.handleReceipt(node)
|
||||
func (int *DangerousInternalClient) HandleReceipt(ctx context.Context, node *waBinary.Node) {
|
||||
int.c.handleReceipt(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) HandleGroupedReceipt(partialReceipt events.Receipt, participants *waBinary.Node) {
|
||||
@@ -527,12 +531,12 @@ func (int *DangerousInternalClient) MaybeDeferredAck(ctx context.Context, node *
|
||||
return int.c.maybeDeferredAck(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendAck(node *waBinary.Node, error int) {
|
||||
int.c.sendAck(node, error)
|
||||
func (int *DangerousInternalClient) SendAck(ctx context.Context, node *waBinary.Node, error int) {
|
||||
int.c.sendAck(ctx, node, error)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendMessageReceipt(info *types.MessageInfo, node *waBinary.Node) {
|
||||
int.c.sendMessageReceipt(info, node)
|
||||
func (int *DangerousInternalClient) SendMessageReceipt(ctx context.Context, info *types.MessageInfo, node *waBinary.Node) {
|
||||
int.c.sendMessageReceipt(ctx, info, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) GenerateRequestID() string {
|
||||
@@ -551,24 +555,24 @@ func (int *DangerousInternalClient) CancelResponse(reqID string, ch chan *waBina
|
||||
int.c.cancelResponse(reqID, ch)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) ReceiveResponse(data *waBinary.Node) bool {
|
||||
return int.c.receiveResponse(data)
|
||||
func (int *DangerousInternalClient) ReceiveResponse(ctx context.Context, data *waBinary.Node) bool {
|
||||
return int.c.receiveResponse(ctx, data)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendIQAsyncAndGetData(query *infoQuery) (<-chan *waBinary.Node, []byte, error) {
|
||||
return int.c.sendIQAsyncAndGetData(query)
|
||||
func (int *DangerousInternalClient) SendIQAsyncAndGetData(ctx context.Context, query *infoQuery) (<-chan *waBinary.Node, []byte, error) {
|
||||
return int.c.sendIQAsyncAndGetData(ctx, query)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) {
|
||||
return int.c.sendIQAsync(query)
|
||||
func (int *DangerousInternalClient) SendIQAsync(ctx context.Context, query infoQuery) (<-chan *waBinary.Node, error) {
|
||||
return int.c.sendIQAsync(ctx, query)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendIQ(query infoQuery) (*waBinary.Node, error) {
|
||||
return int.c.sendIQ(query)
|
||||
func (int *DangerousInternalClient) SendIQ(ctx context.Context, query infoQuery) (*waBinary.Node, error) {
|
||||
return int.c.sendIQ(ctx, query)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) RetryFrame(reqType, id string, data []byte, origResp *waBinary.Node, ctx context.Context, timeout time.Duration) (*waBinary.Node, error) {
|
||||
return int.c.retryFrame(reqType, id, data, origResp, ctx, timeout)
|
||||
func (int *DangerousInternalClient) RetryFrame(ctx context.Context, reqType, id string, data []byte, origResp *waBinary.Node, timeout time.Duration) (*waBinary.Node, error) {
|
||||
return int.c.retryFrame(ctx, reqType, id, data, origResp, timeout)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) AddRecentMessage(to types.JID, id types.MessageID, wa *waE2E.Message, fb *waMsgApplication.MessageApplication) {
|
||||
@@ -635,8 +639,8 @@ func (int *DangerousInternalClient) EncryptMessageForDeviceV3(ctx context.Contex
|
||||
return int.c.encryptMessageForDeviceV3(ctx, payload, skdm, dsm, to, bundle, extraAttrs)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendNewsletter(to types.JID, id types.MessageID, message *waE2E.Message, mediaID string, timings *MessageDebugTimings) ([]byte, error) {
|
||||
return int.c.sendNewsletter(to, id, message, mediaID, timings)
|
||||
func (int *DangerousInternalClient) SendNewsletter(ctx context.Context, to types.JID, id types.MessageID, message *waE2E.Message, mediaID string, timings *MessageDebugTimings) ([]byte, error) {
|
||||
return int.c.sendNewsletter(ctx, to, id, message, mediaID, timings)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) SendGroup(ctx context.Context, ownID, to types.JID, participants []types.JID, id types.MessageID, message *waE2E.Message, timings *MessageDebugTimings, extraParams nodeExtraParams) (string, []byte, error) {
|
||||
|
||||
+8
-6
@@ -27,14 +27,14 @@ var (
|
||||
KeepAliveMaxFailTime = 3 * time.Minute
|
||||
)
|
||||
|
||||
func (cli *Client) keepAliveLoop(ctx context.Context) {
|
||||
func (cli *Client) keepAliveLoop(ctx, connCtx context.Context) {
|
||||
lastSuccess := time.Now()
|
||||
var errorCount int
|
||||
for {
|
||||
interval := rand.Int64N(KeepAliveIntervalMax.Milliseconds()-KeepAliveIntervalMin.Milliseconds()) + KeepAliveIntervalMin.Milliseconds()
|
||||
select {
|
||||
case <-time.After(time.Duration(interval) * time.Millisecond):
|
||||
isSuccess, shouldContinue := cli.sendKeepAlive(ctx)
|
||||
isSuccess, shouldContinue := cli.sendKeepAlive(connCtx)
|
||||
if !shouldContinue {
|
||||
return
|
||||
} else if !isSuccess {
|
||||
@@ -47,7 +47,7 @@ func (cli *Client) keepAliveLoop(ctx context.Context) {
|
||||
cli.Log.Debugf("Forcing reconnect due to keepalive failure")
|
||||
cli.Disconnect()
|
||||
cli.resetExpectedDisconnect()
|
||||
go cli.autoReconnect()
|
||||
go cli.autoReconnect(ctx)
|
||||
}
|
||||
} else {
|
||||
if errorCount > 0 {
|
||||
@@ -56,19 +56,21 @@ func (cli *Client) keepAliveLoop(ctx context.Context) {
|
||||
}
|
||||
lastSuccess = time.Now()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
case <-connCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) sendKeepAlive(ctx context.Context) (isSuccess, shouldContinue bool) {
|
||||
respCh, err := cli.sendIQAsync(infoQuery{
|
||||
respCh, err := cli.sendIQAsync(ctx, infoQuery{
|
||||
Namespace: "w:p",
|
||||
Type: "get",
|
||||
To: types.ServerJID,
|
||||
})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return false, false
|
||||
} else if err != nil {
|
||||
cli.Log.Warnf("Failed to send keepalive: %v", err)
|
||||
return false, true
|
||||
}
|
||||
|
||||
+1
-2
@@ -58,8 +58,7 @@ func (cli *Client) refreshMediaConn(ctx context.Context, force bool) (*MediaConn
|
||||
}
|
||||
|
||||
func (cli *Client) queryMediaConn(ctx context.Context) (*MediaConn, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:m",
|
||||
Type: "set",
|
||||
To: types.ServerJID,
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ func encryptMediaRetryReceipt(messageID types.MessageID, mediaKey []byte) (ciphe
|
||||
// // Alternatively, you can use cli.DownloadMediaWithPath and provide the individual fields manually.
|
||||
// }
|
||||
// }
|
||||
func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey []byte) error {
|
||||
func (cli *Client) SendMediaRetryReceipt(ctx context.Context, message *types.MessageInfo, mediaKey []byte) error {
|
||||
if cli == nil {
|
||||
return ErrClientIsNil
|
||||
}
|
||||
@@ -100,7 +100,7 @@ func (cli *Client) SendMediaRetryReceipt(message *types.MessageInfo, mediaKey []
|
||||
{Tag: "enc_iv", Content: iv},
|
||||
}
|
||||
|
||||
err = cli.sendNode(waBinary.Node{
|
||||
err = cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: waBinary.Attrs{
|
||||
"id": message.ID,
|
||||
|
||||
+14
-15
@@ -40,8 +40,7 @@ import (
|
||||
|
||||
var pbSerializer = store.SignalProtobufSerializer
|
||||
|
||||
func (cli *Client) handleEncryptedMessage(node *waBinary.Node) {
|
||||
ctx := cli.BackgroundEventCtx
|
||||
func (cli *Client) handleEncryptedMessage(ctx context.Context, node *waBinary.Node) {
|
||||
info, err := cli.parseMessageInfo(node)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to parse message: %v", err)
|
||||
@@ -52,10 +51,10 @@ func (cli *Client) handleEncryptedMessage(node *waBinary.Node) {
|
||||
cli.StoreLIDPNMapping(ctx, info.RecipientAlt, info.Chat)
|
||||
}
|
||||
if info.VerifiedName != nil && len(info.VerifiedName.Details.GetVerifiedName()) > 0 {
|
||||
go cli.updateBusinessName(cli.BackgroundEventCtx, info.Sender, info, info.VerifiedName.Details.GetVerifiedName())
|
||||
go cli.updateBusinessName(ctx, info.Sender, info, info.VerifiedName.Details.GetVerifiedName())
|
||||
}
|
||||
if len(info.PushName) > 0 && info.PushName != "-" && (cli.MessengerConfig == nil || info.PushName != "username") {
|
||||
go cli.updatePushName(cli.BackgroundEventCtx, info.Sender, info, info.PushName)
|
||||
go cli.updatePushName(ctx, info.Sender, info, info.PushName)
|
||||
}
|
||||
if info.Sender.Server == types.NewsletterServer {
|
||||
var cancelled bool
|
||||
@@ -303,7 +302,7 @@ func (cli *Client) decryptMessages(ctx context.Context, info *types.MessageInfo,
|
||||
cli.Log.Warnf("Unavailable message %s from %s (type: %q)", info.ID, info.SourceString(), uType)
|
||||
cli.backgroundIfAsyncAck(func() {
|
||||
cli.immediateRequestMessageFromPhone(ctx, info)
|
||||
cli.sendAck(node, 0)
|
||||
cli.sendAck(ctx, node, 0)
|
||||
})
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{Info: *info, IsUnavailable: true, UnavailableType: uType})
|
||||
return
|
||||
@@ -391,15 +390,15 @@ func (cli *Client) decryptMessages(ctx context.Context, info *types.MessageInfo,
|
||||
isUnavailable := encType == "skmsg" && !containsDirectMsg && errors.Is(err, signalerror.ErrNoSenderKeyForUser)
|
||||
if encType == "msmsg" {
|
||||
cli.backgroundIfAsyncAck(func() {
|
||||
cli.sendAck(node, NackMissingMessageSecret)
|
||||
cli.sendAck(ctx, node, NackMissingMessageSecret)
|
||||
})
|
||||
} else if cli.SynchronousAck {
|
||||
cli.sendRetryReceipt(ctx, node, info, isUnavailable)
|
||||
// TODO this probably isn't supposed to ack
|
||||
cli.sendAck(node, 0)
|
||||
cli.sendAck(ctx, node, 0)
|
||||
} else {
|
||||
go cli.sendRetryReceipt(context.WithoutCancel(ctx), node, info, isUnavailable)
|
||||
go cli.sendAck(node, 0)
|
||||
go cli.sendAck(ctx, node, 0)
|
||||
}
|
||||
cli.dispatchEvent(&events.UndecryptableMessage{
|
||||
Info: *info,
|
||||
@@ -460,11 +459,11 @@ func (cli *Client) decryptMessages(ctx context.Context, info *types.MessageInfo,
|
||||
}
|
||||
cli.backgroundIfAsyncAck(func() {
|
||||
if !recognizedStanza {
|
||||
cli.sendAck(node, NackUnrecognizedStanza)
|
||||
cli.sendAck(ctx, node, NackUnrecognizedStanza)
|
||||
} else if protobufFailed {
|
||||
cli.sendAck(node, NackInvalidProtobuf)
|
||||
cli.sendAck(ctx, node, NackInvalidProtobuf)
|
||||
} else {
|
||||
cli.sendMessageReceipt(info, node)
|
||||
cli.sendMessageReceipt(ctx, info, node)
|
||||
}
|
||||
})
|
||||
return
|
||||
@@ -795,7 +794,7 @@ func (cli *Client) handleProtocolMessage(ctx context.Context, info *types.Messag
|
||||
go cli.handleHistorySyncNotificationLoop()
|
||||
}
|
||||
}
|
||||
go cli.sendProtocolMessageReceipt(info.ID, types.ReceiptTypeHistorySync)
|
||||
go cli.sendProtocolMessageReceipt(ctx, info.ID, types.ReceiptTypeHistorySync)
|
||||
}
|
||||
|
||||
if protoMsg.GetLidMigrationMappingSyncMessage() != nil {
|
||||
@@ -811,7 +810,7 @@ func (cli *Client) handleProtocolMessage(ctx context.Context, info *types.Messag
|
||||
}
|
||||
|
||||
if info.Category == "peer" {
|
||||
go cli.sendProtocolMessageReceipt(info.ID, types.ReceiptTypePeerMsg)
|
||||
go cli.sendProtocolMessageReceipt(ctx, info.ID, types.ReceiptTypePeerMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1024,12 +1023,12 @@ func (cli *Client) handleDecryptedMessage(ctx context.Context, info *types.Messa
|
||||
return cli.dispatchEvent(evt.UnwrapRaw())
|
||||
}
|
||||
|
||||
func (cli *Client) sendProtocolMessageReceipt(id types.MessageID, msgType types.ReceiptType) {
|
||||
func (cli *Client) sendProtocolMessageReceipt(ctx context.Context, id types.MessageID, msgType types.ReceiptType) {
|
||||
clientID := cli.Store.ID
|
||||
if len(id) == 0 || clientID == nil {
|
||||
return
|
||||
}
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
err := cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: waBinary.Attrs{
|
||||
"id": string(id),
|
||||
|
||||
+28
-32
@@ -26,8 +26,7 @@ import (
|
||||
|
||||
// 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,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "newsletter",
|
||||
Type: iqSet,
|
||||
To: jid,
|
||||
@@ -46,7 +45,7 @@ func (cli *Client) NewsletterSubscribeLiveUpdates(ctx context.Context, jid types
|
||||
// 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 {
|
||||
func (cli *Client) NewsletterMarkViewed(ctx context.Context, jid types.JID, serverIDs []types.MessageServerID) error {
|
||||
if cli == nil {
|
||||
return ErrClientIsNil
|
||||
}
|
||||
@@ -61,7 +60,7 @@ func (cli *Client) NewsletterMarkViewed(jid types.JID, serverIDs []types.Message
|
||||
}
|
||||
reqID := cli.generateRequestID()
|
||||
resp := cli.waitResponse(reqID)
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
err := cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: waBinary.Attrs{
|
||||
"to": jid,
|
||||
@@ -86,7 +85,7 @@ func (cli *Client) NewsletterMarkViewed(jid types.JID, serverIDs []types.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 {
|
||||
func (cli *Client) NewsletterSendReaction(ctx context.Context, jid types.JID, serverID types.MessageServerID, reaction string, messageID types.MessageID) error {
|
||||
if messageID == "" {
|
||||
messageID = cli.GenerateMessageID()
|
||||
}
|
||||
@@ -102,7 +101,7 @@ func (cli *Client) NewsletterSendReaction(jid types.JID, serverID types.MessageS
|
||||
} else {
|
||||
messageAttrs["edit"] = string(types.EditAttributeSenderRevoke)
|
||||
}
|
||||
return cli.sendNode(waBinary.Node{
|
||||
return cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: messageAttrs,
|
||||
Content: []waBinary.Node{{
|
||||
@@ -181,7 +180,7 @@ func (cli *Client) sendMexIQ(ctx context.Context, queryID string, variables any)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:mex",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
@@ -192,7 +191,6 @@ func (cli *Client) sendMexIQ(ctx context.Context, queryID string, variables any)
|
||||
},
|
||||
Content: payload,
|
||||
}},
|
||||
Context: ctx,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -248,8 +246,8 @@ 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{
|
||||
func (cli *Client) getNewsletterInfo(ctx context.Context, input map[string]any, fetchViewerMeta bool) (*types.NewsletterMetadata, error) {
|
||||
data, err := cli.sendMexIQ(ctx, queryFetchNewsletter, map[string]any{
|
||||
"fetch_creation_time": true,
|
||||
"fetch_full_image": true,
|
||||
"fetch_viewer_metadata": fetchViewerMeta,
|
||||
@@ -266,8 +264,8 @@ func (cli *Client) getNewsletterInfo(input map[string]any, fetchViewerMeta bool)
|
||||
}
|
||||
|
||||
// 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{
|
||||
func (cli *Client) GetNewsletterInfo(ctx context.Context, jid types.JID) (*types.NewsletterMetadata, error) {
|
||||
return cli.getNewsletterInfo(ctx, map[string]any{
|
||||
"key": jid.String(),
|
||||
"type": types.NewsletterKeyTypeJID,
|
||||
}, true)
|
||||
@@ -278,8 +276,8 @@ func (cli *Client) GetNewsletterInfo(jid types.JID) (*types.NewsletterMetadata,
|
||||
// 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{
|
||||
func (cli *Client) GetNewsletterInfoWithInvite(ctx context.Context, key string) (*types.NewsletterMetadata, error) {
|
||||
return cli.getNewsletterInfo(ctx, map[string]any{
|
||||
"key": strings.TrimPrefix(key, NewsletterLinkPrefix),
|
||||
"type": types.NewsletterKeyTypeInvite,
|
||||
}, false)
|
||||
@@ -290,8 +288,8 @@ type respGetSubscribedNewsletters struct {
|
||||
}
|
||||
|
||||
// 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{})
|
||||
func (cli *Client) GetSubscribedNewsletters(ctx context.Context) ([]*types.NewsletterMetadata, error) {
|
||||
data, err := cli.sendMexIQ(ctx, querySubscribedNewsletters, map[string]any{})
|
||||
var respData respGetSubscribedNewsletters
|
||||
if data != nil {
|
||||
jsonErr := json.Unmarshal(data, &respData)
|
||||
@@ -313,8 +311,8 @@ type respCreateNewsletter struct {
|
||||
}
|
||||
|
||||
// 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{
|
||||
func (cli *Client) CreateNewsletter(ctx context.Context, params CreateNewsletterParams) (*types.NewsletterMetadata, error) {
|
||||
resp, err := cli.sendMexIQ(ctx, mutationCreateNewsletter, map[string]any{
|
||||
"newsletter_input": ¶ms,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -333,8 +331,8 @@ func (cli *Client) CreateNewsletter(params CreateNewsletterParams) (*types.Newsl
|
||||
// To accept the terms for creating newsletters, use
|
||||
//
|
||||
// cli.AcceptTOSNotice("20601218", "5")
|
||||
func (cli *Client) AcceptTOSNotice(noticeID, stage string) error {
|
||||
_, err := cli.sendIQ(infoQuery{
|
||||
func (cli *Client) AcceptTOSNotice(ctx context.Context, noticeID, stage string) error {
|
||||
_, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "tos",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
@@ -350,28 +348,28 @@ func (cli *Client) AcceptTOSNotice(noticeID, stage string) error {
|
||||
}
|
||||
|
||||
// NewsletterToggleMute changes the mute status of a newsletter.
|
||||
func (cli *Client) NewsletterToggleMute(jid types.JID, mute bool) error {
|
||||
func (cli *Client) NewsletterToggleMute(ctx context.Context, jid types.JID, mute bool) error {
|
||||
query := mutationUnmuteNewsletter
|
||||
if mute {
|
||||
query = mutationMuteNewsletter
|
||||
}
|
||||
_, err := cli.sendMexIQ(context.TODO(), query, map[string]any{
|
||||
_, err := cli.sendMexIQ(ctx, 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{
|
||||
func (cli *Client) FollowNewsletter(ctx context.Context, jid types.JID) error {
|
||||
_, err := cli.sendMexIQ(ctx, 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{
|
||||
func (cli *Client) UnfollowNewsletter(ctx context.Context, jid types.JID) error {
|
||||
_, err := cli.sendMexIQ(ctx, mutationUnfollowNewsletter, map[string]any{
|
||||
"newsletter_id": jid.String(),
|
||||
})
|
||||
return err
|
||||
@@ -383,7 +381,7 @@ type GetNewsletterMessagesParams struct {
|
||||
}
|
||||
|
||||
// GetNewsletterMessages gets messages in a WhatsApp channel.
|
||||
func (cli *Client) GetNewsletterMessages(jid types.JID, params *GetNewsletterMessagesParams) ([]*types.NewsletterMessage, error) {
|
||||
func (cli *Client) GetNewsletterMessages(ctx context.Context, jid types.JID, params *GetNewsletterMessagesParams) ([]*types.NewsletterMessage, error) {
|
||||
attrs := waBinary.Attrs{
|
||||
"type": "jid",
|
||||
"jid": jid,
|
||||
@@ -396,7 +394,7 @@ func (cli *Client) GetNewsletterMessages(jid types.JID, params *GetNewsletterMes
|
||||
attrs["before"] = params.Before
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "newsletter",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
@@ -404,7 +402,6 @@ func (cli *Client) GetNewsletterMessages(jid types.JID, params *GetNewsletterMes
|
||||
Tag: "messages",
|
||||
Attrs: attrs,
|
||||
}},
|
||||
Context: context.TODO(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -425,7 +422,7 @@ type GetNewsletterUpdatesParams struct {
|
||||
// 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) {
|
||||
func (cli *Client) GetNewsletterMessageUpdates(ctx context.Context, jid types.JID, params *GetNewsletterUpdatesParams) ([]*types.NewsletterMessage, error) {
|
||||
attrs := waBinary.Attrs{}
|
||||
if params != nil {
|
||||
if params.Count != 0 {
|
||||
@@ -438,7 +435,7 @@ func (cli *Client) GetNewsletterMessageUpdates(jid types.JID, params *GetNewslet
|
||||
attrs["after"] = params.After
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "newsletter",
|
||||
Type: iqGet,
|
||||
To: jid,
|
||||
@@ -446,7 +443,6 @@ func (cli *Client) GetNewsletterMessageUpdates(jid types.JID, params *GetNewslet
|
||||
Tag: "message_updates",
|
||||
Attrs: attrs,
|
||||
}},
|
||||
Context: context.TODO(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+1
-2
@@ -409,8 +409,7 @@ func (cli *Client) handleStatusNotification(ctx context.Context, node *waBinary.
|
||||
})
|
||||
}
|
||||
|
||||
func (cli *Client) handleNotification(node *waBinary.Node) {
|
||||
ctx := cli.BackgroundEventCtx
|
||||
func (cli *Client) handleNotification(ctx context.Context, node *waBinary.Node) {
|
||||
ag := node.AttrGetter()
|
||||
notifType := ag.String("type")
|
||||
if !ag.OK() {
|
||||
|
||||
+2
-4
@@ -99,11 +99,10 @@ func (cli *Client) PairPhone(ctx context.Context, phone string, showPushNotifica
|
||||
return "", ErrPhoneNumberIsNotInternational
|
||||
}
|
||||
jid := types.NewJID(phone, types.DefaultUserServer)
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "md",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Context: ctx,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "link_code_companion_reg",
|
||||
Attrs: waBinary.Attrs{
|
||||
@@ -223,11 +222,10 @@ func (cli *Client) handleCodePairNotification(ctx context.Context, parentNode *w
|
||||
advSecret := hkdfutil.SHA256(advSecretInput, nil, []byte("adv_secret"), 32)
|
||||
cli.Store.AdvSecretKey = advSecret
|
||||
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
_, err = cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "md",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
Context: ctx,
|
||||
Content: []waBinary.Node{{
|
||||
Tag: "link_code_companion_reg",
|
||||
Attrs: waBinary.Attrs{
|
||||
|
||||
@@ -33,22 +33,22 @@ var (
|
||||
AdvHostedDeviceSignaturePrefix = []byte{6, 6}
|
||||
)
|
||||
|
||||
func (cli *Client) handleIQ(node *waBinary.Node) {
|
||||
func (cli *Client) handleIQ(ctx context.Context, node *waBinary.Node) {
|
||||
children := node.GetChildren()
|
||||
if len(children) != 1 || node.Attrs["from"] != types.ServerJID {
|
||||
return
|
||||
}
|
||||
switch children[0].Tag {
|
||||
case "pair-device":
|
||||
cli.handlePairDevice(node)
|
||||
cli.handlePairDevice(ctx, node)
|
||||
case "pair-success":
|
||||
cli.handlePairSuccess(node)
|
||||
cli.handlePairSuccess(ctx, node)
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handlePairDevice(node *waBinary.Node) {
|
||||
func (cli *Client) handlePairDevice(ctx context.Context, node *waBinary.Node) {
|
||||
pairDevice := node.GetChildByTag("pair-device")
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
err := cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "iq",
|
||||
Attrs: waBinary.Attrs{
|
||||
"to": node.Attrs["from"],
|
||||
@@ -84,7 +84,7 @@ func (cli *Client) makeQRData(ref string) string {
|
||||
return strings.Join([]string{ref, noise, identity, adv}, ",")
|
||||
}
|
||||
|
||||
func (cli *Client) handlePairSuccess(node *waBinary.Node) {
|
||||
func (cli *Client) handlePairSuccess(ctx context.Context, node *waBinary.Node) {
|
||||
id := node.Attrs["id"].(string)
|
||||
pairSuccess := node.GetChildByTag("pair-success")
|
||||
|
||||
@@ -95,7 +95,7 @@ func (cli *Client) handlePairSuccess(node *waBinary.Node) {
|
||||
platform, _ := pairSuccess.GetChildByTag("platform").Attrs["name"].(string)
|
||||
|
||||
go func() {
|
||||
err := cli.handlePair(context.TODO(), deviceIdentityBytes, id, businessName, platform, jid, lid)
|
||||
err := cli.handlePair(ctx, deviceIdentityBytes, id, businessName, platform, jid, lid)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to pair device: %v", err)
|
||||
cli.Disconnect()
|
||||
@@ -111,7 +111,7 @@ func (cli *Client) handlePair(ctx context.Context, deviceIdentityBytes []byte, r
|
||||
var deviceIdentityContainer waAdv.ADVSignedDeviceIdentityHMAC
|
||||
err := proto.Unmarshal(deviceIdentityBytes, &deviceIdentityContainer)
|
||||
if err != nil {
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return &PairProtoError{"failed to parse device identity container in pair success message", err}
|
||||
}
|
||||
|
||||
@@ -124,33 +124,33 @@ func (cli *Client) handlePair(ctx context.Context, deviceIdentityBytes []byte, r
|
||||
|
||||
if !bytes.Equal(h.Sum(nil), deviceIdentityContainer.HMAC) {
|
||||
cli.Log.Warnf("Invalid HMAC from pair success message")
|
||||
cli.sendPairError(reqID, 401, "hmac-mismatch")
|
||||
cli.sendPairError(ctx, reqID, 401, "hmac-mismatch")
|
||||
return ErrPairInvalidDeviceIdentityHMAC
|
||||
}
|
||||
|
||||
var deviceIdentity waAdv.ADVSignedDeviceIdentity
|
||||
err = proto.Unmarshal(deviceIdentityContainer.Details, &deviceIdentity)
|
||||
if err != nil {
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return &PairProtoError{"failed to parse signed device identity in pair success message", err}
|
||||
}
|
||||
|
||||
var deviceIdentityDetails waAdv.ADVDeviceIdentity
|
||||
err = proto.Unmarshal(deviceIdentity.Details, &deviceIdentityDetails)
|
||||
if err != nil {
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return &PairProtoError{"failed to parse device identity details in pair success message", err}
|
||||
}
|
||||
|
||||
if !verifyAccountSignature(&deviceIdentity, cli.Store.IdentityKey, deviceIdentityDetails.GetDeviceType() == waAdv.ADVEncryptionType_HOSTED) {
|
||||
cli.sendPairError(reqID, 401, "signature-mismatch")
|
||||
cli.sendPairError(ctx, reqID, 401, "signature-mismatch")
|
||||
return ErrPairInvalidDeviceSignature
|
||||
}
|
||||
|
||||
deviceIdentity.DeviceSignature = generateDeviceSignature(&deviceIdentity, cli.Store.IdentityKey)[:]
|
||||
|
||||
if cli.PrePairCallback != nil && !cli.PrePairCallback(jid, platform, businessName) {
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return ErrPairRejectedLocally
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ func (cli *Client) handlePair(ctx context.Context, deviceIdentityBytes []byte, r
|
||||
|
||||
selfSignedDeviceIdentity, err := proto.Marshal(&deviceIdentity)
|
||||
if err != nil {
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return &PairProtoError{"failed to marshal self-signed device identity", err}
|
||||
}
|
||||
|
||||
@@ -173,21 +173,21 @@ func (cli *Client) handlePair(ctx context.Context, deviceIdentityBytes []byte, r
|
||||
cli.Store.Platform = platform
|
||||
err = cli.Store.Save(ctx)
|
||||
if err != nil {
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return &PairDatabaseError{"failed to save device store", err}
|
||||
}
|
||||
cli.StoreLIDPNMapping(ctx, lid, jid)
|
||||
err = cli.Store.Identities.PutIdentity(ctx, mainDeviceLID.SignalAddress().String(), mainDeviceIdentity)
|
||||
if err != nil {
|
||||
_ = cli.Store.Delete(ctx)
|
||||
cli.sendPairError(reqID, 500, "internal-error")
|
||||
cli.sendPairError(ctx, reqID, 500, "internal-error")
|
||||
return &PairDatabaseError{"failed to store main device identity", err}
|
||||
}
|
||||
|
||||
// Expect a disconnect after this and don't dispatch the usual Disconnected event
|
||||
cli.expectDisconnect()
|
||||
|
||||
err = cli.sendNode(waBinary.Node{
|
||||
err = cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "iq",
|
||||
Attrs: waBinary.Attrs{
|
||||
"to": types.ServerJID,
|
||||
@@ -249,8 +249,8 @@ func generateDeviceSignature(deviceIdentity *waAdv.ADVSignedDeviceIdentity, ikp
|
||||
return &sig
|
||||
}
|
||||
|
||||
func (cli *Client) sendPairError(id string, code int, text string) {
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
func (cli *Client) sendPairError(ctx context.Context, id string, code int, text string) {
|
||||
err := cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "iq",
|
||||
Attrs: waBinary.Attrs{
|
||||
"to": types.ServerJID,
|
||||
|
||||
+3
-6
@@ -30,11 +30,10 @@ const (
|
||||
)
|
||||
|
||||
func (cli *Client) getServerPreKeyCount(ctx context.Context) (int, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "encrypt",
|
||||
Type: "get",
|
||||
To: types.ServerJID,
|
||||
Context: ctx,
|
||||
Content: []waBinary.Node{
|
||||
{Tag: "count"},
|
||||
},
|
||||
@@ -66,8 +65,7 @@ func (cli *Client) uploadPreKeys(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
cli.Log.Infof("Uploading %d new prekeys to server", len(preKeys))
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
_, err = cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "encrypt",
|
||||
Type: "set",
|
||||
To: types.ServerJID,
|
||||
@@ -128,8 +126,7 @@ func (cli *Client) fetchPreKeys(ctx context.Context, users []types.JID) (map[typ
|
||||
"reason": "identity",
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "encrypt",
|
||||
Type: "get",
|
||||
To: types.ServerJID,
|
||||
|
||||
+9
-9
@@ -15,7 +15,7 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
|
||||
func (cli *Client) handleChatState(node *waBinary.Node) {
|
||||
func (cli *Client) handleChatState(ctx context.Context, node *waBinary.Node) {
|
||||
source, err := cli.parseMessageSource(node, true)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to parse chat state update: %v", err)
|
||||
@@ -36,7 +36,7 @@ func (cli *Client) handleChatState(node *waBinary.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) handlePresence(node *waBinary.Node) {
|
||||
func (cli *Client) handlePresence(ctx context.Context, node *waBinary.Node) {
|
||||
var evt events.Presence
|
||||
ag := node.AttrGetter()
|
||||
evt.From = ag.JID("from")
|
||||
@@ -61,7 +61,7 @@ func (cli *Client) handlePresence(node *waBinary.Node) {
|
||||
//
|
||||
// You should call this at least once after connecting so that the server has your pushname.
|
||||
// Otherwise, other users will see "-" as the name.
|
||||
func (cli *Client) SendPresence(state types.Presence) error {
|
||||
func (cli *Client) SendPresence(ctx context.Context, state types.Presence) error {
|
||||
if cli == nil {
|
||||
return ErrClientIsNil
|
||||
} else if len(cli.Store.PushName) == 0 && cli.MessengerConfig == nil {
|
||||
@@ -79,7 +79,7 @@ func (cli *Client) SendPresence(state types.Presence) error {
|
||||
if cli.MessengerConfig == nil {
|
||||
attrs["name"] = cli.Store.PushName
|
||||
}
|
||||
return cli.sendNode(waBinary.Node{
|
||||
return cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "presence",
|
||||
Attrs: attrs,
|
||||
})
|
||||
@@ -93,11 +93,11 @@ func (cli *Client) SendPresence(state types.Presence) error {
|
||||
// so you should mark yourself as online before trying to use this function:
|
||||
//
|
||||
// cli.SendPresence(types.PresenceAvailable)
|
||||
func (cli *Client) SubscribePresence(jid types.JID) error {
|
||||
func (cli *Client) SubscribePresence(ctx context.Context, jid types.JID) error {
|
||||
if cli == nil {
|
||||
return ErrClientIsNil
|
||||
}
|
||||
privacyToken, err := cli.Store.PrivacyTokens.GetPrivacyToken(context.TODO(), jid)
|
||||
privacyToken, err := cli.Store.PrivacyTokens.GetPrivacyToken(ctx, jid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get privacy token: %w", err)
|
||||
} else if privacyToken == nil {
|
||||
@@ -120,13 +120,13 @@ func (cli *Client) SubscribePresence(jid types.JID) error {
|
||||
Content: privacyToken.Token,
|
||||
}}
|
||||
}
|
||||
return cli.sendNode(req)
|
||||
return cli.sendNode(ctx, req)
|
||||
}
|
||||
|
||||
// SendChatPresence updates the user's typing status in a specific chat.
|
||||
//
|
||||
// The media parameter can be set to indicate the user is recording media (like a voice message) rather than typing a text message.
|
||||
func (cli *Client) SendChatPresence(jid types.JID, state types.ChatPresence, media types.ChatPresenceMedia) error {
|
||||
func (cli *Client) SendChatPresence(ctx context.Context, jid types.JID, state types.ChatPresence, media types.ChatPresenceMedia) error {
|
||||
ownID := cli.getOwnID()
|
||||
if ownID.IsEmpty() {
|
||||
return ErrNotLoggedIn
|
||||
@@ -137,7 +137,7 @@ func (cli *Client) SendChatPresence(jid types.JID, state types.ChatPresence, med
|
||||
"media": string(media),
|
||||
}
|
||||
}
|
||||
return cli.sendNode(waBinary.Node{
|
||||
return cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "chatstate",
|
||||
Attrs: waBinary.Attrs{
|
||||
"from": ownID,
|
||||
|
||||
+4
-5
@@ -23,9 +23,8 @@ func (cli *Client) TryFetchPrivacySettings(ctx context.Context, ignoreCache bool
|
||||
} else if val := cli.privacySettingsCache.Load(); val != nil && !ignoreCache {
|
||||
return val.(*types.PrivacySettings), nil
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "privacy",
|
||||
Context: ctx,
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{Tag: "privacy"}},
|
||||
@@ -66,7 +65,7 @@ func (cli *Client) SetPrivacySetting(ctx context.Context, name types.PrivacySett
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
_, err = cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "privacy",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
@@ -106,8 +105,8 @@ func (cli *Client) SetPrivacySetting(ctx context.Context, name types.PrivacySett
|
||||
}
|
||||
|
||||
// SetDefaultDisappearingTimer will set the default disappearing message timer.
|
||||
func (cli *Client) SetDefaultDisappearingTimer(timer time.Duration) (err error) {
|
||||
_, err = cli.sendIQ(infoQuery{
|
||||
func (cli *Client) SetDefaultDisappearingTimer(ctx context.Context, timer time.Duration) (err error) {
|
||||
_, err = cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "disappearing_mode",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
|
||||
@@ -82,12 +82,11 @@ func (wpc *WebPushConfig) GetPushConfigAttrs() waBinary.Attrs {
|
||||
}
|
||||
|
||||
func (cli *Client) GetServerPushNotificationConfig(ctx context.Context) (*waBinary.Node, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "urn:xmpp:whatsapp:push",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Content: []waBinary.Node{{Tag: "settings"}},
|
||||
Context: ctx,
|
||||
})
|
||||
return resp, err
|
||||
}
|
||||
@@ -96,7 +95,7 @@ func (cli *Client) GetServerPushNotificationConfig(ctx context.Context) (*waBina
|
||||
//
|
||||
// This is generally not necessary for anything. Don't use this if you don't know what you're doing.
|
||||
func (cli *Client) RegisterForPushNotifications(ctx context.Context, pc PushConfig) error {
|
||||
_, err := cli.sendIQ(infoQuery{
|
||||
_, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "urn:xmpp:whatsapp:push",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
@@ -104,7 +103,6 @@ func (cli *Client) RegisterForPushNotifications(ctx context.Context, pc PushConf
|
||||
Tag: "config",
|
||||
Attrs: pc.GetPushConfigAttrs(),
|
||||
}},
|
||||
Context: ctx,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
+12
-12
@@ -19,16 +19,16 @@ import (
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
)
|
||||
|
||||
func (cli *Client) handleReceipt(node *waBinary.Node) {
|
||||
func (cli *Client) handleReceipt(ctx context.Context, node *waBinary.Node) {
|
||||
var cancelled bool
|
||||
defer cli.maybeDeferredAck(cli.BackgroundEventCtx, node)(&cancelled)
|
||||
defer cli.maybeDeferredAck(ctx, node)(&cancelled)
|
||||
receipt, err := cli.parseReceipt(node)
|
||||
if err != nil {
|
||||
cli.Log.Warnf("Failed to parse receipt: %v", err)
|
||||
} else if receipt != nil {
|
||||
if receipt.Type == types.ReceiptTypeRetry {
|
||||
go func() {
|
||||
err := cli.handleRetryReceipt(cli.BackgroundEventCtx, receipt, node)
|
||||
err := cli.handleRetryReceipt(ctx, receipt, node)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to handle retry receipt for %s/%s from %s: %v", receipt.Chat, receipt.MessageIDs[0], receipt.Sender, err)
|
||||
}
|
||||
@@ -121,10 +121,10 @@ func (cli *Client) maybeDeferredAck(ctx context.Context, node *waBinary.Node) fu
|
||||
Msg("Not sending ack for node")
|
||||
return
|
||||
}
|
||||
cli.sendAck(node, 0)
|
||||
cli.sendAck(ctx, node, 0)
|
||||
}
|
||||
} else {
|
||||
go cli.sendAck(node, 0)
|
||||
go cli.sendAck(ctx, node, 0)
|
||||
return func(...*bool) {}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@ const (
|
||||
NackDBOperationFailed = 552
|
||||
)
|
||||
|
||||
func (cli *Client) sendAck(node *waBinary.Node, error int) {
|
||||
func (cli *Client) sendAck(ctx context.Context, node *waBinary.Node, error int) {
|
||||
attrs := waBinary.Attrs{
|
||||
"class": node.Tag,
|
||||
"id": node.Attrs["id"],
|
||||
@@ -172,7 +172,7 @@ func (cli *Client) sendAck(node *waBinary.Node, error int) {
|
||||
if error != 0 {
|
||||
attrs["error"] = error
|
||||
}
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
err := cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "ack",
|
||||
Attrs: attrs,
|
||||
})
|
||||
@@ -191,7 +191,7 @@ func (cli *Client) sendAck(node *waBinary.Node, error int) {
|
||||
//
|
||||
// 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 {
|
||||
func (cli *Client) MarkRead(ctx context.Context, 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")
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, se
|
||||
"t": timestamp.Unix(),
|
||||
},
|
||||
}
|
||||
if chat.Server == types.NewsletterServer || cli.GetPrivacySettings(context.TODO()).ReadReceipts == types.PrivacySettingNone {
|
||||
if chat.Server == types.NewsletterServer || cli.GetPrivacySettings(ctx).ReadReceipts == types.PrivacySettingNone {
|
||||
switch receiptType {
|
||||
case types.ReceiptTypeRead:
|
||||
node.Attrs["type"] = string(types.ReceiptTypeReadSelf)
|
||||
@@ -231,7 +231,7 @@ func (cli *Client) MarkRead(ids []types.MessageID, timestamp time.Time, chat, se
|
||||
Content: children,
|
||||
}}
|
||||
}
|
||||
return cli.sendNode(node)
|
||||
return cli.sendNode(ctx, node)
|
||||
}
|
||||
|
||||
// SetForceActiveDeliveryReceipts will force the client to send normal delivery
|
||||
@@ -274,7 +274,7 @@ func buildBaseReceipt(id string, node *waBinary.Node) waBinary.Attrs {
|
||||
return attrs
|
||||
}
|
||||
|
||||
func (cli *Client) sendMessageReceipt(info *types.MessageInfo, node *waBinary.Node) {
|
||||
func (cli *Client) sendMessageReceipt(ctx context.Context, info *types.MessageInfo, node *waBinary.Node) {
|
||||
attrs := buildBaseReceipt(info.ID, node)
|
||||
if info.IsFromMe {
|
||||
attrs["type"] = string(types.ReceiptTypeSender)
|
||||
@@ -284,7 +284,7 @@ func (cli *Client) sendMessageReceipt(info *types.MessageInfo, node *waBinary.No
|
||||
} else if cli.sendActiveReceipts.Load() == 0 {
|
||||
attrs["type"] = string(types.ReceiptTypeInactive)
|
||||
}
|
||||
err := cli.sendNode(waBinary.Node{
|
||||
err := cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "receipt",
|
||||
Attrs: attrs,
|
||||
})
|
||||
|
||||
+25
-19
@@ -68,7 +68,7 @@ func (cli *Client) cancelResponse(reqID string, ch chan *waBinary.Node) {
|
||||
cli.responseWaitersLock.Unlock()
|
||||
}
|
||||
|
||||
func (cli *Client) receiveResponse(data *waBinary.Node) bool {
|
||||
func (cli *Client) receiveResponse(ctx context.Context, data *waBinary.Node) bool {
|
||||
id, ok := data.Attrs["id"].(string)
|
||||
if !ok || (data.Tag != "iq" && data.Tag != "ack") {
|
||||
return false
|
||||
@@ -81,7 +81,10 @@ func (cli *Client) receiveResponse(data *waBinary.Node) bool {
|
||||
}
|
||||
delete(cli.responseWaiters, id)
|
||||
cli.responseWaitersLock.Unlock()
|
||||
waiter <- data
|
||||
select {
|
||||
case waiter <- data:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -103,10 +106,9 @@ type infoQuery struct {
|
||||
|
||||
Timeout time.Duration
|
||||
NoRetry bool
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
func (cli *Client) sendIQAsyncAndGetData(query *infoQuery) (<-chan *waBinary.Node, []byte, error) {
|
||||
func (cli *Client) sendIQAsyncAndGetData(ctx context.Context, query *infoQuery) (<-chan *waBinary.Node, []byte, error) {
|
||||
if cli == nil {
|
||||
return nil, nil, ErrClientIsNil
|
||||
}
|
||||
@@ -128,7 +130,7 @@ func (cli *Client) sendIQAsyncAndGetData(query *infoQuery) (<-chan *waBinary.Nod
|
||||
if !query.Target.IsEmpty() {
|
||||
attrs["target"] = query.Target
|
||||
}
|
||||
data, err := cli.sendNodeAndGetData(waBinary.Node{
|
||||
data, err := cli.sendNodeAndGetData(ctx, waBinary.Node{
|
||||
Tag: "iq",
|
||||
Attrs: attrs,
|
||||
Content: query.Content,
|
||||
@@ -140,23 +142,20 @@ func (cli *Client) sendIQAsyncAndGetData(query *infoQuery) (<-chan *waBinary.Nod
|
||||
return waiter, data, nil
|
||||
}
|
||||
|
||||
func (cli *Client) sendIQAsync(query infoQuery) (<-chan *waBinary.Node, error) {
|
||||
ch, _, err := cli.sendIQAsyncAndGetData(&query)
|
||||
func (cli *Client) sendIQAsync(ctx context.Context, query infoQuery) (<-chan *waBinary.Node, error) {
|
||||
ch, _, err := cli.sendIQAsyncAndGetData(ctx, &query)
|
||||
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
|
||||
}
|
||||
func (cli *Client) sendIQ(ctx context.Context, query infoQuery) (*waBinary.Node, error) {
|
||||
if query.Timeout == 0 {
|
||||
query.Timeout = defaultRequestTimeout
|
||||
}
|
||||
if query.Context == nil {
|
||||
query.Context = context.Background()
|
||||
resChan, data, err := cli.sendIQAsyncAndGetData(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
select {
|
||||
case res := <-resChan:
|
||||
@@ -164,7 +163,7 @@ func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
|
||||
if query.NoRetry {
|
||||
return nil, &DisconnectedError{Action: "info query", Node: res}
|
||||
}
|
||||
res, err = cli.retryFrame("info query", query.ID, data, res, query.Context, query.Timeout)
|
||||
res, err = cli.retryFrame(ctx, "info query", query.ID, data, res, query.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,14 +175,21 @@ func (cli *Client) sendIQ(query infoQuery) (*waBinary.Node, error) {
|
||||
return res, parseIQError(res)
|
||||
}
|
||||
return res, nil
|
||||
case <-query.Context.Done():
|
||||
return nil, query.Context.Err()
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-time.After(query.Timeout):
|
||||
return nil, ErrIQTimedOut
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) retryFrame(reqType, id string, data []byte, origResp *waBinary.Node, ctx context.Context, timeout time.Duration) (*waBinary.Node, error) {
|
||||
func (cli *Client) retryFrame(
|
||||
ctx context.Context,
|
||||
reqType,
|
||||
id string,
|
||||
data []byte,
|
||||
origResp *waBinary.Node,
|
||||
timeout time.Duration,
|
||||
) (*waBinary.Node, error) {
|
||||
if isAuthErrorDisconnect(origResp) {
|
||||
cli.Log.Debugf("%s (%s) was interrupted by websocket disconnection (%s), not retrying as it looks like an auth error", id, reqType, origResp.XMLString())
|
||||
return nil, &DisconnectedError{Action: reqType, Node: origResp}
|
||||
@@ -203,7 +209,7 @@ func (cli *Client) retryFrame(reqType, id string, data []byte, origResp *waBinar
|
||||
}
|
||||
|
||||
respChan := cli.waitResponse(id)
|
||||
err := sock.SendFrame(data)
|
||||
err := sock.SendFrame(ctx, data)
|
||||
if err != nil {
|
||||
cli.cancelResponse(id, respChan)
|
||||
return nil, err
|
||||
|
||||
@@ -295,7 +295,7 @@ func (cli *Client) handleRetryReceipt(ctx context.Context, receipt *events.Recei
|
||||
{Tag: "franking", Content: []waBinary.Node{{Tag: "franking_tag", Content: frankingTag}}},
|
||||
}
|
||||
}
|
||||
err = cli.sendNode(waBinary.Node{
|
||||
err = cli.sendNode(ctx, waBinary.Node{
|
||||
Tag: "message",
|
||||
Attrs: attrs,
|
||||
Content: content,
|
||||
@@ -444,7 +444,7 @@ func (cli *Client) sendRetryReceipt(ctx context.Context, node *waBinary.Node, in
|
||||
})
|
||||
}
|
||||
}
|
||||
err := cli.sendNode(payload)
|
||||
err := cli.sendNode(ctx, payload)
|
||||
if err != nil {
|
||||
cli.Log.Errorf("Failed to send retry receipt for %s: %v", id, err)
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waE2E
|
||||
return
|
||||
} else if toLID.IsEmpty() {
|
||||
var info map[types.JID]types.UserInfo
|
||||
info, err = cli.GetUserInfo([]types.JID{to})
|
||||
info, err = cli.GetUserInfo(ctx, []types.JID{to})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to get user info for %s to fill LID cache: %w", to, err)
|
||||
return
|
||||
@@ -399,7 +399,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waE2E
|
||||
data, err = cli.sendDM(ctx, ownID, to, req.ID, message, &resp.DebugTimings, extraParams)
|
||||
}
|
||||
case types.NewsletterServer:
|
||||
data, err = cli.sendNewsletter(to, req.ID, message, req.MediaHandle, &resp.DebugTimings)
|
||||
data, err = cli.sendNewsletter(ctx, to, req.ID, message, req.MediaHandle, &resp.DebugTimings)
|
||||
default:
|
||||
err = fmt.Errorf("%w %s", ErrUnknownServer, to.Server)
|
||||
}
|
||||
@@ -429,7 +429,7 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waE2E
|
||||
resp.DebugTimings.Resp = time.Since(start)
|
||||
if isDisconnectNode(respNode) {
|
||||
start = time.Now()
|
||||
respNode, err = cli.retryFrame("message send", req.ID, data, respNode, ctx, 0)
|
||||
respNode, err = cli.retryFrame(ctx, "message send", req.ID, data, respNode, 0)
|
||||
resp.DebugTimings.Retry = time.Since(start)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -458,8 +458,8 @@ func (cli *Client) SendMessage(ctx context.Context, to types.JID, message *waE2E
|
||||
// The return value is the timestamp of the message from the server.
|
||||
//
|
||||
// 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))
|
||||
func (cli *Client) RevokeMessage(ctx context.Context, chat types.JID, id types.MessageID) (SendResponse, error) {
|
||||
return cli.SendMessage(ctx, chat, cli.BuildRevoke(chat, types.EmptyJID, id))
|
||||
}
|
||||
|
||||
// BuildMessageKey builds a MessageKey object, which is used to refer to previous messages
|
||||
@@ -618,13 +618,13 @@ func ParseDisappearingTimerString(val string) (time.Duration, bool) {
|
||||
// and in groups the server will just reject the change. You can use the DisappearingTimer<Duration> constants for convenience.
|
||||
//
|
||||
// In groups, the server will echo the change as a notification, so it'll show up as a *events.GroupInfo update.
|
||||
func (cli *Client) SetDisappearingTimer(chat types.JID, timer time.Duration, settingTS time.Time) (err error) {
|
||||
func (cli *Client) SetDisappearingTimer(ctx context.Context, chat types.JID, timer time.Duration, settingTS time.Time) (err error) {
|
||||
switch chat.Server {
|
||||
case types.DefaultUserServer, types.HiddenUserServer:
|
||||
if settingTS.IsZero() {
|
||||
settingTS = time.Now()
|
||||
}
|
||||
_, err = cli.SendMessage(context.TODO(), chat, &waE2E.Message{
|
||||
_, err = cli.SendMessage(ctx, chat, &waE2E.Message{
|
||||
ProtocolMessage: &waE2E.ProtocolMessage{
|
||||
Type: waE2E.ProtocolMessage_EPHEMERAL_SETTING.Enum(),
|
||||
EphemeralExpiration: proto.Uint32(uint32(timer.Seconds())),
|
||||
@@ -633,9 +633,9 @@ func (cli *Client) SetDisappearingTimer(chat types.JID, timer time.Duration, set
|
||||
})
|
||||
case types.GroupServer:
|
||||
if timer == 0 {
|
||||
_, err = cli.sendGroupIQ(context.TODO(), iqSet, chat, waBinary.Node{Tag: "not_ephemeral"})
|
||||
_, err = cli.sendGroupIQ(ctx, iqSet, chat, waBinary.Node{Tag: "not_ephemeral"})
|
||||
} else {
|
||||
_, err = cli.sendGroupIQ(context.TODO(), iqSet, chat, waBinary.Node{
|
||||
_, err = cli.sendGroupIQ(ctx, iqSet, chat, waBinary.Node{
|
||||
Tag: "ephemeral",
|
||||
Attrs: waBinary.Attrs{
|
||||
"expiration": strconv.Itoa(int(timer.Seconds())),
|
||||
@@ -663,6 +663,7 @@ func participantListHashV2(participants []types.JID) string {
|
||||
}
|
||||
|
||||
func (cli *Client) sendNewsletter(
|
||||
ctx context.Context,
|
||||
to types.JID,
|
||||
id types.MessageID,
|
||||
message *waE2E.Message,
|
||||
@@ -706,7 +707,7 @@ func (cli *Client) sendNewsletter(
|
||||
Content: []waBinary.Node{plaintextNode},
|
||||
}
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(node)
|
||||
data, err := cli.sendNodeAndGetData(ctx, node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
@@ -787,7 +788,7 @@ func (cli *Client) sendGroup(
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
data, err := cli.sendNodeAndGetData(ctx, *node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
@@ -807,7 +808,7 @@ func (cli *Client) sendPeerMessage(
|
||||
return nil, err
|
||||
}
|
||||
start := time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
data, err := cli.sendNodeAndGetData(ctx, *node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
@@ -853,7 +854,7 @@ func (cli *Client) sendDM(
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
data, err := cli.sendNodeAndGetData(ctx, *node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
|
||||
@@ -182,7 +182,7 @@ func (cli *Client) SendFBMessage(
|
||||
resp.DebugTimings.Resp = time.Since(start)
|
||||
if isDisconnectNode(respNode) {
|
||||
start = time.Now()
|
||||
respNode, err = cli.retryFrame("message send", req.ID, data, respNode, ctx, 0)
|
||||
respNode, err = cli.retryFrame(ctx, "message send", req.ID, data, respNode, 0)
|
||||
resp.DebugTimings.Retry = time.Since(start)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -293,7 +293,7 @@ func (cli *Client) sendGroupV3(
|
||||
node.Content = append(node.GetChildren(), skMsg)
|
||||
|
||||
start = time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
data, err := cli.sendNodeAndGetData(ctx, *node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to send message node: %w", err)
|
||||
@@ -324,7 +324,7 @@ func (cli *Client) sendDMV3(
|
||||
return nil, "", err
|
||||
}
|
||||
start := time.Now()
|
||||
data, err := cli.sendNodeAndGetData(*node)
|
||||
data, err := cli.sendNodeAndGetData(ctx, *node)
|
||||
timings.Send = time.Since(start)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to send message node: %w", err)
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ const (
|
||||
var WAConnHeader = []byte{'W', 'A', WAMagicValue, token.DictVersion}
|
||||
|
||||
const (
|
||||
FrameMaxSize = 2 << 23
|
||||
FrameMaxSize = 1 << 24
|
||||
FrameLengthSize = 3
|
||||
)
|
||||
|
||||
|
||||
+46
-59
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Tulir Asokan
|
||||
// Copyright (c) 2025 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
|
||||
@@ -12,29 +12,30 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/coder/websocket"
|
||||
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
)
|
||||
|
||||
type FrameSocket struct {
|
||||
conn *websocket.Conn
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
log waLog.Logger
|
||||
lock sync.Mutex
|
||||
parentCtx context.Context
|
||||
cancelCtx context.Context
|
||||
cancel context.CancelFunc
|
||||
conn *websocket.Conn
|
||||
log waLog.Logger
|
||||
lock sync.Mutex
|
||||
|
||||
URL string
|
||||
HTTPHeaders http.Header
|
||||
HTTPClient *http.Client
|
||||
|
||||
Frames chan []byte
|
||||
OnDisconnect func(remote bool)
|
||||
WriteTimeout time.Duration
|
||||
OnDisconnect func(ctx context.Context, remote bool)
|
||||
|
||||
Header []byte
|
||||
Dialer websocket.Dialer
|
||||
|
||||
closed bool
|
||||
|
||||
incomingLength int
|
||||
receivedLength int
|
||||
@@ -42,17 +43,15 @@ type FrameSocket struct {
|
||||
partialHeader []byte
|
||||
}
|
||||
|
||||
func NewFrameSocket(log waLog.Logger, dialer websocket.Dialer) *FrameSocket {
|
||||
func NewFrameSocket(log waLog.Logger, client *http.Client) *FrameSocket {
|
||||
return &FrameSocket{
|
||||
conn: nil,
|
||||
log: log,
|
||||
Header: WAConnHeader,
|
||||
Frames: make(chan []byte),
|
||||
|
||||
URL: URL,
|
||||
HTTPHeaders: http.Header{"Origin": {Origin}},
|
||||
|
||||
Dialer: dialer,
|
||||
HTTPClient: client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,11 +59,7 @@ func (fs *FrameSocket) IsConnected() bool {
|
||||
return fs.conn != nil
|
||||
}
|
||||
|
||||
func (fs *FrameSocket) Context() context.Context {
|
||||
return fs.ctx
|
||||
}
|
||||
|
||||
func (fs *FrameSocket) Close(code int) {
|
||||
func (fs *FrameSocket) Close(code websocket.StatusCode) {
|
||||
fs.lock.Lock()
|
||||
defer fs.lock.Unlock()
|
||||
|
||||
@@ -72,58 +67,56 @@ func (fs *FrameSocket) Close(code int) {
|
||||
return
|
||||
}
|
||||
|
||||
fs.closed = true
|
||||
if code > 0 {
|
||||
message := websocket.FormatCloseMessage(code, "")
|
||||
err := fs.conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
|
||||
err := fs.conn.Close(code, "")
|
||||
if err != nil {
|
||||
fs.log.Warnf("Error sending close message: %v", err)
|
||||
fs.log.Warnf("Error sending close to websocket: %v", err)
|
||||
}
|
||||
} else {
|
||||
err := fs.conn.CloseNow()
|
||||
if err != nil {
|
||||
fs.log.Debugf("Error force closing websocket: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fs.cancel()
|
||||
err := fs.conn.Close()
|
||||
if err != nil {
|
||||
fs.log.Errorf("Error closing websocket: %v", err)
|
||||
}
|
||||
fs.conn = nil
|
||||
fs.ctx = nil
|
||||
fs.cancel()
|
||||
fs.cancel = nil
|
||||
if fs.OnDisconnect != nil {
|
||||
go fs.OnDisconnect(code == 0)
|
||||
go fs.OnDisconnect(fs.parentCtx, code == 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FrameSocket) Connect() error {
|
||||
func (fs *FrameSocket) Connect(ctx context.Context) error {
|
||||
fs.lock.Lock()
|
||||
defer fs.lock.Unlock()
|
||||
|
||||
if fs.conn != nil {
|
||||
return ErrSocketAlreadyOpen
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
fs.parentCtx = ctx
|
||||
fs.cancelCtx, fs.cancel = context.WithCancel(ctx)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fs.ctx, fs.cancel = ctx, cancel
|
||||
fs.conn = conn
|
||||
conn.SetCloseHandler(func(code int, text string) error {
|
||||
fs.log.Debugf("Server closed websocket with status %d/%s", code, text)
|
||||
cancel()
|
||||
// from default CloseHandler
|
||||
message := websocket.FormatCloseMessage(code, "")
|
||||
_ = conn.WriteControl(websocket.CloseMessage, message, time.Now().Add(time.Second))
|
||||
return nil
|
||||
conn, _, err := websocket.Dial(ctx, fs.URL, &websocket.DialOptions{
|
||||
HTTPClient: fs.HTTPClient,
|
||||
HTTPHeader: fs.HTTPHeaders,
|
||||
})
|
||||
if err != nil {
|
||||
fs.cancel()
|
||||
return fmt.Errorf("failed to dial whatsapp web websocket: %w", err)
|
||||
}
|
||||
conn.SetReadLimit(FrameMaxSize)
|
||||
|
||||
fs.conn = conn
|
||||
|
||||
go fs.readPump(conn, ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *FrameSocket) Context() context.Context {
|
||||
return fs.cancelCtx
|
||||
}
|
||||
|
||||
func (fs *FrameSocket) SendFrame(data []byte) error {
|
||||
conn := fs.conn
|
||||
if conn == nil {
|
||||
@@ -153,13 +146,7 @@ func (fs *FrameSocket) SendFrame(data []byte) error {
|
||||
// Copy actual frame data
|
||||
copy(wholeFrame[headerLength+FrameLengthSize:], data)
|
||||
|
||||
if fs.WriteTimeout > 0 {
|
||||
err := conn.SetWriteDeadline(time.Now().Add(fs.WriteTimeout))
|
||||
if err != nil {
|
||||
fs.log.Warnf("Failed to set write deadline: %v", err)
|
||||
}
|
||||
}
|
||||
return conn.WriteMessage(websocket.BinaryMessage, wholeFrame)
|
||||
return conn.Write(fs.cancelCtx, websocket.MessageBinary, wholeFrame)
|
||||
}
|
||||
|
||||
func (fs *FrameSocket) frameComplete() {
|
||||
@@ -219,14 +206,14 @@ func (fs *FrameSocket) readPump(conn *websocket.Conn, ctx context.Context) {
|
||||
go fs.Close(0)
|
||||
}()
|
||||
for {
|
||||
msgType, data, err := conn.ReadMessage()
|
||||
msgType, data, err := conn.Read(ctx)
|
||||
if err != nil {
|
||||
// Ignore the error if the context has been closed
|
||||
if !errors.Is(ctx.Err(), context.Canceled) {
|
||||
if !fs.closed && !errors.Is(ctx.Err(), context.Canceled) {
|
||||
fs.log.Errorf("Error reading from websocket: %v", err)
|
||||
}
|
||||
return
|
||||
} else if msgType != websocket.BinaryMessage {
|
||||
} else if msgType != websocket.MessageBinary {
|
||||
fs.log.Warnf("Got unexpected websocket message type %d", msgType)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Tulir Asokan
|
||||
// Copyright (c) 2025 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
|
||||
@@ -7,6 +7,7 @@
|
||||
package socket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
@@ -74,14 +75,19 @@ func (nh *NoiseHandshake) Decrypt(ciphertext []byte) (plaintext []byte, err erro
|
||||
return
|
||||
}
|
||||
|
||||
func (nh *NoiseHandshake) Finish(fs *FrameSocket, frameHandler FrameHandler, disconnectHandler DisconnectHandler) (*NoiseSocket, error) {
|
||||
func (nh *NoiseHandshake) Finish(
|
||||
ctx context.Context,
|
||||
fs *FrameSocket,
|
||||
frameHandler FrameHandler,
|
||||
disconnectHandler DisconnectHandler,
|
||||
) (*NoiseSocket, error) {
|
||||
if write, read, err := nh.extractAndExpand(nh.salt, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to extract final keys: %w", err)
|
||||
} else if writeKey, err := gcmutil.Prepare(write); err != nil {
|
||||
return nil, fmt.Errorf("failed to create final write cipher: %w", err)
|
||||
} else if readKey, err := gcmutil.Prepare(read); err != nil {
|
||||
return nil, fmt.Errorf("failed to create final read cipher: %w", err)
|
||||
} else if ns, err := newNoiseSocket(fs, writeKey, readKey, frameHandler, disconnectHandler); err != nil {
|
||||
} else if ns, err := newNoiseSocket(ctx, fs, writeKey, readKey, frameHandler, disconnectHandler); err != nil {
|
||||
return nil, fmt.Errorf("failed to create noise socket: %w", err)
|
||||
} else {
|
||||
return ns, nil
|
||||
|
||||
+36
-22
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Tulir Asokan
|
||||
// Copyright (c) 2025 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
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
type NoiseSocket struct {
|
||||
@@ -28,10 +28,16 @@ type NoiseSocket struct {
|
||||
stopConsumer chan struct{}
|
||||
}
|
||||
|
||||
type DisconnectHandler func(socket *NoiseSocket, remote bool)
|
||||
type FrameHandler func([]byte)
|
||||
type DisconnectHandler func(ctx context.Context, socket *NoiseSocket, remote bool)
|
||||
type FrameHandler func(context.Context, []byte)
|
||||
|
||||
func newNoiseSocket(fs *FrameSocket, writeKey, readKey cipher.AEAD, frameHandler FrameHandler, disconnectHandler DisconnectHandler) (*NoiseSocket, error) {
|
||||
func newNoiseSocket(
|
||||
ctx context.Context,
|
||||
fs *FrameSocket,
|
||||
writeKey, readKey cipher.AEAD,
|
||||
frameHandler FrameHandler,
|
||||
disconnectHandler DisconnectHandler,
|
||||
) (*NoiseSocket, error) {
|
||||
ns := &NoiseSocket{
|
||||
fs: fs,
|
||||
writeKey: writeKey,
|
||||
@@ -39,10 +45,10 @@ func newNoiseSocket(fs *FrameSocket, writeKey, readKey cipher.AEAD, frameHandler
|
||||
onFrame: frameHandler,
|
||||
stopConsumer: make(chan struct{}),
|
||||
}
|
||||
fs.OnDisconnect = func(remote bool) {
|
||||
disconnectHandler(ns, remote)
|
||||
fs.OnDisconnect = func(ctx context.Context, remote bool) {
|
||||
disconnectHandler(ctx, ns, remote)
|
||||
}
|
||||
go ns.consumeFrames(fs.ctx, fs.Frames)
|
||||
go ns.consumeFrames(ctx, fs.Frames)
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
@@ -55,7 +61,7 @@ func (ns *NoiseSocket) consumeFrames(ctx context.Context, frames <-chan []byte)
|
||||
for {
|
||||
select {
|
||||
case frame := <-frames:
|
||||
ns.receiveEncryptedFrame(frame)
|
||||
ns.receiveEncryptedFrame(ctx, frame)
|
||||
case <-ctxDone:
|
||||
return
|
||||
case <-ns.stopConsumer:
|
||||
@@ -70,37 +76,45 @@ func generateIV(count uint32) []byte {
|
||||
return iv
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) Context() context.Context {
|
||||
return ns.fs.Context()
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) Stop(disconnect bool) {
|
||||
if ns.destroyed.CompareAndSwap(false, true) {
|
||||
close(ns.stopConsumer)
|
||||
ns.fs.OnDisconnect = nil
|
||||
if disconnect {
|
||||
ns.fs.Close(websocket.CloseNormalClosure)
|
||||
ns.fs.Close(websocket.StatusNormalClosure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) SendFrame(plaintext []byte) error {
|
||||
func (ns *NoiseSocket) SendFrame(ctx context.Context, plaintext []byte) error {
|
||||
ns.writeLock.Lock()
|
||||
defer ns.writeLock.Unlock()
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
// Don't reuse plaintext slice for storage as it may be needed for retries
|
||||
ciphertext := ns.writeKey.Seal(nil, generateIV(ns.writeCounter), plaintext, nil)
|
||||
ns.writeCounter++
|
||||
err := ns.fs.SendFrame(ciphertext)
|
||||
ns.writeLock.Unlock()
|
||||
return err
|
||||
doneChan := make(chan error, 1)
|
||||
go func() {
|
||||
doneChan <- ns.fs.SendFrame(ciphertext)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case retErr := <-doneChan:
|
||||
return retErr
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) receiveEncryptedFrame(ciphertext []byte) {
|
||||
count := atomic.AddUint32(&ns.readCounter, 1) - 1
|
||||
plaintext, err := ns.readKey.Open(nil, generateIV(count), ciphertext, nil)
|
||||
func (ns *NoiseSocket) receiveEncryptedFrame(ctx context.Context, ciphertext []byte) {
|
||||
plaintext, err := ns.readKey.Open(ciphertext[:0], generateIV(ns.readCounter), ciphertext, nil)
|
||||
ns.readCounter++
|
||||
if err != nil {
|
||||
ns.fs.log.Warnf("Failed to decrypt frame: %v", err)
|
||||
return
|
||||
}
|
||||
ns.onFrame(plaintext)
|
||||
ns.onFrame(ctx, plaintext)
|
||||
}
|
||||
|
||||
func (ns *NoiseSocket) IsConnected() bool {
|
||||
|
||||
@@ -236,7 +236,7 @@ func (cli *Client) rawUpload(ctx context.Context, dataToUpload io.Reader, upload
|
||||
req.Header.Set("Origin", socket.Origin)
|
||||
req.Header.Set("Referer", socket.Origin+"/")
|
||||
|
||||
httpResp, err := cli.http.Do(req)
|
||||
httpResp, err := cli.mediaHTTP.Do(req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to execute request: %w", err)
|
||||
} else if httpResp.StatusCode != http.StatusOK {
|
||||
|
||||
@@ -37,11 +37,11 @@ const (
|
||||
//
|
||||
// The links look like https://wa.me/message/<code> or https://api.whatsapp.com/message/<code>. You can either provide
|
||||
// the full link, or just the <code> part.
|
||||
func (cli *Client) ResolveBusinessMessageLink(code string) (*types.BusinessMessageLinkTarget, error) {
|
||||
func (cli *Client) ResolveBusinessMessageLink(ctx context.Context, code string) (*types.BusinessMessageLinkTarget, error) {
|
||||
code = strings.TrimPrefix(code, BusinessMessageLinkPrefix)
|
||||
code = strings.TrimPrefix(code, BusinessMessageLinkDirectPrefix)
|
||||
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:qr",
|
||||
Type: iqGet,
|
||||
// WhatsApp android doesn't seem to have a "to" field for this one at all, not sure why but it works
|
||||
@@ -84,11 +84,11 @@ func (cli *Client) ResolveBusinessMessageLink(code string) (*types.BusinessMessa
|
||||
//
|
||||
// The links look like https://wa.me/qr/<code> or https://api.whatsapp.com/qr/<code>. You can either provide
|
||||
// the full link, or just the <code> part.
|
||||
func (cli *Client) ResolveContactQRLink(code string) (*types.ContactQRLinkTarget, error) {
|
||||
func (cli *Client) ResolveContactQRLink(ctx context.Context, code string) (*types.ContactQRLinkTarget, error) {
|
||||
code = strings.TrimPrefix(code, ContactQRLinkPrefix)
|
||||
code = strings.TrimPrefix(code, ContactQRLinkDirectPrefix)
|
||||
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:qr",
|
||||
Type: iqGet,
|
||||
Content: []waBinary.Node{{
|
||||
@@ -119,12 +119,12 @@ func (cli *Client) ResolveContactQRLink(code string) (*types.ContactQRLinkTarget
|
||||
// (or scanned with the official apps when encoded as a QR code).
|
||||
//
|
||||
// If the revoke parameter is set to true, it will ask the server to revoke the previous link and generate a new one.
|
||||
func (cli *Client) GetContactQRLink(revoke bool) (string, error) {
|
||||
func (cli *Client) GetContactQRLink(ctx context.Context, revoke bool) (string, error) {
|
||||
action := "get"
|
||||
if revoke {
|
||||
action = "revoke"
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "w:qr",
|
||||
Type: iqSet,
|
||||
Content: []waBinary.Node{{
|
||||
@@ -150,8 +150,8 @@ func (cli *Client) GetContactQRLink(revoke bool) (string, error) {
|
||||
//
|
||||
// This is different from the ephemeral status broadcast messages. Use SendMessage to types.StatusBroadcastJID to send
|
||||
// such messages.
|
||||
func (cli *Client) SetStatusMessage(msg string) error {
|
||||
_, err := cli.sendIQ(infoQuery{
|
||||
func (cli *Client) SetStatusMessage(ctx context.Context, msg string) error {
|
||||
_, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "status",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
@@ -165,12 +165,12 @@ func (cli *Client) SetStatusMessage(msg string) error {
|
||||
|
||||
// IsOnWhatsApp checks if the given phone numbers are registered on WhatsApp.
|
||||
// The phone numbers should be in international format, including the `+` prefix.
|
||||
func (cli *Client) IsOnWhatsApp(phones []string) ([]types.IsOnWhatsAppResponse, error) {
|
||||
func (cli *Client) IsOnWhatsApp(ctx context.Context, phones []string) ([]types.IsOnWhatsAppResponse, error) {
|
||||
jids := make([]types.JID, len(phones))
|
||||
for i := range jids {
|
||||
jids[i] = types.NewJID(phones[i], types.LegacyUserServer)
|
||||
}
|
||||
list, err := cli.usync(context.TODO(), jids, "query", "interactive", []waBinary.Node{
|
||||
list, err := cli.usync(ctx, jids, "query", "interactive", []waBinary.Node{
|
||||
{Tag: "business", Content: []waBinary.Node{{Tag: "verified_name"}}},
|
||||
{Tag: "contact"},
|
||||
})
|
||||
@@ -200,8 +200,8 @@ func (cli *Client) IsOnWhatsApp(phones []string) ([]types.IsOnWhatsAppResponse,
|
||||
}
|
||||
|
||||
// GetUserInfo gets basic user info (avatar, status, verified business name, device list).
|
||||
func (cli *Client) GetUserInfo(jids []types.JID) (map[types.JID]types.UserInfo, error) {
|
||||
list, err := cli.usync(context.TODO(), jids, "full", "background", []waBinary.Node{
|
||||
func (cli *Client) GetUserInfo(ctx context.Context, jids []types.JID) (map[types.JID]types.UserInfo, error) {
|
||||
list, err := cli.usync(ctx, jids, "full", "background", []waBinary.Node{
|
||||
{Tag: "business", Content: []waBinary.Node{{Tag: "verified_name"}}},
|
||||
{Tag: "status"},
|
||||
{Tag: "picture"},
|
||||
@@ -236,12 +236,12 @@ func (cli *Client) GetUserInfo(jids []types.JID) (map[types.JID]types.UserInfo,
|
||||
}
|
||||
|
||||
if verifiedName != nil {
|
||||
cli.updateBusinessName(context.TODO(), jid, nil, verifiedName.Details.GetVerifiedName())
|
||||
cli.updateBusinessName(ctx, jid, nil, verifiedName.Details.GetVerifiedName())
|
||||
}
|
||||
respData[jid] = info
|
||||
}
|
||||
|
||||
err = cli.Store.LIDs.PutManyLIDMappings(context.TODO(), mappings)
|
||||
err = cli.Store.LIDs.PutManyLIDMappings(ctx, mappings)
|
||||
if err != nil {
|
||||
// not worth returning on the error, instead just post a log
|
||||
cli.Log.Errorf("Failed to place LID mappings from USync call")
|
||||
@@ -250,8 +250,8 @@ func (cli *Client) GetUserInfo(jids []types.JID) (map[types.JID]types.UserInfo,
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
func (cli *Client) GetBotListV2() ([]types.BotListInfo, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
func (cli *Client) GetBotListV2(ctx context.Context) ([]types.BotListInfo, error) {
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
To: types.ServerJID,
|
||||
Namespace: "bot",
|
||||
Type: iqGet,
|
||||
@@ -284,13 +284,13 @@ func (cli *Client) GetBotListV2() ([]types.BotListInfo, error) {
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (cli *Client) GetBotProfiles(botInfo []types.BotListInfo) ([]types.BotProfileInfo, error) {
|
||||
func (cli *Client) GetBotProfiles(ctx context.Context, botInfo []types.BotListInfo) ([]types.BotProfileInfo, error) {
|
||||
jids := make([]types.JID, len(botInfo))
|
||||
for i, bot := range botInfo {
|
||||
jids[i] = bot.BotJID
|
||||
}
|
||||
|
||||
list, err := cli.usync(context.TODO(), jids, "query", "interactive", []waBinary.Node{
|
||||
list, err := cli.usync(ctx, jids, "query", "interactive", []waBinary.Node{
|
||||
{Tag: "bot", Content: []waBinary.Node{{Tag: "profile", Attrs: waBinary.Attrs{"v": "1"}}}},
|
||||
}, UsyncQueryExtras{
|
||||
BotListInfo: botInfo,
|
||||
@@ -410,8 +410,8 @@ func (cli *Client) parseBusinessProfile(node *waBinary.Node) (*types.BusinessPro
|
||||
}
|
||||
|
||||
// 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{
|
||||
func (cli *Client) GetBusinessProfile(ctx context.Context, jid types.JID) (*types.BusinessProfile, error) {
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
Namespace: "w:biz",
|
||||
@@ -438,16 +438,14 @@ func (cli *Client) GetBusinessProfile(jid types.JID) (*types.BusinessProfile, er
|
||||
return cli.parseBusinessProfile(&node)
|
||||
}
|
||||
|
||||
func (cli *Client) GetUserDevicesContext(ctx context.Context, jids []types.JID) ([]types.JID, error) {
|
||||
return cli.GetUserDevices(ctx, jids)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: use GetUserDevicesContext instead.
|
||||
func (cli *Client) GetUserDevices(jids []types.JID) ([]types.JID, error) {
|
||||
return cli.GetUserDevicesContext(context.Background(), jids)
|
||||
}
|
||||
|
||||
func (cli *Client) GetUserDevicesContext(ctx context.Context, jids []types.JID) ([]types.JID, error) {
|
||||
func (cli *Client) GetUserDevices(ctx context.Context, jids []types.JID) ([]types.JID, error) {
|
||||
if cli == nil {
|
||||
return nil, ErrClientIsNil
|
||||
}
|
||||
@@ -516,7 +514,7 @@ type GetProfilePictureParams struct {
|
||||
// If the profile picture hasn't changed, this will return nil with no error.
|
||||
//
|
||||
// To get a community photo, you should pass `IsCommunity: true`, as otherwise you may get a 401 error.
|
||||
func (cli *Client) GetProfilePictureInfo(jid types.JID, params *GetProfilePictureParams) (*types.ProfilePictureInfo, error) {
|
||||
func (cli *Client) GetProfilePictureInfo(ctx context.Context, jid types.JID, params *GetProfilePictureParams) (*types.ProfilePictureInfo, error) {
|
||||
attrs := waBinary.Attrs{
|
||||
"query": "url",
|
||||
}
|
||||
@@ -565,7 +563,7 @@ func (cli *Client) GetProfilePictureInfo(jid types.JID, params *GetProfilePictur
|
||||
}
|
||||
|
||||
var pictureContent []waBinary.Node
|
||||
if token, _ := cli.Store.PrivacyTokens.GetPrivacyToken(context.TODO(), jid); token != nil {
|
||||
if token, _ := cli.Store.PrivacyTokens.GetPrivacyToken(ctx, jid); token != nil {
|
||||
pictureContent = []waBinary.Node{{
|
||||
Tag: "tctoken",
|
||||
Content: token.Token,
|
||||
@@ -578,7 +576,7 @@ func (cli *Client) GetProfilePictureInfo(jid types.JID, params *GetProfilePictur
|
||||
Content: pictureContent,
|
||||
}}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: namespace,
|
||||
Type: "get",
|
||||
To: to,
|
||||
@@ -769,8 +767,7 @@ func (cli *Client) getFBIDDevicesInternal(ctx context.Context, jids []types.JID)
|
||||
users[i].Attrs = waBinary.Attrs{"jid": jid}
|
||||
// TODO include dhash for users
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "fbid:devices",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
@@ -857,8 +854,7 @@ func (cli *Client) usync(ctx context.Context, jids []types.JID, mode, context st
|
||||
return nil, fmt.Errorf("unknown user server '%s'", jid.Server)
|
||||
}
|
||||
}
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
Context: ctx,
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "usync",
|
||||
Type: "get",
|
||||
To: types.ServerJID,
|
||||
@@ -904,8 +900,8 @@ func (cli *Client) parseBlocklist(node *waBinary.Node) *types.Blocklist {
|
||||
}
|
||||
|
||||
// GetBlocklist gets the list of users that this user has blocked.
|
||||
func (cli *Client) GetBlocklist() (*types.Blocklist, error) {
|
||||
resp, err := cli.sendIQ(infoQuery{
|
||||
func (cli *Client) GetBlocklist(ctx context.Context) (*types.Blocklist, error) {
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "blocklist",
|
||||
Type: iqGet,
|
||||
To: types.ServerJID,
|
||||
@@ -921,8 +917,8 @@ func (cli *Client) GetBlocklist() (*types.Blocklist, error) {
|
||||
}
|
||||
|
||||
// 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{
|
||||
func (cli *Client) UpdateBlocklist(ctx context.Context, jid types.JID, action events.BlocklistChangeAction) (*types.Blocklist, error) {
|
||||
resp, err := cli.sendIQ(ctx, infoQuery{
|
||||
Namespace: "blocklist",
|
||||
Type: iqSet,
|
||||
To: types.ServerJID,
|
||||
|
||||
Reference in New Issue
Block a user