summaryrefslogtreecommitdiffstats
path: root/vendor/go.mau.fi/whatsmeow/presence.go
blob: 0ce94a70ab1b7ffa4796d0c79c636f4a8b782456 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright (c) 2021 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package whatsmeow

import (
	"fmt"
	"sync/atomic"

	waBinary "go.mau.fi/whatsmeow/binary"
	"go.mau.fi/whatsmeow/types"
	"go.mau.fi/whatsmeow/types/events"
)

func (cli *Client) handleChatState(node *waBinary.Node) {
	source, err := cli.parseMessageSource(node, true)
	if err != nil {
		cli.Log.Warnf("Failed to parse chat state update: %v", err)
	} else if len(node.GetChildren()) != 1 {
		cli.Log.Warnf("Failed to parse chat state update: unexpected number of children in element (%d)", len(node.GetChildren()))
	} else {
		child := node.GetChildren()[0]
		presence := types.ChatPresence(child.Tag)
		if presence != types.ChatPresenceComposing && presence != types.ChatPresencePaused {
			cli.Log.Warnf("Unrecognized chat presence state %s", child.Tag)
		}
		media := types.ChatPresenceMedia(child.AttrGetter().OptionalString("media"))
		cli.dispatchEvent(&events.ChatPresence{
			MessageSource: source,
			State:         presence,
			Media:         media,
		})
	}
}

func (cli *Client) handlePresence(node *waBinary.Node) {
	var evt events.Presence
	ag := node.AttrGetter()
	evt.From = ag.JID("from")
	presenceType := ag.OptionalString("type")
	if presenceType == "unavailable" {
		evt.Unavailable = true
	} else if presenceType != "" {
		cli.Log.Debugf("Unrecognized presence type '%s' in presence event from %s", presenceType, evt.From)
	}
	lastSeen := ag.OptionalString("last")
	if lastSeen != "" && lastSeen != "deny" {
		evt.LastSeen = ag.UnixTime("last")
	}
	if !ag.OK() {
		cli.Log.Warnf("Error parsing presence event: %+v", ag.Errors)
	} else {
		cli.dispatchEvent(&evt)
	}
}

// SendPresence updates the user's presence status on WhatsApp.
//
// 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 {
	if len(cli.Store.PushName) == 0 {
		return ErrNoPushName
	}
	if state == types.PresenceAvailable {
		atomic.CompareAndSwapUint32(&cli.sendActiveReceipts, 0, 1)
	} else {
		atomic.CompareAndSwapUint32(&cli.sendActiveReceipts, 1, 0)
	}
	return cli.sendNode(waBinary.Node{
		Tag: "presence",
		Attrs: waBinary.Attrs{
			"name": cli.Store.PushName,
			"type": string(state),
		},
	})
}

// SubscribePresence asks the WhatsApp servers to send presence updates of a specific user to this client.
//
// After subscribing to this event, you should start receiving *events.Presence for that user in normal event handlers.
//
// Also, it seems that the WhatsApp servers require you to be online to receive presence status from other users,
// 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 {
	privacyToken, err := cli.Store.PrivacyTokens.GetPrivacyToken(jid)
	if err != nil {
		return fmt.Errorf("failed to get privacy token: %w", err)
	} else if privacyToken == nil {
		if cli.ErrorOnSubscribePresenceWithoutToken {
			return fmt.Errorf("%w for %v", ErrNoPrivacyToken, jid.ToNonAD())
		} else {
			cli.Log.Debugf("Trying to subscribe to presence of %s without privacy token", jid)
		}
	}
	req := waBinary.Node{
		Tag: "presence",
		Attrs: waBinary.Attrs{
			"type": "subscribe",
			"to":   jid,
		},
	}
	if privacyToken != nil {
		req.Content = []waBinary.Node{{
			Tag:     "tctoken",
			Content: privacyToken.Token,
		}}
	}
	return cli.sendNode(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 {
	ownID := cli.getOwnID()
	if ownID.IsEmpty() {
		return ErrNotLoggedIn
	}
	content := []waBinary.Node{{Tag: string(state)}}
	if state == types.ChatPresenceComposing && len(media) > 0 {
		content[0].Attrs = waBinary.Attrs{
			"media": string(media),
		}
	}
	return cli.sendNode(waBinary.Node{
		Tag: "chatstate",
		Attrs: waBinary.Attrs{
			"from": ownID,
			"to":   jid,
		},
		Content: content,
	})
}