ScuttleBot

scuttlebot / pkg / sessionrelay / sessionrelay.go
Blame History Raw 150 lines
1
package sessionrelay
2
3
import (
4
"context"
5
"encoding/json"
6
"fmt"
7
"net/http"
8
"strings"
9
"time"
10
)
11
12
const (
13
defaultRequestTimeout = 3 * time.Second
14
defaultBufferSize = 512
15
)
16
17
type Transport string
18
19
const (
20
TransportHTTP Transport = "http"
21
TransportIRC Transport = "irc"
22
)
23
24
type Config struct {
25
Transport Transport
26
URL string
27
Token string
28
Channel string
29
Channels []string
30
Nick string
31
HTTPClient *http.Client
32
IRC IRCConfig
33
}
34
35
type IRCConfig struct {
36
Addr string
37
Pass string
38
AgentType string
39
DeleteOnClose bool
40
// EnvelopeMode wraps outgoing messages in protocol.Envelope JSON.
41
// When true, agents in the channel can parse relay output as structured data.
42
EnvelopeMode bool
43
}
44
45
type Message struct {
46
At time.Time
47
Channel string
48
Nick string
49
Text string
50
MsgID string
51
}
52
53
type Connector interface {
54
Connect(ctx context.Context) error
55
Post(ctx context.Context, text string) error
56
PostTo(ctx context.Context, channel, text string) error
57
PostWithMeta(ctx context.Context, text string, meta json.RawMessage) error
58
PostToWithMeta(ctx context.Context, channel, text string, meta json.RawMessage) error
59
MessagesSince(ctx context.Context, since time.Time) ([]Message, error)
60
Touch(ctx context.Context) error
61
JoinChannel(ctx context.Context, channel string) error
62
PartChannel(ctx context.Context, channel string) error
63
Channels() []string
64
ControlChannel() string
65
Close(ctx context.Context) error
66
}
67
68
func New(cfg Config) (Connector, error) {
69
cfg = withDefaults(cfg)
70
if err := validateBaseConfig(cfg); err != nil {
71
return nil, err
72
}
73
74
switch cfg.Transport {
75
case TransportHTTP:
76
return newHTTPConnector(cfg), nil
77
case TransportIRC:
78
return newIRCConnector(cfg)
79
default:
80
return nil, fmt.Errorf("sessionrelay: unsupported transport %q", cfg.Transport)
81
}
82
}
83
84
func withDefaults(cfg Config) Config {
85
if cfg.Transport == "" {
86
cfg.Transport = TransportHTTP
87
}
88
if cfg.HTTPClient == nil {
89
cfg.HTTPClient = &http.Client{Timeout: defaultRequestTimeout}
90
}
91
if cfg.IRC.AgentType == "" {
92
cfg.IRC.AgentType = "worker"
93
}
94
cfg.Channel = normalizeChannel(cfg.Channel)
95
cfg.Channels = normalizeChannels(cfg.Channel, cfg.Channels)
96
if cfg.Channel == "" && len(cfg.Channels) > 0 {
97
cfg.Channel = cfg.Channels[0]
98
}
99
cfg.Transport = Transport(strings.ToLower(string(cfg.Transport)))
100
return cfg
101
}
102
103
func validateBaseConfig(cfg Config) error {
104
if cfg.Channel == "" || len(cfg.Channels) == 0 {
105
return fmt.Errorf("sessionrelay: channel is required")
106
}
107
if cfg.Nick == "" {
108
return fmt.Errorf("sessionrelay: nick is required")
109
}
110
return nil
111
}
112
113
func normalizeChannel(channel string) string {
114
channel = strings.TrimSpace(channel)
115
if channel == "" {
116
return ""
117
}
118
if strings.HasPrefix(channel, "#") {
119
return channel
120
}
121
return "#" + channel
122
}
123
124
func channelSlug(channel string) string {
125
return strings.TrimPrefix(normalizeChannel(channel), "#")
126
}
127
128
func normalizeChannels(primary string, channels []string) []string {
129
seen := make(map[string]struct{}, len(channels)+1)
130
out := make([]string, 0, len(channels)+1)
131
132
add := func(channel string) {
133
channel = normalizeChannel(channel)
134
if channel == "" {
135
return
136
}
137
if _, ok := seen[channel]; ok {
138
return
139
}
140
seen[channel] = struct{}{}
141
out = append(out, channel)
142
}
143
144
add(primary)
145
for _, channel := range channels {
146
add(channel)
147
}
148
return out
149
}
150

Keyboard Shortcuts

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