ScuttleBot

fix: refresh NAMES before returning user list girc's internal user tracking was missing bots that joined after the bridge. Now sends a NAMES command to Ergo before reading the user list, forcing an authoritative refresh from the server. Also adds RefreshNames method to bridge bot.

lmata 2026-04-05 23:43 trunk
Commit cca45cb0bf21809ec77ba76de788b25e05b90a11836bea47659f9f45faa765ea
--- internal/api/chat.go
+++ internal/api/chat.go
@@ -23,10 +23,11 @@
2323
Stats() bridge.Stats
2424
TouchUser(channel, nick string)
2525
Users(channel string) []string
2626
UsersWithModes(channel string) []bridge.UserInfo
2727
ChannelModes(channel string) string
28
+ RefreshNames(channel string)
2829
}
2930
3031
func (s *Server) handleJoinChannel(w http.ResponseWriter, r *http.Request) {
3132
channel := "#" + r.PathValue("channel")
3233
s.bridge.JoinChannel(channel)
@@ -110,10 +111,13 @@
110111
w.WriteHeader(http.StatusNoContent)
111112
}
112113
113114
func (s *Server) handleChannelUsers(w http.ResponseWriter, r *http.Request) {
114115
channel := "#" + r.PathValue("channel")
116
+ // Refresh girc's user list from the server before returning.
117
+ s.bridge.RefreshNames(channel)
118
+ time.Sleep(200 * time.Millisecond) // give girc time to process NAMES reply
115119
users := s.bridge.UsersWithModes(channel)
116120
if users == nil {
117121
users = []bridge.UserInfo{}
118122
}
119123
modes := s.bridge.ChannelModes(channel)
120124
--- internal/api/chat.go
+++ internal/api/chat.go
@@ -23,10 +23,11 @@
23 Stats() bridge.Stats
24 TouchUser(channel, nick string)
25 Users(channel string) []string
26 UsersWithModes(channel string) []bridge.UserInfo
27 ChannelModes(channel string) string
 
28 }
29
30 func (s *Server) handleJoinChannel(w http.ResponseWriter, r *http.Request) {
31 channel := "#" + r.PathValue("channel")
32 s.bridge.JoinChannel(channel)
@@ -110,10 +111,13 @@
110 w.WriteHeader(http.StatusNoContent)
111 }
112
113 func (s *Server) handleChannelUsers(w http.ResponseWriter, r *http.Request) {
114 channel := "#" + r.PathValue("channel")
 
 
 
115 users := s.bridge.UsersWithModes(channel)
116 if users == nil {
117 users = []bridge.UserInfo{}
118 }
119 modes := s.bridge.ChannelModes(channel)
120
--- internal/api/chat.go
+++ internal/api/chat.go
@@ -23,10 +23,11 @@
23 Stats() bridge.Stats
24 TouchUser(channel, nick string)
25 Users(channel string) []string
26 UsersWithModes(channel string) []bridge.UserInfo
27 ChannelModes(channel string) string
28 RefreshNames(channel string)
29 }
30
31 func (s *Server) handleJoinChannel(w http.ResponseWriter, r *http.Request) {
32 channel := "#" + r.PathValue("channel")
33 s.bridge.JoinChannel(channel)
@@ -110,10 +111,13 @@
111 w.WriteHeader(http.StatusNoContent)
112 }
113
114 func (s *Server) handleChannelUsers(w http.ResponseWriter, r *http.Request) {
115 channel := "#" + r.PathValue("channel")
116 // Refresh girc's user list from the server before returning.
117 s.bridge.RefreshNames(channel)
118 time.Sleep(200 * time.Millisecond) // give girc time to process NAMES reply
119 users := s.bridge.UsersWithModes(channel)
120 if users == nil {
121 users = []bridge.UserInfo{}
122 }
123 modes := s.bridge.ChannelModes(channel)
124
--- internal/api/chat_test.go
+++ internal/api/chat_test.go
@@ -35,10 +35,11 @@
3535
}
3636
func (b *stubChatBridge) Stats() bridge.Stats { return bridge.Stats{} }
3737
func (b *stubChatBridge) Users(string) []string { return nil }
3838
func (b *stubChatBridge) UsersWithModes(string) []bridge.UserInfo { return nil }
3939
func (b *stubChatBridge) ChannelModes(string) string { return "" }
40
+func (b *stubChatBridge) RefreshNames(string) {}
4041
func (b *stubChatBridge) TouchUser(channel, nick string) {
4142
b.touched = append(b.touched, struct{ channel, nick string }{channel: channel, nick: nick})
4243
}
4344
4445
func TestHandleChannelPresence(t *testing.T) {
4546
--- internal/api/chat_test.go
+++ internal/api/chat_test.go
@@ -35,10 +35,11 @@
35 }
36 func (b *stubChatBridge) Stats() bridge.Stats { return bridge.Stats{} }
37 func (b *stubChatBridge) Users(string) []string { return nil }
38 func (b *stubChatBridge) UsersWithModes(string) []bridge.UserInfo { return nil }
39 func (b *stubChatBridge) ChannelModes(string) string { return "" }
 
