BREAKING CHANGE: switch to coder/websocket and use contexts everywhere

Closes #896
This commit is contained in:
Tulir Asokan
2025-10-27 16:17:26 +02:00
parent 088fa33fb8
commit 3d82d3101d
34 changed files with 509 additions and 502 deletions
+2 -4
View File
@@ -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
View File
@@ -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,
+6 -4
View File
@@ -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{{
+90 -80
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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=
+47 -48
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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": &params,
})
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
View File
@@ -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
View File
@@ -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{
+19 -19
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+2 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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)
}
+14 -13
View File
@@ -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)
+3 -3
View File
@@ -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
View File
@@ -32,7 +32,7 @@ const (
var WAConnHeader = []byte{'W', 'A', WAMagicValue, token.DictVersion}
const (
FrameMaxSize = 2 << 23
FrameMaxSize = 1 << 24
FrameLengthSize = 3
)
+46 -59
View File
@@ -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
}
+9 -3
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+34 -38
View File
@@ -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,