| | @@ -24,18 +24,21 @@ |
| 24 | 24 | Name string |
| 25 | 25 | |
| 26 | 26 | // Topic is the initial channel topic (shared state header). |
| 27 | 27 | Topic string |
| 28 | 28 | |
| 29 | | - // Ops is a list of nicks to grant +o (channel operator) status. |
| 29 | + // Ops is a list of nicks to grant +o (channel operator) status via AMODE. |
| 30 | 30 | Ops []string |
| 31 | 31 | |
| 32 | | - // Voice is a list of nicks to grant +v status. |
| 32 | + // Voice is a list of nicks to grant +v status via AMODE. |
| 33 | 33 | Voice []string |
| 34 | 34 | |
| 35 | 35 | // Autojoin is a list of bot nicks to invite after provisioning. |
| 36 | 36 | Autojoin []string |
| 37 | + |
| 38 | + // Modes is a list of channel modes to set (e.g. "+m" for moderated). |
| 39 | + Modes []string |
| 37 | 40 | } |
| 38 | 41 | |
| 39 | 42 | // channelRecord tracks a provisioned channel for TTL-based reaping. |
| 40 | 43 | type channelRecord struct { |
| 41 | 44 | name string |
| | @@ -207,15 +210,21 @@ |
| 207 | 210 | |
| 208 | 211 | if ch.Topic != "" { |
| 209 | 212 | m.chanserv("TOPIC %s %s", ch.Name, ch.Topic) |
| 210 | 213 | } |
| 211 | 214 | |
| 215 | + // Use AMODE for persistent auto-mode on join (survives reconnects). |
| 212 | 216 | for _, nick := range ch.Ops { |
| 213 | | - m.chanserv("ACCESS %s ADD %s OP", ch.Name, nick) |
| 217 | + m.chanserv("AMODE %s +o %s", ch.Name, nick) |
| 214 | 218 | } |
| 215 | 219 | for _, nick := range ch.Voice { |
| 216 | | - m.chanserv("ACCESS %s ADD %s VOICE", ch.Name, nick) |
| 220 | + m.chanserv("AMODE %s +v %s", ch.Name, nick) |
| 221 | + } |
| 222 | + |
| 223 | + // Apply channel modes (e.g. +m for moderated). |
| 224 | + for _, mode := range ch.Modes { |
| 225 | + m.client.Cmd.Mode(ch.Name, mode) |
| 217 | 226 | } |
| 218 | 227 | |
| 219 | 228 | if len(ch.Autojoin) > 0 { |
| 220 | 229 | m.Invite(ch.Name, ch.Autojoin) |
| 221 | 230 | } |
| | @@ -274,27 +283,37 @@ |
| 274 | 283 | m.log.Info("reaping expired ephemeral channel", "channel", rec.name, "age", now.Sub(rec.provisionedAt).Round(time.Minute)) |
| 275 | 284 | m.DropChannel(rec.name) |
| 276 | 285 | } |
| 277 | 286 | } |
| 278 | 287 | |
| 279 | | -// GrantAccess sets a ChanServ ACCESS entry for nick on the given channel. |
| 280 | | -// level is "OP" or "VOICE". If level is empty, no access is granted. |
| 288 | +// GrantAccess sets a ChanServ AMODE entry for nick on the given channel. |
| 289 | +// level is "OP" or "VOICE". AMODE persists across reconnects — ChanServ |
| 290 | +// automatically applies the mode every time the nick joins. |
| 281 | 291 | func (m *Manager) GrantAccess(nick, channel, level string) { |
| 282 | 292 | if m.client == nil || level == "" { |
| 283 | 293 | return |
| 284 | 294 | } |
| 285 | | - m.chanserv("ACCESS %s ADD %s %s", channel, nick, level) |
| 286 | | - m.log.Info("granted channel access", "nick", nick, "channel", channel, "level", level) |
| 295 | + switch strings.ToUpper(level) { |
| 296 | + case "OP": |
| 297 | + m.chanserv("AMODE %s +o %s", channel, nick) |
| 298 | + case "VOICE": |
| 299 | + m.chanserv("AMODE %s +v %s", channel, nick) |
| 300 | + default: |
| 301 | + m.log.Warn("unknown access level", "level", level) |
| 302 | + return |
| 303 | + } |
| 304 | + m.log.Info("granted channel access (AMODE)", "nick", nick, "channel", channel, "level", level) |
| 287 | 305 | } |
| 288 | 306 | |
| 289 | | -// RevokeAccess removes a ChanServ ACCESS entry for nick on the given channel. |
| 307 | +// RevokeAccess removes ChanServ AMODE entries for nick on the given channel. |
| 290 | 308 | func (m *Manager) RevokeAccess(nick, channel string) { |
| 291 | 309 | if m.client == nil { |
| 292 | 310 | return |
| 293 | 311 | } |
| 294 | | - m.chanserv("ACCESS %s DEL %s", channel, nick) |
| 295 | | - m.log.Info("revoked channel access", "nick", nick, "channel", channel) |
| 312 | + m.chanserv("AMODE %s -o %s", channel, nick) |
| 313 | + m.chanserv("AMODE %s -v %s", channel, nick) |
| 314 | + m.log.Info("revoked channel access (AMODE)", "nick", nick, "channel", channel) |
| 296 | 315 | } |
| 297 | 316 | |
| 298 | 317 | func (m *Manager) chanserv(format string, args ...any) { |
| 299 | 318 | msg := fmt.Sprintf(format, args...) |
| 300 | 319 | m.client.Cmd.Message("ChanServ", msg) |
| 301 | 320 | |