ScuttleBot

scuttlebot / internal / topology / policy.go
Blame History Raw 158 lines
1
package topology
2
3
import (
4
"context"
5
"strings"
6
"time"
7
8
"github.com/conflicthq/scuttlebot/internal/config"
9
)
10
11
// ChannelType is the resolved policy for a class of channels.
12
type ChannelType struct {
13
Name string
14
Prefix string
15
Autojoin []string
16
Modes []string
17
Supervision string
18
Ephemeral bool
19
TTL time.Duration
20
}
21
22
// EventType classifies a channel lifecycle event.
23
type EventType string
24
25
const (
26
EventCreated EventType = "created"
27
EventClosed EventType = "closed"
28
EventReopened EventType = "reopened"
29
EventTransferred EventType = "transferred"
30
)
31
32
// ChannelEvent is emitted on channel lifecycle transitions.
33
type ChannelEvent struct {
34
Type EventType
35
Channel string
36
By string // nick that triggered the event
37
Meta map[string]string // optional domain-specific metadata
38
}
39
40
// EventHook is implemented by anything that reacts to channel lifecycle events.
41
type EventHook interface {
42
OnChannelEvent(ctx context.Context, event ChannelEvent) error
43
}
44
45
// Policy is the domain-agnostic evaluation layer between topology config and
46
// the runtime (IRC, bot invites, API). It answers questions like:
47
//
48
// - What type is #task.gh-42?
49
// - Which bots should join #incident.p1?
50
// - Where should summaries from #feature.auth surface?
51
//
52
// Rules come entirely from config — the Policy itself contains no hardcoded
53
// domain knowledge.
54
type Policy struct {
55
staticChannels []config.StaticChannelConfig
56
types []ChannelType
57
}
58
59
// NewPolicy constructs a Policy from the topology section of the config.
60
func NewPolicy(cfg config.TopologyConfig) *Policy {
61
types := make([]ChannelType, 0, len(cfg.Types))
62
for _, t := range cfg.Types {
63
types = append(types, ChannelType{
64
Name: t.Name,
65
Prefix: t.Prefix,
66
Autojoin: append([]string(nil), t.Autojoin...),
67
Modes: append([]string(nil), t.Modes...),
68
Supervision: t.Supervision,
69
Ephemeral: t.Ephemeral,
70
TTL: t.TTL.Duration,
71
})
72
}
73
return &Policy{
74
staticChannels: append([]config.StaticChannelConfig(nil), cfg.Channels...),
75
types: types,
76
}
77
}
78
79
// Match returns the ChannelType for the given channel name by prefix, or nil
80
// if no type matches. Channel names are matched after stripping the leading #.
81
func (p *Policy) Match(channel string) *ChannelType {
82
slug := strings.TrimPrefix(channel, "#")
83
for i := range p.types {
84
if strings.HasPrefix(slug, p.types[i].Prefix) {
85
return &p.types[i]
86
}
87
}
88
return nil
89
}
90
91
// AutojoinFor returns the bot nicks that should join channel.
92
// For dynamic channels this comes from the matching ChannelType.
93
// For static channels it comes from the StaticChannelConfig.
94
// Returns nil if no rule matches.
95
func (p *Policy) AutojoinFor(channel string) []string {
96
// Check static channels first (exact match).
97
for _, sc := range p.staticChannels {
98
if strings.EqualFold(sc.Name, channel) {
99
return append([]string(nil), sc.Autojoin...)
100
}
101
}
102
if t := p.Match(channel); t != nil {
103
return append([]string(nil), t.Autojoin...)
104
}
105
return nil
106
}
107
108
// SupervisionFor returns the coordination/supervision channel for the given
109
// channel, or an empty string if none is configured for its type.
110
func (p *Policy) SupervisionFor(channel string) string {
111
if t := p.Match(channel); t != nil {
112
return t.Supervision
113
}
114
return ""
115
}
116
117
// TypeName returns the type name for the given channel, or "unknown".
118
func (p *Policy) TypeName(channel string) string {
119
if t := p.Match(channel); t != nil {
120
return t.Name
121
}
122
return ""
123
}
124
125
// IsEphemeral reports whether channels of the matched type are ephemeral.
126
func (p *Policy) IsEphemeral(channel string) bool {
127
if t := p.Match(channel); t != nil {
128
return t.Ephemeral
129
}
130
return false
131
}
132
133
// TTLFor returns the TTL for the matched channel type, or zero if none.
134
func (p *Policy) TTLFor(channel string) time.Duration {
135
if t := p.Match(channel); t != nil {
136
return t.TTL
137
}
138
return 0
139
}
140
141
// ModesFor returns the channel modes for the given channel, or nil.
142
func (p *Policy) ModesFor(channel string) []string {
143
if t := p.Match(channel); t != nil {
144
return append([]string(nil), t.Modes...)
145
}
146
return nil
147
}
148
149
// StaticChannels returns the list of channels to provision at startup.
150
func (p *Policy) StaticChannels() []config.StaticChannelConfig {
151
return append([]config.StaticChannelConfig(nil), p.staticChannels...)
152
}
153
154
// Types returns all registered channel types.
155
func (p *Policy) Types() []ChannelType {
156
return append([]ChannelType(nil), p.types...)
157
}
158

Keyboard Shortcuts

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