40 func (b *stubChatBridge) TouchUser(channel, nick string) {
41 b.touched = append(b.touched, struct{ channel, nick string }{channel: channel, nick: nick})
42 }
43
44 func TestHandleChannelPresence(t *testing.T) {
45
--- internal/api/chat_test.go
+++ internal/api/chat_test.go
@@ -35,10 +35,11 @@
35 }
36 func (b *stubChatBridge) Stats() bridge.Stats { return bridge.Stats{} }
37 func (b *stubChatBridge) Users(string) []string { return nil }
38 func (b *stubChatBridge) UsersWithModes(string) []bridge.UserInfo { return nil }
39 func (b *stubChatBridge) ChannelModes(string) string { return "" }
40 func (b *stubChatBridge) RefreshNames(string) {}
41 func (b *stubChatBridge) TouchUser(channel, nick string) {
42 b.touched = append(b.touched, struct{ channel, nick string }{channel: channel, nick: nick})
43 }
44
45 func TestHandleChannelPresence(t *testing.T) {
46
--- internal/bots/bridge/bridge.go
+++ internal/bots/bridge/bridge.go
@@ -449,18 +449,26 @@
449449
b.webUsers[channel] = make(map[string]time.Time)
450450
}
451451
b.webUsers[channel][nick] = time.Now()
452452
b.mu.Unlock()
453453
}
454
+
455
+// RefreshNames sends a NAMES command for a channel, forcing girc to
456
+// update its user list from the server's authoritative response.
457
+func (b *Bot) RefreshNames(channel string) {
458
+ if b.client != nil {
459
+ b.client.Cmd.SendRawf("NAMES %s", channel)
460
+ }
461
+}
454462
455463
// Users returns the current nick list for a channel — IRC connections plus
456464
// web UI users who have posted recently within the configured TTL.
457465
func (b *Bot) Users(channel string) []string {
458466
seen := make(map[string]bool)
459467
var nicks []string
460468
461
- // IRC-connected nicks from NAMES — exclude the bridge bot itself.
469
+ // IRC-connected nicks from girc's state — exclude the bridge bot itself.
462470
if b.client != nil {
463471
if ch := b.client.LookupChannel(channel); ch != nil {
464472
for _, u := range ch.Users(b.client) {
465473
if u.Nick == b.nick {
466474
continue // skip the bridge bot
467475
--- internal/bots/bridge/bridge.go
+++ internal/bots/bridge/bridge.go
@@ -449,18 +449,26 @@
449 b.webUsers[channel] = make(map[string]time.Time)
450 }
451 b.webUsers[channel][nick] = time.Now()
452 b.mu.Unlock()
453 }
 
 
 
 
 
 
 
 
454
455 // Users returns the current nick list for a channel — IRC connections plus
456 // web UI users who have posted recently within the configured TTL.
457 func (b *Bot) Users(channel string) []string {
458 seen := make(map[string]bool)
459 var nicks []string
460
461 // IRC-connected nicks from NAMES — exclude the bridge bot itself.
462 if b.client != nil {
463 if ch := b.client.LookupChannel(channel); ch != nil {
464 for _, u := range ch.Users(b.client) {
465 if u.Nick == b.nick {
466 continue // skip the bridge bot
467
--- internal/bots/bridge/bridge.go
+++ internal/bots/bridge/bridge.go
@@ -449,18 +449,26 @@
449 b.webUsers[channel] = make(map[string]time.Time)
450 }
451 b.webUsers[channel][nick] = time.Now()
452 b.mu.Unlock()
453 }
454
455 // RefreshNames sends a NAMES command for a channel, forcing girc to
456 // update its user list from the server's authoritative response.
457 func (b *Bot) RefreshNames(channel string) {
458 if b.client != nil {
459 b.client.Cmd.SendRawf("NAMES %s", channel)
460 }
461 }
462
463 // Users returns the current nick list for a channel — IRC connections plus
464 // web UI users who have posted recently within the configured TTL.
465 func (b *Bot) Users(channel string) []string {
466 seen := make(map[string]bool)
467 var nicks []string
468
469 // IRC-connected nicks from girc's state — exclude the bridge bot itself.
470 if b.client != nil {
471 if ch := b.client.LookupChannel(channel); ch != nil {
472 for _, u := range ch.Users(b.client) {
473 if u.Nick == b.nick {
474 continue // skip the bridge bot
475

Keyboard Shortcuts

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