ScuttleBot

Merge pull request #130 from ConflictHQ/fix/71-bot-channel-join fix: all system bots join channels on connect

noreply 2026-04-04 19:45 trunk merge
Commit 2bdb8bbe4af0139d0acf5e3fc0edf8f63ea76c4ee18e499f1b05718148e2718a
--- 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