Skip to content

Commit 4889322

Browse files
feat: add presence handling
1 parent 8488980 commit 4889322

4 files changed

Lines changed: 45 additions & 1 deletion

File tree

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ away, stop it (Ctrl-C) when you're back. That's the whole model.
1212
- Never replies to your own messages (including its own prior replies).
1313
- Sends at most one reply per sender per calendar day.
1414
- Supports end-to-end encrypted DMs (which is most of them, by default).
15+
- Also sets your Matrix presence (default: "unavailable"/"Away") for as long
16+
as it runs, so you don't show as available just because the bot is
17+
connected and polling `/sync`.
1518

1619
## 1. Create a dedicated login session for the bot
1720

@@ -76,7 +79,8 @@ cp config.example.yaml config.yaml
7679

7780
Fill in `homeserver`, `user_id`, `access_token`, `device_id`, `pickle_key`,
7881
and (recommended) `recovery_key`. Adjust `reply_message` to whatever you
79-
want people to see. `config.yaml` is gitignored - it holds secrets.
82+
want people to see, and `presence` if you don't want the default "Away"
83+
status (see below). `config.yaml` is gitignored - it holds secrets.
8084

8185
## 5. Build & run
8286

@@ -176,6 +180,18 @@ Example: `"Hello {firstName}, I'm currently away and will get back to you as soo
176180
Falls back to the username wherever a display name isn't known yet (e.g. the
177181
bot hasn't seen their profile before their first message).
178182

183+
## Presence
184+
185+
Every `/sync` request a client makes tells the homeserver `set_presence`,
186+
and the Matrix spec's default for that is `online` - so a bot that's just
187+
connected and long-polling `/sync`, with no presence handling of its own,
188+
makes you show up as available to everyone the whole time it runs, no
189+
matter what the auto-reply says. This bot sets `presence` (default
190+
`"unavailable"`, shown as "Away" in most clients) on every sync request
191+
instead, so your status matches reality. Set it to `"offline"` to look
192+
fully offline, or `"online"` if you'd rather leave your real presence
193+
alone and rely on the auto-reply only.
194+
179195
## How "one reply per sender per day" works
180196

181197
`state.json` maps each sender's user ID to the last calendar day (local

config.example.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,11 @@ state_path: "state.json"
2828
# The message sent back to each DM sender, at most once per day. Supports
2929
# placeholders: {firstName}, {displayName}, {username} - see README.md.
3030
reply_message: "Hello {firstName}, I'm currently away and will get back to you as soon as I can."
31+
32+
# Presence shown to others while the bot is running. One of "unavailable"
33+
# (shown as "Away" in most clients), "offline", or "online" (leaves your
34+
# real presence alone - use this if you only want the auto-reply and don't
35+
# want the bot to touch presence at all). Without this, simply polling
36+
# /sync marks you "online" on every request, so you'd always show as
37+
# available regardless of what the auto-reply says.
38+
presence: "unavailable"

config.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ type Config struct {
3030
StatePath string `yaml:"state_path"`
3131

3232
ReplyMessage string `yaml:"reply_message"`
33+
34+
// Presence is sent as set_presence on every /sync request, so it's what
35+
// makes you show up as "away" to others while the bot runs - without it,
36+
// the mere act of the bot polling /sync marks you "online" on every
37+
// request, regardless of what the auto-reply says. One of "unavailable"
38+
// (shown as "Away" in most clients - the default), "offline", or
39+
// "online" (to leave your real presence alone and only use the
40+
// auto-reply).
41+
Presence string `yaml:"presence"`
3342
}
3443

3544
func LoadConfig(path string) (*Config, error) {
@@ -42,6 +51,7 @@ func LoadConfig(path string) (*Config, error) {
4251
CryptoDBPath: "crypto.db",
4352
StatePath: "state.json",
4453
ReplyMessage: "I'm currently away and will get back to you as soon as I can.",
54+
Presence: "unavailable",
4555
}
4656
if err := yaml.Unmarshal(data, cfg); err != nil {
4757
return nil, fmt.Errorf("failed to parse config file %q: %w", path, err)
@@ -69,5 +79,10 @@ func (c *Config) validate() error {
6979
if problems != "" {
7080
return fmt.Errorf("config is missing required field(s): %s", problems)
7181
}
82+
switch c.Presence {
83+
case "online", "offline", "unavailable":
84+
default:
85+
return fmt.Errorf("presence must be one of \"online\", \"offline\", \"unavailable\", got %q", c.Presence)
86+
}
7287
return nil
7388
}

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ func main() {
4242
}
4343
client.DeviceID = id.DeviceID(cfg.DeviceID)
4444
client.Log = log
45+
// Sent as set_presence on every /sync request. Without this, the mere
46+
// act of the bot polling /sync marks the account "online" on every
47+
// request - regardless of the auto-reply - which is why presence would
48+
// otherwise always show as available while the bot runs.
49+
client.SyncPresence = event.Presence(cfg.Presence)
4550

4651
syncer := mautrix.NewDefaultSyncer()
4752
client.Syncer = syncer

0 commit comments

Comments
 (0)