ScuttleBot

fix: all system bots join channels on connect (#71) 7 of 10 bots connected to IRC but never joined channels because their constructors didn't accept or use the channel list from the manager. Add channels parameter to herald, oracle, scroll, warden constructors and Channels field to snitch, sentinel, steward Config structs. Each bot's CONNECTED handler now joins channels on connect. Also fix herald default policy to include join_all_channels: true.

lmata 2026-04-04 19:43 trunk
Commit d605172b52ae0048189f5c95d7e506b7ae7895c3d54cbfa50731f9019fe5cbd9
--- internal/api/policies.go
+++ internal/api/policies.go
@@ -95,14 +95,15 @@
9595
Description: "Records all channel messages to a structured log store.",
9696
Nick: "scribe",
9797
JoinAllChannels: true,
9898
},
9999
{
100
- ID: "herald",
101
- Name: "Herald",
102
- Description: "Routes event notifications from external systems to IRC channels.",
103
- Nick: "herald",
100
+ ID: "herald",
101
+ Name: "Herald",
102
+ Description: "Routes event notifications from external systems to IRC channels.",
103
+ Nick: "herald",
104
+ JoinAllChannels: true,
104105
},
105106
{
106107
ID: "oracle",
107108
Name: "Oracle",
108109
Description: "On-demand channel summarisation via DM using an LLM.",
109110
--- internal/api/policies.go
+++ internal/api/policies.go
@@ -95,14 +95,15 @@
95 Description: "Records all channel messages to a structured log store.",
96 Nick: "scribe",
97 JoinAllChannels: true,
98 },
99 {
100 ID: "herald",
101 Name: "Herald",
102 Description: "Routes event notifications from external systems to IRC channels.",
103 Nick: "herald",
 
104 },
105 {
106 ID: "oracle",
107 Name: "Oracle",
108 Description: "On-demand channel summarisation via DM using an LLM.",
109
--- internal/api/policies.go
+++ internal/api/policies.go
@@ -95,14 +95,15 @@
95 Description: "Records all channel messages to a structured log store.",
96 Nick: "scribe",
97 JoinAllChannels: true,
98 },
99 {
100 ID: "herald",
101 Name: "Herald",
102 Description: "Routes event notifications from external systems to IRC channels.",
103 Nick: "herald",
104 JoinAllChannels: true,
105 },
106 {
107 ID: "oracle",
108 Name: "Oracle",
109 Description: "On-demand channel summarisation via DM using an LLM.",
110
--- internal/bots/herald/herald.go
+++ internal/bots/herald/herald.go
@@ -86,10 +86,11 @@
8686
8787
// Bot is the herald bot.
8888
type Bot struct {
8989
ircAddr string
9090
password string
91
+ channels []string
9192
routes RouteConfig
9293
limiter *RateLimiter
9394
queue chan Event
9495
log *slog.Logger
9596
client *girc.Client
@@ -97,20 +98,21 @@
9798
9899
const defaultQueueSize = 256
99100
100101
// New creates a herald bot. ratePerSec and burst configure the token-bucket
101102
// rate limiter (e.g. 5 messages/sec with burst of 20).
102
-func New(ircAddr, password string, routes RouteConfig, ratePerSec float64, burst int, log *slog.Logger) *Bot {
103
+func New(ircAddr, password string, channels []string, routes RouteConfig, ratePerSec float64, burst int, log *slog.Logger) *Bot {
103104
if ratePerSec <= 0 {
104105
ratePerSec = 5
105106
}
106107
if burst <= 0 {
107108
burst = 20
108109
}
109110
return &Bot{
110111
ircAddr: ircAddr,
111112
password: password,
113
+ channels: channels,
112114
routes: routes,
113115
limiter: newRateLimiter(ratePerSec, burst),
114116
queue: make(chan Event, defaultQueueSize),
115117
log: log,
116118
}
@@ -148,13 +150,16 @@
148150
PingDelay: 30 * time.Second,
149151
PingTimeout: 30 * time.Second,
150152
SSL: false,
151153
})
152154
153
- c.Handlers.AddBg(girc.CONNECTED, func(_ *girc.Client, _ girc.Event) {
155
+ c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
156
+ for _, ch := range b.channels {
157
+ cl.Cmd.Join(ch)
158
+ }
154159
if b.log != nil {
155
- b.log.Info("herald connected")
160
+ b.log.Info("herald connected", "channels", b.channels)
156161
}
157162
})
158163
159164
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
160165
if ch := e.Last(); strings.HasPrefix(ch, "#") {
161166
--- internal/bots/herald/herald.go
+++ internal/bots/herald/herald.go
@@ -86,10 +86,11 @@
86
87 // Bot is the herald bot.
88 type Bot struct {
89 ircAddr string
90 password string
 
91 routes RouteConfig
92 limiter *RateLimiter
93 queue chan Event
94 log *slog.Logger
95 client *girc.Client
@@ -97,20 +98,21 @@
97
98 const defaultQueueSize = 256
99
100 // New creates a herald bot. ratePerSec and burst configure the token-bucket
101 // rate limiter (e.g. 5 messages/sec with burst of 20).
102 func New(ircAddr, password string, routes RouteConfig, ratePerSec float64, burst int, log *slog.Logger) *Bot {
103 if ratePerSec <= 0 {
104 ratePerSec = 5
105 }
106 if burst <= 0 {
107 burst = 20
108 }
109 return &Bot{
110 ircAddr: ircAddr,
111 password: password,
 
112 routes: routes,
113 limiter: newRateLimiter(ratePerSec, burst),
114 queue: make(chan Event, defaultQueueSize),
115 log: log,
116 }
@@ -148,13 +150,16 @@
148 PingDelay: 30 * time.Second,
149 PingTimeout: 30 * time.Second,
150 SSL: false,
151 })
152
153 c.Handlers.AddBg(girc.CONNECTED, func(_ *girc.Client, _ girc.Event) {
 
 
 
154 if b.log != nil {
155 b.log.Info("herald connected")
156 }
157 })
158
159 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
160 if ch := e.Last(); strings.HasPrefix(ch, "#") {
161
--- internal/bots/herald/herald.go
+++ internal/bots/herald/herald.go
@@ -86,10 +86,11 @@
86
87 // Bot is the herald bot.
88 type Bot struct {
89 ircAddr string
90 password string
91 channels []string
92 routes RouteConfig
93 limiter *RateLimiter
94 queue chan Event
95 log *slog.Logger
96 client *girc.Client
@@ -97,20 +98,21 @@
98
99 const defaultQueueSize = 256
100
101 // New creates a herald bot. ratePerSec and burst configure the token-bucket
102 // rate limiter (e.g. 5 messages/sec with burst of 20).
103 func New(ircAddr, password string, channels []string, routes RouteConfig, ratePerSec float64, burst int, log *slog.Logger) *Bot {
104 if ratePerSec <= 0 {
105 ratePerSec = 5
106 }
107 if burst <= 0 {
108 burst = 20
109 }
110 return &Bot{
111 ircAddr: ircAddr,
112 password: password,
113 channels: channels,
114 routes: routes,
115 limiter: newRateLimiter(ratePerSec, burst),
116 queue: make(chan Event, defaultQueueSize),
117 log: log,
118 }
@@ -148,13 +150,16 @@
150 PingDelay: 30 * time.Second,
151 PingTimeout: 30 * time.Second,
152 SSL: false,
153 })
154
155 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
156 for _, ch := range b.channels {
157 cl.Cmd.Join(ch)
158 }
159 if b.log != nil {
160 b.log.Info("herald connected", "channels", b.channels)
161 }
162 })
163
164 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
165 if ch := e.Last(); strings.HasPrefix(ch, "#") {
166
--- internal/bots/herald/herald_test.go
+++ internal/bots/herald/herald_test.go
@@ -6,11 +6,11 @@
66
77
"github.com/conflicthq/scuttlebot/internal/bots/herald"
88
)
99
1010
func newBot(routes herald.RouteConfig) *herald.Bot {
11
- return herald.New("localhost:6667", "pass", routes, 100, 100, nil)
11
+ return herald.New("localhost:6667", "pass", nil, routes, 100, 100, nil)
1212
}
1313
1414
func TestBotName(t *testing.T) {
1515
b := newBot(herald.RouteConfig{})
1616
if b.Name() != "herald" {
@@ -42,11 +42,11 @@
4242
"ci.build.": "#builds",
4343
"deploy.": "#deploys",
4444
},
4545
DefaultChannel: "#alerts",
4646
}
47
- b := herald.New("localhost:6667", "pass", routes, 5, 20, nil)
47
+ b := herald.New("localhost:6667", "pass", nil, routes, 5, 20, nil)
4848
if b == nil {
4949
t.Fatal("expected non-nil bot")
5050
}
5151
}
5252
5353
--- internal/bots/herald/herald_test.go
+++ internal/bots/herald/herald_test.go
@@ -6,11 +6,11 @@
6
7 "github.com/conflicthq/scuttlebot/internal/bots/herald"
8 )
9
10 func newBot(routes herald.RouteConfig) *herald.Bot {
11 return herald.New("localhost:6667", "pass", routes, 100, 100, nil)
12 }
13
14 func TestBotName(t *testing.T) {
15 b := newBot(herald.RouteConfig{})
16 if b.Name() != "herald" {
@@ -42,11 +42,11 @@
42 "ci.build.": "#builds",
43 "deploy.": "#deploys",
44 },
45 DefaultChannel: "#alerts",
46 }
47 b := herald.New("localhost:6667", "pass", routes, 5, 20, nil)
48 if b == nil {
49 t.Fatal("expected non-nil bot")
50 }
51 }
52
53
--- internal/bots/herald/herald_test.go
+++ internal/bots/herald/herald_test.go
@@ -6,11 +6,11 @@
6
7 "github.com/conflicthq/scuttlebot/internal/bots/herald"
8 )
9
10 func newBot(routes herald.RouteConfig) *herald.Bot {
11 return herald.New("localhost:6667", "pass", nil, routes, 100, 100, nil)
12 }
13
14 func TestBotName(t *testing.T) {
15 b := newBot(herald.RouteConfig{})
16 if b.Name() != "herald" {
@@ -42,11 +42,11 @@
42 "ci.build.": "#builds",
43 "deploy.": "#deploys",
44 },
45 DefaultChannel: "#alerts",
46 }
47 b := herald.New("localhost:6667", "pass", nil, routes, 5, 20, nil)
48 if b == nil {
49 t.Fatal("expected non-nil bot")
50 }
51 }
52
53
--- internal/bots/manager/manager.go
+++ internal/bots/manager/manager.go
@@ -233,26 +233,27 @@
233233
AlertNicks: splitCSV(cfgStr(cfg, "alert_nicks", "")),
234234
FloodMessages: cfgInt(cfg, "flood_messages", 10),
235235
FloodWindow: time.Duration(cfgInt(cfg, "flood_window_sec", 5)) * time.Second,
236236
JoinPartThreshold: cfgInt(cfg, "join_part_threshold", 5),
237237
JoinPartWindow: time.Duration(cfgInt(cfg, "join_part_window_sec", 30)) * time.Second,
238
+ Channels: channels,
238239
}, m.log), nil
239240
240241
case "warden":
241
- return warden.New(m.ircAddr, pass, nil, warden.ChannelConfig{
242
+ return warden.New(m.ircAddr, pass, channels, nil, warden.ChannelConfig{
242243
MessagesPerSecond: cfgFloat(cfg, "messages_per_second", 5),
243244
Burst: cfgInt(cfg, "burst", 10),
244245
}, m.log), nil
245246
246247
case "scroll":
247
- return scroll.New(m.ircAddr, pass, &scribe.MemoryStore{}, m.log), nil
248
+ return scroll.New(m.ircAddr, pass, channels, &scribe.MemoryStore{}, m.log), nil
248249
249250
case "systembot":
250251
return systembot.New(m.ircAddr, pass, channels, &systembot.MemoryStore{}, m.log), nil
251252
252253
case "herald":
253
- return herald.New(m.ircAddr, pass, herald.RouteConfig{
254
+ return herald.New(m.ircAddr, pass, channels, herald.RouteConfig{
254255
DefaultChannel: cfgStr(cfg, "default_channel", ""),
255256
}, cfgFloat(cfg, "rate_limit", 1), cfgInt(cfg, "burst", 5), m.log), nil
256257
257258
case "oracle":
258259
// Resolve API key — prefer direct api_key, fall back to api_key_env for
@@ -282,11 +283,11 @@
282283
// Read from the same dir scribe writes to.
283284
scribeDir := cfgStr(cfg, "scribe_dir", filepath.Join(m.dataDir, "logs", "scribe"))
284285
fs := scribe.NewFileStore(scribe.FileStoreConfig{Dir: scribeDir, Format: "jsonl"})
285286
history := &scribeHistoryAdapter{store: fs}
286287
287
- return oracle.New(m.ircAddr, pass, history, provider, m.log), nil
288
+ return oracle.New(m.ircAddr, pass, channels, history, provider, m.log), nil
288289
289290
case "sentinel":
290291
apiKey := cfgStr(cfg, "api_key", "")
291292
if apiKey == "" {
292293
if env := cfgStr(cfg, "api_key_env", ""); env != "" {
@@ -316,10 +317,11 @@
316317
Policy: cfgStr(cfg, "policy", ""),
317318
WindowSize: cfgInt(cfg, "window_size", 20),
318319
WindowAge: time.Duration(cfgInt(cfg, "window_age_sec", 300)) * time.Second,
319320
CooldownPerNick: time.Duration(cfgInt(cfg, "cooldown_sec", 600)) * time.Second,
320321
MinSeverity: cfgStr(cfg, "min_severity", "medium"),
322
+ Channels: channels,
321323
}, provider, m.log), nil
322324
323325
case "steward":
324326
return steward.New(steward.Config{
325327
IRCAddr: m.ircAddr,
@@ -330,10 +332,11 @@
330332
DMOnAction: cfgBool(cfg, "dm_on_action", false),
331333
AutoAct: cfgBool(cfg, "auto_act", true),
332334
MuteDuration: time.Duration(cfgInt(cfg, "mute_duration_sec", 600)) * time.Second,
333335
WarnOnLow: cfgBool(cfg, "warn_on_low", true),
334336
CooldownPerNick: time.Duration(cfgInt(cfg, "cooldown_sec", 300)) * time.Second,
337
+ Channels: channels,
335338
}, m.log), nil
336339
337340
default:
338341
return nil, fmt.Errorf("unknown bot ID %q", spec.ID)
339342
}
340343
--- internal/bots/manager/manager.go
+++ internal/bots/manager/manager.go
@@ -233,26 +233,27 @@
233 AlertNicks: splitCSV(cfgStr(cfg, "alert_nicks", "")),
234 FloodMessages: cfgInt(cfg, "flood_messages", 10),
235 FloodWindow: time.Duration(cfgInt(cfg, "flood_window_sec", 5)) * time.Second,
236 JoinPartThreshold: cfgInt(cfg, "join_part_threshold", 5),
237 JoinPartWindow: time.Duration(cfgInt(cfg, "join_part_window_sec", 30)) * time.Second,
 
238 }, m.log), nil
239
240 case "warden":
241 return warden.New(m.ircAddr, pass, nil, warden.ChannelConfig{
242 MessagesPerSecond: cfgFloat(cfg, "messages_per_second", 5),
243 Burst: cfgInt(cfg, "burst", 10),
244 }, m.log), nil
245
246 case "scroll":
247 return scroll.New(m.ircAddr, pass, &scribe.MemoryStore{}, m.log), nil
248
249 case "systembot":
250 return systembot.New(m.ircAddr, pass, channels, &systembot.MemoryStore{}, m.log), nil
251
252 case "herald":
253 return herald.New(m.ircAddr, pass, herald.RouteConfig{
254 DefaultChannel: cfgStr(cfg, "default_channel", ""),
255 }, cfgFloat(cfg, "rate_limit", 1), cfgInt(cfg, "burst", 5), m.log), nil
256
257 case "oracle":
258 // Resolve API key — prefer direct api_key, fall back to api_key_env for
@@ -282,11 +283,11 @@
282 // Read from the same dir scribe writes to.
283 scribeDir := cfgStr(cfg, "scribe_dir", filepath.Join(m.dataDir, "logs", "scribe"))
284 fs := scribe.NewFileStore(scribe.FileStoreConfig{Dir: scribeDir, Format: "jsonl"})
285 history := &scribeHistoryAdapter{store: fs}
286
287 return oracle.New(m.ircAddr, pass, history, provider, m.log), nil
288
289 case "sentinel":
290 apiKey := cfgStr(cfg, "api_key", "")
291 if apiKey == "" {
292 if env := cfgStr(cfg, "api_key_env", ""); env != "" {
@@ -316,10 +317,11 @@
316 Policy: cfgStr(cfg, "policy", ""),
317 WindowSize: cfgInt(cfg, "window_size", 20),
318 WindowAge: time.Duration(cfgInt(cfg, "window_age_sec", 300)) * time.Second,
319 CooldownPerNick: time.Duration(cfgInt(cfg, "cooldown_sec", 600)) * time.Second,
320 MinSeverity: cfgStr(cfg, "min_severity", "medium"),
 
321 }, provider, m.log), nil
322
323 case "steward":
324 return steward.New(steward.Config{
325 IRCAddr: m.ircAddr,
@@ -330,10 +332,11 @@
330 DMOnAction: cfgBool(cfg, "dm_on_action", false),
331 AutoAct: cfgBool(cfg, "auto_act", true),
332 MuteDuration: time.Duration(cfgInt(cfg, "mute_duration_sec", 600)) * time.Second,
333 WarnOnLow: cfgBool(cfg, "warn_on_low", true),
334 CooldownPerNick: time.Duration(cfgInt(cfg, "cooldown_sec", 300)) * time.Second,
 
335 }, m.log), nil
336
337 default:
338 return nil, fmt.Errorf("unknown bot ID %q", spec.ID)
339 }
340
--- internal/bots/manager/manager.go
+++ internal/bots/manager/manager.go
@@ -233,26 +233,27 @@
233 AlertNicks: splitCSV(cfgStr(cfg, "alert_nicks", "")),
234 FloodMessages: cfgInt(cfg, "flood_messages", 10),
235 FloodWindow: time.Duration(cfgInt(cfg, "flood_window_sec", 5)) * time.Second,
236 JoinPartThreshold: cfgInt(cfg, "join_part_threshold", 5),
237 JoinPartWindow: time.Duration(cfgInt(cfg, "join_part_window_sec", 30)) * time.Second,
238 Channels: channels,
239 }, m.log), nil
240
241 case "warden":
242 return warden.New(m.ircAddr, pass, channels, nil, warden.ChannelConfig{
243 MessagesPerSecond: cfgFloat(cfg, "messages_per_second", 5),
244 Burst: cfgInt(cfg, "burst", 10),
245 }, m.log), nil
246
247 case "scroll":
248 return scroll.New(m.ircAddr, pass, channels, &scribe.MemoryStore{}, m.log), nil
249
250 case "systembot":
251 return systembot.New(m.ircAddr, pass, channels, &systembot.MemoryStore{}, m.log), nil
252
253 case "herald":
254 return herald.New(m.ircAddr, pass, channels, herald.RouteConfig{
255 DefaultChannel: cfgStr(cfg, "default_channel", ""),
256 }, cfgFloat(cfg, "rate_limit", 1), cfgInt(cfg, "burst", 5), m.log), nil
257
258 case "oracle":
259 // Resolve API key — prefer direct api_key, fall back to api_key_env for
@@ -282,11 +283,11 @@
283 // Read from the same dir scribe writes to.
284 scribeDir := cfgStr(cfg, "scribe_dir", filepath.Join(m.dataDir, "logs", "scribe"))
285 fs := scribe.NewFileStore(scribe.FileStoreConfig{Dir: scribeDir, Format: "jsonl"})
286 history := &scribeHistoryAdapter{store: fs}
287
288 return oracle.New(m.ircAddr, pass, channels, history, provider, m.log), nil
289
290 case "sentinel":
291 apiKey := cfgStr(cfg, "api_key", "")
292 if apiKey == "" {
293 if env := cfgStr(cfg, "api_key_env", ""); env != "" {
@@ -316,10 +317,11 @@
317 Policy: cfgStr(cfg, "policy", ""),
318 WindowSize: cfgInt(cfg, "window_size", 20),
319 WindowAge: time.Duration(cfgInt(cfg, "window_age_sec", 300)) * time.Second,
320 CooldownPerNick: time.Duration(cfgInt(cfg, "cooldown_sec", 600)) * time.Second,
321 MinSeverity: cfgStr(cfg, "min_severity", "medium"),
322 Channels: channels,
323 }, provider, m.log), nil
324
325 case "steward":
326 return steward.New(steward.Config{
327 IRCAddr: m.ircAddr,
@@ -330,10 +332,11 @@
332 DMOnAction: cfgBool(cfg, "dm_on_action", false),
333 AutoAct: cfgBool(cfg, "auto_act", true),
334 MuteDuration: time.Duration(cfgInt(cfg, "mute_duration_sec", 600)) * time.Second,
335 WarnOnLow: cfgBool(cfg, "warn_on_low", true),
336 CooldownPerNick: time.Duration(cfgInt(cfg, "cooldown_sec", 300)) * time.Second,
337 Channels: channels,
338 }, m.log), nil
339
340 default:
341 return nil, fmt.Errorf("unknown bot ID %q", spec.ID)
342 }
343
--- internal/bots/oracle/oracle.go
+++ internal/bots/oracle/oracle.go
@@ -117,23 +117,25 @@
117117
118118
// Bot is the oracle bot.
119119
type Bot struct {
120120
ircAddr string
121121
password string
122
+ channels []string
122123
history HistoryFetcher
123124
llm LLMProvider
124125
log *slog.Logger
125126
mu sync.Mutex
126127
lastReq map[string]time.Time // nick → last request time
127128
client *girc.Client
128129
}
129130
130131
// New creates an oracle bot.
131
-func New(ircAddr, password string, history HistoryFetcher, llm LLMProvider, log *slog.Logger) *Bot {
132
+func New(ircAddr, password string, channels []string, history HistoryFetcher, llm LLMProvider, log *slog.Logger) *Bot {
132133
return &Bot{
133134
ircAddr: ircAddr,
134135
password: password,
136
+ channels: channels,
135137
history: history,
136138
llm: llm,
137139
log: log,
138140
lastReq: make(map[string]time.Time),
139141
}
@@ -159,13 +161,16 @@
159161
PingDelay: 30 * time.Second,
160162
PingTimeout: 30 * time.Second,
161163
SSL: false,
162164
})
163165
164
- c.Handlers.AddBg(girc.CONNECTED, func(_ *girc.Client, _ girc.Event) {
166
+ c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
167
+ for _, ch := range b.channels {
168
+ cl.Cmd.Join(ch)
169
+ }
165170
if b.log != nil {
166
- b.log.Info("oracle connected")
171
+ b.log.Info("oracle connected", "channels", b.channels)
167172
}
168173
})
169174
170175
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
171176
if ch := e.Last(); strings.HasPrefix(ch, "#") {
172177
--- internal/bots/oracle/oracle.go
+++ internal/bots/oracle/oracle.go
@@ -117,23 +117,25 @@
117
118 // Bot is the oracle bot.
119 type Bot struct {
120 ircAddr string
121 password string
 
122 history HistoryFetcher
123 llm LLMProvider
124 log *slog.Logger
125 mu sync.Mutex
126 lastReq map[string]time.Time // nick → last request time
127 client *girc.Client
128 }
129
130 // New creates an oracle bot.
131 func New(ircAddr, password string, history HistoryFetcher, llm LLMProvider, log *slog.Logger) *Bot {
132 return &Bot{
133 ircAddr: ircAddr,
134 password: password,
 
135 history: history,
136 llm: llm,
137 log: log,
138 lastReq: make(map[string]time.Time),
139 }
@@ -159,13 +161,16 @@
159 PingDelay: 30 * time.Second,
160 PingTimeout: 30 * time.Second,
161 SSL: false,
162 })
163
164 c.Handlers.AddBg(girc.CONNECTED, func(_ *girc.Client, _ girc.Event) {
 
 
 
165 if b.log != nil {
166 b.log.Info("oracle connected")
167 }
168 })
169
170 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
171 if ch := e.Last(); strings.HasPrefix(ch, "#") {
172
--- internal/bots/oracle/oracle.go
+++ internal/bots/oracle/oracle.go
@@ -117,23 +117,25 @@
117
118 // Bot is the oracle bot.
119 type Bot struct {
120 ircAddr string
121 password string
122 channels []string
123 history HistoryFetcher
124 llm LLMProvider
125 log *slog.Logger
126 mu sync.Mutex
127 lastReq map[string]time.Time // nick → last request time
128 client *girc.Client
129 }
130
131 // New creates an oracle bot.
132 func New(ircAddr, password string, channels []string, history HistoryFetcher, llm LLMProvider, log *slog.Logger) *Bot {
133 return &Bot{
134 ircAddr: ircAddr,
135 password: password,
136 channels: channels,
137 history: history,
138 llm: llm,
139 log: log,
140 lastReq: make(map[string]time.Time),
141 }
@@ -159,13 +161,16 @@
161 PingDelay: 30 * time.Second,
162 PingTimeout: 30 * time.Second,
163 SSL: false,
164 })
165
166 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
167 for _, ch := range b.channels {
168 cl.Cmd.Join(ch)
169 }
170 if b.log != nil {
171 b.log.Info("oracle connected", "channels", b.channels)
172 }
173 })
174
175 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
176 if ch := e.Last(); strings.HasPrefix(ch, "#") {
177
--- internal/bots/oracle/oracle_test.go
+++ internal/bots/oracle/oracle_test.go
@@ -91,11 +91,11 @@
9191
}
9292
9393
// --- Bot construction ---
9494
9595
func TestBotName(t *testing.T) {
96
- b := oracle.New("localhost:6667", "pass",
96
+ b := oracle.New("localhost:6667", "pass", nil,
9797
newHistory("#fleet", nil),
9898
&oracle.StubProvider{Response: "summary"},
9999
nil,
100100
)
101101
if b.Name() != "oracle" {
102102
--- internal/bots/oracle/oracle_test.go
+++ internal/bots/oracle/oracle_test.go
@@ -91,11 +91,11 @@
91 }
92
93 // --- Bot construction ---
94
95 func TestBotName(t *testing.T) {
96 b := oracle.New("localhost:6667", "pass",
97 newHistory("#fleet", nil),
98 &oracle.StubProvider{Response: "summary"},
99 nil,
100 )
101 if b.Name() != "oracle" {
102
--- internal/bots/oracle/oracle_test.go
+++ internal/bots/oracle/oracle_test.go
@@ -91,11 +91,11 @@
91 }
92
93 // --- Bot construction ---
94
95 func TestBotName(t *testing.T) {
96 b := oracle.New("localhost:6667", "pass", nil,
97 newHistory("#fleet", nil),
98 &oracle.StubProvider{Response: "summary"},
99 nil,
100 )
101 if b.Name() != "oracle" {
102
--- internal/bots/scroll/scroll.go
+++ internal/bots/scroll/scroll.go
@@ -34,21 +34,23 @@
3434
3535
// Bot is the scroll history-replay bot.
3636
type Bot struct {
3737
ircAddr string
3838
password string
39
+ channels []string
3940
store scribe.Store
4041
log *slog.Logger
4142
client *girc.Client
4243
rateLimit sync.Map // nick → last request time
4344
}
4445
4546
// New creates a scroll Bot backed by the given scribe Store.
46
-func New(ircAddr, password string, store scribe.Store, log *slog.Logger) *Bot {
47
+func New(ircAddr, password string, channels []string, store scribe.Store, log *slog.Logger) *Bot {
4748
return &Bot{
4849
ircAddr: ircAddr,
4950
password: password,
51
+ channels: channels,
5052
store: store,
5153
log: log,
5254
}
5355
}
5456
@@ -72,12 +74,15 @@
7274
PingDelay: 30 * time.Second,
7375
PingTimeout: 30 * time.Second,
7476
SSL: false,
7577
})
7678
77
- c.Handlers.AddBg(girc.CONNECTED, func(client *girc.Client, e girc.Event) {
78
- b.log.Info("scroll connected")
79
+ c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, e girc.Event) {
80
+ for _, ch := range b.channels {
81
+ cl.Cmd.Join(ch)
82
+ }
83
+ b.log.Info("scroll connected", "channels", b.channels)
7984
})
8085
8186
// Only respond to DMs — ignore anything in a channel.
8287
c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) {
8388
if len(e.Params) < 1 {
8489
--- internal/bots/scroll/scroll.go
+++ internal/bots/scroll/scroll.go
@@ -34,21 +34,23 @@
34
35 // Bot is the scroll history-replay bot.
36 type Bot struct {
37 ircAddr string
38 password string
 
39 store scribe.Store
40 log *slog.Logger
41 client *girc.Client
42 rateLimit sync.Map // nick → last request time
43 }
44
45 // New creates a scroll Bot backed by the given scribe Store.
46 func New(ircAddr, password string, store scribe.Store, log *slog.Logger) *Bot {
47 return &Bot{
48 ircAddr: ircAddr,
49 password: password,
 
50 store: store,
51 log: log,
52 }
53 }
54
@@ -72,12 +74,15 @@
72 PingDelay: 30 * time.Second,
73 PingTimeout: 30 * time.Second,
74 SSL: false,
75 })
76
77 c.Handlers.AddBg(girc.CONNECTED, func(client *girc.Client, e girc.Event) {
78 b.log.Info("scroll connected")
 
 
 
79 })
80
81 // Only respond to DMs — ignore anything in a channel.
82 c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) {
83 if len(e.Params) < 1 {
84
--- internal/bots/scroll/scroll.go
+++ internal/bots/scroll/scroll.go
@@ -34,21 +34,23 @@
34
35 // Bot is the scroll history-replay bot.
36 type Bot struct {
37 ircAddr string
38 password string
39 channels []string
40 store scribe.Store
41 log *slog.Logger
42 client *girc.Client
43 rateLimit sync.Map // nick → last request time
44 }
45
46 // New creates a scroll Bot backed by the given scribe Store.
47 func New(ircAddr, password string, channels []string, store scribe.Store, log *slog.Logger) *Bot {
48 return &Bot{
49 ircAddr: ircAddr,
50 password: password,
51 channels: channels,
52 store: store,
53 log: log,
54 }
55 }
56
@@ -72,12 +74,15 @@
74 PingDelay: 30 * time.Second,
75 PingTimeout: 30 * time.Second,
76 SSL: false,
77 })
78
79 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, e girc.Event) {
80 for _, ch := range b.channels {
81 cl.Cmd.Join(ch)
82 }
83 b.log.Info("scroll connected", "channels", b.channels)
84 })
85
86 // Only respond to DMs — ignore anything in a channel.
87 c.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, e girc.Event) {
88 if len(e.Params) < 1 {
89
--- internal/bots/sentinel/sentinel.go
+++ internal/bots/sentinel/sentinel.go
@@ -61,10 +61,13 @@
6161
// Default: 10 minutes.
6262
CooldownPerNick time.Duration
6363
// MinSeverity controls which severities trigger a report.
6464
// "low", "medium", "high" — default: "medium".
6565
MinSeverity string
66
+
67
+ // Channels is the list of channels to join on connect.
68
+ Channels []string
6669
}
6770
6871
func (c *Config) setDefaults() {
6972
if c.Nick == "" {
7073
c.Nick = defaultNick
@@ -143,14 +146,17 @@
143146
PingDelay: 30 * time.Second,
144147
PingTimeout: 30 * time.Second,
145148
})
146149
147150
c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
148
- if b.log != nil {
149
- b.log.Info("sentinel connected")
151
+ for _, ch := range b.cfg.Channels {
152
+ cl.Cmd.Join(ch)
150153
}
151154
cl.Cmd.Join(b.cfg.ModChannel)
155
+ if b.log != nil {
156
+ b.log.Info("sentinel connected", "channels", b.cfg.Channels)
157
+ }
152158
})
153159
154160
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
155161
if ch := e.Last(); strings.HasPrefix(ch, "#") {
156162
cl.Cmd.Join(ch)
157163
--- internal/bots/sentinel/sentinel.go
+++ internal/bots/sentinel/sentinel.go
@@ -61,10 +61,13 @@
61 // Default: 10 minutes.
62 CooldownPerNick time.Duration
63 // MinSeverity controls which severities trigger a report.
64 // "low", "medium", "high" — default: "medium".
65 MinSeverity string
 
 
 
66 }
67
68 func (c *Config) setDefaults() {
69 if c.Nick == "" {
70 c.Nick = defaultNick
@@ -143,14 +146,17 @@
143 PingDelay: 30 * time.Second,
144 PingTimeout: 30 * time.Second,
145 })
146
147 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
148 if b.log != nil {
149 b.log.Info("sentinel connected")
150 }
151 cl.Cmd.Join(b.cfg.ModChannel)
 
 
 
