ScuttleBot

feat: snitch MONITOR for presence tracking and away-notify (#108, #121) Add MONITOR support to track agent online/offline status — alerts operators when a monitored nick goes offline. Config accepts MonitorNicks list, with MonitorAdd/MonitorRemove for runtime updates. Also handle away-notify: alert when agents go AWAY unexpectedly.

lmata 2026-04-05 03:57 trunk
Commit 170b64595fd946bf7d4946900dddb1b3033bb6e7ebb07a5cfe811b89626b15e1
--- internal/bots/snitch/snitch.go
+++ internal/bots/snitch/snitch.go
@@ -48,10 +48,14 @@
4848
// JoinPartWindow is the rolling window for join/part cycling. Default: 30s.
4949
JoinPartWindow time.Duration
5050
5151
// Channels is the list of channels to join on connect.
5252
Channels []string
53
+
54
+ // MonitorNicks is the list of nicks to track via IRC MONITOR.
55
+ // Snitch will alert when a monitored nick goes offline unexpectedly.
56
+ MonitorNicks []string
5357
}
5458
5559
func (c *Config) setDefaults() {
5660
if c.Nick == "" {
5761
c.Nick = defaultNick
@@ -142,12 +146,38 @@
142146
cl.Cmd.Join(ch)
143147
}
144148
if b.cfg.AlertChannel != "" {
145149
cl.Cmd.Join(b.cfg.AlertChannel)
146150
}
151
+ if len(b.cfg.MonitorNicks) > 0 {
152
+ cl.Cmd.SendRawf("MONITOR + %s", strings.Join(b.cfg.MonitorNicks, ","))
153
+ }
147154
if b.log != nil {
148
- b.log.Info("snitch connected", "channels", b.cfg.Channels)
155
+ b.log.Info("snitch connected", "channels", b.cfg.Channels, "monitor", b.cfg.MonitorNicks)
156
+ }
157
+ })
158
+
159
+ // away-notify: track agents going idle or returning.
160
+ c.Handlers.AddBg(girc.AWAY, func(_ *girc.Client, e girc.Event) {
161
+ if e.Source == nil {
162
+ return
163
+ }
164
+ nick := e.Source.Name
165
+ reason := e.Last()
166
+ if reason != "" {
167
+ b.alert(fmt.Sprintf("agent away: %s (%s)", nick, reason))
168
+ }
169
+ })
170
+
171
+ c.Handlers.AddBg(girc.RPL_MONOFFLINE, func(_ *girc.Client, e girc.Event) {
172
+ nicks := e.Last()
173
+ for _, nick := range strings.Split(nicks, ",") {
174
+ nick = strings.TrimSpace(nick)
175
+ if nick == "" {
176
+ continue
177
+ }
178
+ b.alert(fmt.Sprintf("monitored nick offline: %s", nick))
149179
}
150180
})
151181
152182
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
153183
if ch := e.Last(); strings.HasPrefix(ch, "#") {
@@ -203,10 +233,24 @@
203233
func (b *Bot) JoinChannel(channel string) {
204234
if b.client != nil {
205235
b.client.Cmd.Join(channel)
206236
}
207237
}
238
+
239
+// MonitorAdd adds nicks to the MONITOR list at runtime.
240
+func (b *Bot) MonitorAdd(nicks ...string) {
241
+ if b.client != nil && len(nicks) > 0 {
242
+ b.client.Cmd.SendRawf("MONITOR + %s", strings.Join(nicks, ","))
243
+ }
244
+}
245
+
246
+// MonitorRemove removes nicks from the MONITOR list at runtime.
247
+func (b *Bot) MonitorRemove(nicks ...string) {
248
+ if b.client != nil && len(nicks) > 0 {
249
+ b.client.Cmd.SendRawf("MONITOR - %s", strings.Join(nicks, ","))
250
+ }
251
+}
208252
209253
func (b *Bot) window(channel, nick string) *nickWindow {
210254
if b.windows[channel] == nil {
211255
b.windows[channel] = make(map[string]*nickWindow)
212256
}
213257
--- internal/bots/snitch/snitch.go
+++ internal/bots/snitch/snitch.go
@@ -48,10 +48,14 @@
48 // JoinPartWindow is the rolling window for join/part cycling. Default: 30s.
49 JoinPartWindow time.Duration
50
51 // Channels is the list of channels to join on connect.
52 Channels []string
 
 
 
 
53 }
54
55 func (c *Config) setDefaults() {
56 if c.Nick == "" {
57 c.Nick = defaultNick
@@ -142,12 +146,38 @@
142 cl.Cmd.Join(ch)
143 }
144 if b.cfg.AlertChannel != "" {
145 cl.Cmd.Join(b.cfg.AlertChannel)
146 }
 
 
 
147 if b.log != nil {
148 b.log.Info("snitch connected", "channels", b.cfg.Channels)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149 }
150 })
151
152 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
153 if ch := e.Last(); strings.HasPrefix(ch, "#") {
@@ -203,10 +233,24 @@
203 func (b *Bot) JoinChannel(channel string) {
204 if b.client != nil {
205 b.client.Cmd.Join(channel)
206 }
207 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
209 func (b *Bot) window(channel, nick string) *nickWindow {
210 if b.windows[channel] == nil {
211 b.windows[channel] = make(map[string]*nickWindow)
212 }
213
--- internal/bots/snitch/snitch.go
+++ internal/bots/snitch/snitch.go
@@ -48,10 +48,14 @@
48 // JoinPartWindow is the rolling window for join/part cycling. Default: 30s.
49 JoinPartWindow time.Duration
50
51 // Channels is the list of channels to join on connect.
52 Channels []string
53
54 // MonitorNicks is the list of nicks to track via IRC MONITOR.
55 // Snitch will alert when a monitored nick goes offline unexpectedly.
56 MonitorNicks []string
57 }
58
59 func (c *Config) setDefaults() {
60 if c.Nick == "" {
61 c.Nick = defaultNick
@@ -142,12 +146,38 @@
146 cl.Cmd.Join(ch)
147 }
148 if b.cfg.AlertChannel != "" {
149 cl.Cmd.Join(b.cfg.AlertChannel)
150 }
151 if len(b.cfg.MonitorNicks) > 0 {
152 cl.Cmd.SendRawf("MONITOR + %s", strings.Join(b.cfg.MonitorNicks, ","))
153 }
154 if b.log != nil {
155 b.log.Info("snitch connected", "channels", b.cfg.Channels, "monitor", b.cfg.MonitorNicks)
156 }
157 })
158
159 // away-notify: track agents going idle or returning.
160 c.Handlers.AddBg(girc.AWAY, func(_ *girc.Client, e girc.Event) {
161 if e.Source == nil {
162 return
163 }
164 nick := e.Source.Name
165 reason := e.Last()
166 if reason != "" {
167 b.alert(fmt.Sprintf("agent away: %s (%s)", nick, reason))
168 }
169 })
170
171 c.Handlers.AddBg(girc.RPL_MONOFFLINE, func(_ *girc.Client, e girc.Event) {
172 nicks := e.Last()
173 for _, nick := range strings.Split(nicks, ",") {
174 nick = strings.TrimSpace(nick)
175 if nick == "" {
176 continue
177 }
178 b.alert(fmt.Sprintf("monitored nick offline: %s", nick))
179 }
180 })
181
182 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
183 if ch := e.Last(); strings.HasPrefix(ch, "#") {
@@ -203,10 +233,24 @@
233 func (b *Bot) JoinChannel(channel string) {
234 if b.client != nil {
235 b.client.Cmd.Join(channel)
236 }
237 }
238
239 // MonitorAdd adds nicks to the MONITOR list at runtime.
240 func (b *Bot) MonitorAdd(nicks ...string) {
241 if b.client != nil && len(nicks) > 0 {
242 b.client.Cmd.SendRawf("MONITOR + %s", strings.Join(nicks, ","))
243 }
244 }
245
246 // MonitorRemove removes nicks from the MONITOR list at runtime.
247 func (b *Bot) MonitorRemove(nicks ...string) {
248 if b.client != nil && len(nicks) > 0 {
249 b.client.Cmd.SendRawf("MONITOR - %s", strings.Join(nicks, ","))
250 }
251 }
252
253 func (b *Bot) window(channel, nick string) *nickWindow {
254 if b.windows[channel] == nil {
255 b.windows[channel] = make(map[string]*nickWindow)
256 }
257

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button