ScuttleBot

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

Keyboard Shortcuts

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