152 })
153
154 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
155 if ch := e.Last(); strings.HasPrefix(ch, "#") {
156 cl.Cmd.Join(ch)
157
--- internal/bots/sentinel/sentinel.go
+++ internal/bots/sentinel/sentinel.go
@@ -61,10 +61,13 @@
61 // Default: 10 minutes.
62 CooldownPerNick time.Duration
63 // MinSeverity controls which severities trigger a report.
64 // "low", "medium", "high" — default: "medium".
65 MinSeverity string
66
67 // Channels is the list of channels to join on connect.
68 Channels []string
69 }
70
71 func (c *Config) setDefaults() {
72 if c.Nick == "" {
73 c.Nick = defaultNick
@@ -143,14 +146,17 @@
146 PingDelay: 30 * time.Second,
147 PingTimeout: 30 * time.Second,
148 })
149
150 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
151 for _, ch := range b.cfg.Channels {
152 cl.Cmd.Join(ch)
153 }
154 cl.Cmd.Join(b.cfg.ModChannel)
155 if b.log != nil {
156 b.log.Info("sentinel connected", "channels", b.cfg.Channels)
157 }
158 })
159
160 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
161 if ch := e.Last(); strings.HasPrefix(ch, "#") {
162 cl.Cmd.Join(ch)
163
--- internal/bots/snitch/snitch.go
+++ internal/bots/snitch/snitch.go
@@ -45,10 +45,13 @@
4545
FloodWindow time.Duration
4646
// JoinPartThreshold is join+part events in JoinPartWindow to trigger alert. Default: 5.
4747
JoinPartThreshold int
4848
// JoinPartWindow is the rolling window for join/part cycling. Default: 30s.
4949
JoinPartWindow time.Duration
50
+
51
+ // Channels is the list of channels to join on connect.
52
+ Channels []string
5053
}
5154
5255
func (c *Config) setDefaults() {
5356
if c.Nick == "" {
5457
c.Nick = defaultNick
@@ -132,16 +135,19 @@
132135
PingDelay: 30 * time.Second,
133136
PingTimeout: 30 * time.Second,
134137
})
135138
136139
c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
137
- if b.log != nil {
138
- b.log.Info("snitch connected")
140
+ for _, ch := range b.cfg.Channels {
141
+ cl.Cmd.Join(ch)
139142
}
140143
if b.cfg.AlertChannel != "" {
141144
cl.Cmd.Join(b.cfg.AlertChannel)
142145
}
146
+ if b.log != nil {
147
+ b.log.Info("snitch connected", "channels", b.cfg.Channels)
148
+ }
143149
})
144150
145151
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
146152
if ch := e.Last(); strings.HasPrefix(ch, "#") {
147153
cl.Cmd.Join(ch)
148154
--- internal/bots/snitch/snitch.go
+++ internal/bots/snitch/snitch.go
@@ -45,10 +45,13 @@
45 FloodWindow time.Duration
46 // JoinPartThreshold is join+part events in JoinPartWindow to trigger alert. Default: 5.
47 JoinPartThreshold int
48 // JoinPartWindow is the rolling window for join/part cycling. Default: 30s.
49 JoinPartWindow time.Duration
 
 
 
50 }
51
52 func (c *Config) setDefaults() {
53 if c.Nick == "" {
54 c.Nick = defaultNick
@@ -132,16 +135,19 @@
132 PingDelay: 30 * time.Second,
133 PingTimeout: 30 * time.Second,
134 })
135
136 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
137 if b.log != nil {
138 b.log.Info("snitch connected")
139 }
140 if b.cfg.AlertChannel != "" {
141 cl.Cmd.Join(b.cfg.AlertChannel)
142 }
 
 
 
143 })
144
145 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
146 if ch := e.Last(); strings.HasPrefix(ch, "#") {
147 cl.Cmd.Join(ch)
148
--- internal/bots/snitch/snitch.go
+++ internal/bots/snitch/snitch.go
@@ -45,10 +45,13 @@
45 FloodWindow time.Duration
46 // JoinPartThreshold is join+part events in JoinPartWindow to trigger alert. Default: 5.
47 JoinPartThreshold int
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
@@ -132,16 +135,19 @@
135 PingDelay: 30 * time.Second,
136 PingTimeout: 30 * time.Second,
137 })
138
139 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
140 for _, ch := range b.cfg.Channels {
141 cl.Cmd.Join(ch)
142 }
143 if b.cfg.AlertChannel != "" {
144 cl.Cmd.Join(b.cfg.AlertChannel)
145 }
146 if b.log != nil {
147 b.log.Info("snitch connected", "channels", b.cfg.Channels)
148 }
149 })
150
151 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
152 if ch := e.Last(); strings.HasPrefix(ch, "#") {
153 cl.Cmd.Join(ch)
154
--- internal/bots/steward/steward.go
+++ internal/bots/steward/steward.go
@@ -65,10 +65,13 @@
6565
DMOnAction bool
6666
6767
// CooldownPerNick is the minimum time between automated actions on the
6868
// same nick. Default: 5 minutes.
6969
CooldownPerNick time.Duration
70
+
71
+ // Channels is the list of channels to join on connect.
72
+ Channels []string
7073
}
7174
7275
func (c *Config) setDefaults() {
7376
if c.Nick == "" {
7477
c.Nick = defaultNick
@@ -127,14 +130,17 @@
127130
PingDelay: 30 * time.Second,
128131
PingTimeout: 30 * time.Second,
129132
})
130133
131134
c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
132
- if b.log != nil {
133
- b.log.Info("steward connected")
135
+ for _, ch := range b.cfg.Channels {
136
+ cl.Cmd.Join(ch)
134137
}
135138
cl.Cmd.Join(b.cfg.ModChannel)
139
+ if b.log != nil {
140
+ b.log.Info("steward connected", "channels", b.cfg.Channels)
141
+ }
136142
})
137143
138144
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
139145
if ch := e.Last(); strings.HasPrefix(ch, "#") {
140146
cl.Cmd.Join(ch)
141147
--- internal/bots/steward/steward.go
+++ internal/bots/steward/steward.go
@@ -65,10 +65,13 @@
65 DMOnAction bool
66
67 // CooldownPerNick is the minimum time between automated actions on the
68 // same nick. Default: 5 minutes.
69 CooldownPerNick time.Duration
 
 
 
70 }
71
72 func (c *Config) setDefaults() {
73 if c.Nick == "" {
74 c.Nick = defaultNick
@@ -127,14 +130,17 @@
127 PingDelay: 30 * time.Second,
128 PingTimeout: 30 * time.Second,
129 })
130
131 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
132 if b.log != nil {
133 b.log.Info("steward connected")
134 }
135 cl.Cmd.Join(b.cfg.ModChannel)
 
 
 
136 })
137
138 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
139 if ch := e.Last(); strings.HasPrefix(ch, "#") {
140 cl.Cmd.Join(ch)
141
--- internal/bots/steward/steward.go
+++ internal/bots/steward/steward.go
@@ -65,10 +65,13 @@
65 DMOnAction bool
66
67 // CooldownPerNick is the minimum time between automated actions on the
68 // same nick. Default: 5 minutes.
69 CooldownPerNick time.Duration
70
71 // Channels is the list of channels to join on connect.
72 Channels []string
73 }
74
75 func (c *Config) setDefaults() {
76 if c.Nick == "" {
77 c.Nick = defaultNick
@@ -127,14 +130,17 @@
130 PingDelay: 30 * time.Second,
131 PingTimeout: 30 * time.Second,
132 })
133
134 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
135 for _, ch := range b.cfg.Channels {
136 cl.Cmd.Join(ch)
137 }
138 cl.Cmd.Join(b.cfg.ModChannel)
139 if b.log != nil {
140 b.log.Info("steward connected", "channels", b.cfg.Channels)
141 }
142 })
143
144 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
145 if ch := e.Last(); strings.HasPrefix(ch, "#") {
146 cl.Cmd.Join(ch)
147
--- internal/bots/warden/warden.go
+++ internal/bots/warden/warden.go
@@ -138,10 +138,11 @@
138138
139139
// Bot is the warden.
140140
type Bot struct {
141141
ircAddr string
142142
password string
143
+ initChannels []string // channels to join on connect
143144
channelConfigs map[string]ChannelConfig // keyed by channel name
144145
defaultConfig ChannelConfig
145146
mu sync.RWMutex
146147
channels map[string]*channelState
147148
log *slog.Logger
@@ -162,15 +163,16 @@
162163
Record(ActionRecord)
163164
}
164165
165166
// New creates a warden bot. channelConfigs overrides per-channel limits;
166167
// defaultConfig is used for channels not in the map.
167
-func New(ircAddr, password string, channelConfigs map[string]ChannelConfig, defaultConfig ChannelConfig, log *slog.Logger) *Bot {
168
+func New(ircAddr, password string, channels []string, channelConfigs map[string]ChannelConfig, defaultConfig ChannelConfig, log *slog.Logger) *Bot {
168169
defaultConfig.defaults()
169170
return &Bot{
170171
ircAddr: ircAddr,
171172
password: password,
173
+ initChannels: channels,
172174
channelConfigs: channelConfigs,
173175
defaultConfig: defaultConfig,
174176
channels: make(map[string]*channelState),
175177
log: log,
176178
}
@@ -197,16 +199,18 @@
197199
PingTimeout: 30 * time.Second,
198200
SSL: false,
199201
})
200202
201203
c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
202
- // Join all configured channels.
204
+ for _, ch := range b.initChannels {
205
+ cl.Cmd.Join(ch)
206
+ }
203207
for ch := range b.channelConfigs {
204208
cl.Cmd.Join(ch)
205209
}
206210
if b.log != nil {
207
- b.log.Info("warden connected")
211
+ b.log.Info("warden connected", "channels", b.initChannels)
208212
}
209213
})
210214
211215
c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
212216
if ch := e.Last(); strings.HasPrefix(ch, "#") {
213217
--- internal/bots/warden/warden.go
+++ internal/bots/warden/warden.go
@@ -138,10 +138,11 @@
138
139 // Bot is the warden.
140 type Bot struct {
141 ircAddr string
142 password string
 
143 channelConfigs map[string]ChannelConfig // keyed by channel name
144 defaultConfig ChannelConfig
145 mu sync.RWMutex
146 channels map[string]*channelState
147 log *slog.Logger
@@ -162,15 +163,16 @@
162 Record(ActionRecord)
163 }
164
165 // New creates a warden bot. channelConfigs overrides per-channel limits;
166 // defaultConfig is used for channels not in the map.
167 func New(ircAddr, password string, channelConfigs map[string]ChannelConfig, defaultConfig ChannelConfig, log *slog.Logger) *Bot {
168 defaultConfig.defaults()
169 return &Bot{
170 ircAddr: ircAddr,
171 password: password,
 
172 channelConfigs: channelConfigs,
173 defaultConfig: defaultConfig,
174 channels: make(map[string]*channelState),
175 log: log,
176 }
@@ -197,16 +199,18 @@
197 PingTimeout: 30 * time.Second,
198 SSL: false,
199 })
200
201 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
202 // Join all configured channels.
 
 
203 for ch := range b.channelConfigs {
204 cl.Cmd.Join(ch)
205 }
206 if b.log != nil {
207 b.log.Info("warden connected")
208 }
209 })
210
211 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
212 if ch := e.Last(); strings.HasPrefix(ch, "#") {
213
--- internal/bots/warden/warden.go
+++ internal/bots/warden/warden.go
@@ -138,10 +138,11 @@
138
139 // Bot is the warden.
140 type Bot struct {
141 ircAddr string
142 password string
143 initChannels []string // channels to join on connect
144 channelConfigs map[string]ChannelConfig // keyed by channel name
145 defaultConfig ChannelConfig
146 mu sync.RWMutex
147 channels map[string]*channelState
148 log *slog.Logger
@@ -162,15 +163,16 @@
163 Record(ActionRecord)
164 }
165
166 // New creates a warden bot. channelConfigs overrides per-channel limits;
167 // defaultConfig is used for channels not in the map.
168 func New(ircAddr, password string, channels []string, channelConfigs map[string]ChannelConfig, defaultConfig ChannelConfig, log *slog.Logger) *Bot {
169 defaultConfig.defaults()
170 return &Bot{
171 ircAddr: ircAddr,
172 password: password,
173 initChannels: channels,
174 channelConfigs: channelConfigs,
175 defaultConfig: defaultConfig,
176 channels: make(map[string]*channelState),
177 log: log,
178 }
@@ -197,16 +199,18 @@
199 PingTimeout: 30 * time.Second,
200 SSL: false,
201 })
202
203 c.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
204 for _, ch := range b.initChannels {
205 cl.Cmd.Join(ch)
206 }
207 for ch := range b.channelConfigs {
208 cl.Cmd.Join(ch)
209 }
210 if b.log != nil {
211 b.log.Info("warden connected", "channels", b.initChannels)
212 }
213 })
214
215 c.Handlers.AddBg(girc.INVITE, func(cl *girc.Client, e girc.Event) {
216 if ch := e.Last(); strings.HasPrefix(ch, "#") {
217
--- internal/bots/warden/warden_test.go
+++ internal/bots/warden/warden_test.go
@@ -6,11 +6,11 @@
66
77
"github.com/conflicthq/scuttlebot/internal/bots/warden"
88
)
99
1010
func newBot() *warden.Bot {
11
- return warden.New("localhost:6667", "pass",
11
+ return warden.New("localhost:6667", "pass", nil,
1212
map[string]warden.ChannelConfig{
1313
"#fleet": {MessagesPerSecond: 5, Burst: 10, CoolDown: 60 * time.Second},
1414
},
1515
warden.ChannelConfig{MessagesPerSecond: 2, Burst: 5},
1616
nil,
@@ -31,11 +31,11 @@
3131
}
3232
}
3333
3434
func TestChannelConfigDefaults(t *testing.T) {
3535
// Zero-value config should get sane defaults applied.
36
- b := warden.New("localhost:6667", "pass",
36
+ b := warden.New("localhost:6667", "pass", nil,
3737
nil,
3838
warden.ChannelConfig{}, // zero — should default
3939
nil,
4040
)
4141
if b == nil {
@@ -50,11 +50,11 @@
5050
cfg := warden.ChannelConfig{
5151
MessagesPerSecond: 10,
5252
Burst: 20,
5353
CoolDown: 30 * time.Second,
5454
}
55
- b := warden.New("localhost:6667", "pass",
55
+ b := warden.New("localhost:6667", "pass", nil,
5656
map[string]warden.ChannelConfig{"#fleet": cfg},
5757
warden.ChannelConfig{},
5858
nil,
5959
)
6060
if b == nil {
6161
--- internal/bots/warden/warden_test.go
+++ internal/bots/warden/warden_test.go
@@ -6,11 +6,11 @@
6
7 "github.com/conflicthq/scuttlebot/internal/bots/warden"
8 )
9
10 func newBot() *warden.Bot {
11 return warden.New("localhost:6667", "pass",
12 map[string]warden.ChannelConfig{
13 "#fleet": {MessagesPerSecond: 5, Burst: 10, CoolDown: 60 * time.Second},
14 },
15 warden.ChannelConfig{MessagesPerSecond: 2, Burst: 5},
16 nil,
@@ -31,11 +31,11 @@
31 }
32 }
33
34 func TestChannelConfigDefaults(t *testing.T) {
35 // Zero-value config should get sane defaults applied.
36 b := warden.New("localhost:6667", "pass",
37 nil,
38 warden.ChannelConfig{}, // zero — should default
39 nil,
40 )
41 if b == nil {
@@ -50,11 +50,11 @@
50 cfg := warden.ChannelConfig{
51 MessagesPerSecond: 10,
52 Burst: 20,
53 CoolDown: 30 * time.Second,
54 }
55 b := warden.New("localhost:6667", "pass",
56 map[string]warden.ChannelConfig{"#fleet": cfg},
57 warden.ChannelConfig{},
58 nil,
59 )
60 if b == nil {
61
--- internal/bots/warden/warden_test.go
+++ internal/bots/warden/warden_test.go
@@ -6,11 +6,11 @@
6
7 "github.com/conflicthq/scuttlebot/internal/bots/warden"
8 )
9
10 func newBot() *warden.Bot {
11 return warden.New("localhost:6667", "pass", nil,
12 map[string]warden.ChannelConfig{
13 "#fleet": {MessagesPerSecond: 5, Burst: 10, CoolDown: 60 * time.Second},
14 },
15 warden.ChannelConfig{MessagesPerSecond: 2, Burst: 5},
16 nil,
@@ -31,11 +31,11 @@
31 }
32 }
33
34 func TestChannelConfigDefaults(t *testing.T) {
35 // Zero-value config should get sane defaults applied.
36 b := warden.New("localhost:6667", "pass", nil,
37 nil,
38 warden.ChannelConfig{}, // zero — should default
39 nil,
40 )
41 if b == nil {
@@ -50,11 +50,11 @@
50 cfg := warden.ChannelConfig{
51 MessagesPerSecond: 10,
52 Burst: 20,
53 CoolDown: 30 * time.Second,
54 }
55 b := warden.New("localhost:6667", "pass", nil,
56 map[string]warden.ChannelConfig{"#fleet": cfg},
57 warden.ChannelConfig{},
58 nil,
59 )
60 if b == nil {
61

Keyboard Shortcuts

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