-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
273 lines (234 loc) · 7.43 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package main
import (
"flag"
"log"
"os"
"os/signal"
"strings"
"sync"
"time"
"github.com/nolanlum/tanya/gateway"
"github.com/nolanlum/tanya/irc"
)
var configPathFlag = flag.String("config", "config.toml", "path to config file")
var noGenFlag = flag.Bool("no-generate", false, "disables auto-generation of config files")
var debugFlag = flag.Bool("debug", false, "toggles Slack library debug mode (logs to stdout)")
func killHandler(sigChan <-chan os.Signal, stopChan chan<- interface{}) {
<-sigChan
log.Println("tanya shutting down, stopping connections and goroutines")
close(stopChan)
}
func slackUserToIRCUser(s *gateway.SlackUser) irc.User {
return irc.User{
Nick: s.Nick,
Ident: s.SlackID,
Host: "localhost",
RealName: s.RealName,
}
}
func slackToPrivmsg(m *gateway.MessageEventData) *irc.Privmsg {
return &irc.Privmsg{
From: slackUserToIRCUser(&m.From),
Target: m.Target,
Message: m.Message,
}
}
func slackToNick(n *gateway.NickChangeEventData) *irc.Nick {
return &irc.Nick{
From: slackUserToIRCUser(&n.From),
NewNick: n.NewNick,
}
}
func slackToTopic(t *gateway.TopicChangeEventData) *irc.Topic {
return &irc.Topic{
From: slackUserToIRCUser(&t.From),
Channel: t.Target,
Topic: t.NewTopic,
}
}
func slackToJoin(j *gateway.JoinPartEventData) *irc.Join {
return &irc.Join{
User: slackUserToIRCUser(&j.User),
Channel: j.Target,
}
}
func slackToPart(j *gateway.JoinPartEventData) *irc.Part {
return &irc.Part{
User: slackUserToIRCUser(&j.User),
Channel: j.Target,
Message: "Leaving",
}
}
// this name specially chosen to trigger ATRAN
type corpusCallosum struct {
sc *gateway.SlackClient
}
// GetChannelUsers implements irc.ServerStateProvider.GetChannelUsers
func (c *corpusCallosum) GetChannelUsers(channelName string) []irc.User {
channel := c.sc.ResolveNameToChannel(channelName)
if channel == nil {
log.Printf("%s error while querying user list for %v: channel_not_found", c.sc.Tag(), channelName)
return nil
}
channelUsers, err := c.sc.GetChannelUsers(channel.SlackID)
if err != nil {
log.Printf("%s error while querying user list for %v: %v", c.sc.Tag(), channelName, err)
return nil
}
var users []irc.User
for _, user := range channelUsers {
users = append(users, slackUserToIRCUser(&user))
}
return users
}
// GetChannelTopic implements irc.ServerStateProvider.GetChannelTopic
func (c *corpusCallosum) GetChannelTopic(channelName string) (topic irc.ChannelTopic) {
channel := c.sc.ResolveNameToChannel(channelName)
if channel == nil {
log.Printf("%s error while querying topic for %v: channel_not_found", c.sc.Tag(), channelName)
return
}
topic.Topic = c.sc.ParseMessageText(channel.Topic.Value)
topic.Topic = strings.Replace(topic.Topic, "\n", " ", -1)
topic.SetAt = channel.Topic.LastSet.Time()
if channel.Topic.Creator != "" {
setBy, err := c.sc.ResolveUser(channel.Topic.Creator)
if err != nil {
log.Printf("%s error while querying topic creator for %v: %v", c.sc.Tag(), channelName, err)
return
}
topic.SetBy = setBy.Nick
}
return
}
// GetChannelCTime implements irc.ServerStateProvider.GetChannelCTime
func (c *corpusCallosum) GetChannelCTime(channelName string) time.Time {
channel := c.sc.ResolveNameToChannel(channelName)
if channel == nil {
log.Printf("%s error while querying ctime for %v: channel_not_found", c.sc.Tag(), channelName)
return time.Time{}
}
return channel.Created
}
// GetChannelPrivate implements irc.ServerStateProvider.GetChannelPrivate
func (c *corpusCallosum) GetChannelPrivate(channelName string) bool {
channel := c.sc.ResolveNameToChannel(channelName)
if channel == nil {
log.Printf("%s error while querying private flag for %v: channel_not_found", c.sc.Tag(), channelName)
return false
}
return channel.Private
}
// GetJoinedChannels implements irc.ServerStateProvider.GetJoinedChannels
func (c *corpusCallosum) GetJoinedChannels() []string {
var channelNames []string
for _, channel := range c.sc.GetChannelMemberships() {
channelNames = append(channelNames, channel.Name)
}
return channelNames
}
// SendPrivmsg sends an IRC PRIVMSG through Slack resolving channels properly
func (c *corpusCallosum) SendPrivmsg(privMsg *irc.Privmsg) (err error) {
// TODO: we should enforce that we are not sending PRIVMSGs from other people
// Don't bother sending anything on an empty message
if len(privMsg.Message) == 0 {
return
}
if privMsg.IsTargetChannel() {
channel := c.sc.ResolveNameToChannel(privMsg.Target)
err = c.sc.SendMessage(channel, privMsg.Message)
} else if privMsg.IsValidTarget() {
slackUser := c.sc.ResolveNickToUser(privMsg.Target)
if slackUser != nil {
err = c.sc.SendDirectMessage(slackUser, privMsg.Message)
}
}
return
}
func (c *corpusCallosum) GetUserFromNick(nick string) irc.User {
slackUser := c.sc.ResolveNickToUser(nick)
if slackUser != nil {
return slackUserToIRCUser(slackUser)
}
return irc.User{}
}
func writeMessageLoop(
recvChan <-chan *gateway.SlackEvent,
sendChan chan<- *irc.Message,
stopChan <-chan interface{},
server *irc.Server,
) {
Loop:
for {
select {
case <-stopChan:
break Loop
case msg := <-recvChan:
switch msg.EventType {
case gateway.SlackConnectedEvent:
// This is a state-changing event. Not 100% sure the main goroutine
// should be handling it but it doesn't make sense to have a separate
// server goroutine just for reconnected events, nor does it make sense
// to multiplex it onto sendChan.
b := msg.Data.(*gateway.SlackConnectedEventData)
server.HandleConnectBurst(slackUserToIRCUser(b.UserInfo))
case gateway.MessageEvent:
p := slackToPrivmsg(msg.Data.(*gateway.MessageEventData))
sendChan <- p.ToMessage()
case gateway.NickChangeEvent:
n := slackToNick(msg.Data.(*gateway.NickChangeEventData))
sendChan <- n.ToMessage()
case gateway.TopicChangeEvent:
t := slackToTopic(msg.Data.(*gateway.TopicChangeEventData))
sendChan <- t.ToMessage()
case gateway.SelfJoinEvent:
server.HandleChannelJoined(msg.Data.(*gateway.JoinPartEventData).Target)
case gateway.JoinEvent:
j := slackToJoin(msg.Data.(*gateway.JoinPartEventData))
sendChan <- j.ToMessage()
case gateway.PartEvent:
p := slackToPart(msg.Data.(*gateway.JoinPartEventData))
sendChan <- p.ToMessage()
}
}
}
}
func launchGateway(conf *GatewayInstance, stopChan chan interface{}) {
slackIncomingChan := make(chan *gateway.SlackEvent)
slackClient := gateway.NewSlackClient()
slackClient.Initialize(conf.Slack.Token, *debugFlag)
go slackClient.Poop(&gateway.ClientChans{
IncomingChan: slackIncomingChan,
StopChan: stopChan,
})
ircOutgoingChan := make(chan *irc.Message)
ircStateProvider := &corpusCallosum{slackClient}
ircServer := irc.NewServer(&conf.IRC, stopChan, ircStateProvider)
go ircServer.Listen()
go ircServer.HandleOutgoingMessageRouting(ircOutgoingChan)
writeMessageLoop(slackIncomingChan, ircOutgoingChan, stopChan, ircServer)
}
func main() {
flag.Parse()
conf, err := LoadConfig(*configPathFlag, *noGenFlag)
if err != nil {
log.Fatal(err)
}
// Stop channel
stopChan := make(chan interface{})
// Setup our stop handling
killSignalChan := make(chan os.Signal, 1)
go killHandler(killSignalChan, stopChan)
signal.Notify(killSignalChan, os.Interrupt)
log.Println("starting tanya")
var wg sync.WaitGroup
wg.Add(len(conf.Gateway))
for _, g := range conf.Gateway {
go func(g GatewayInstance) {
launchGateway(&g, stopChan)
wg.Done()
}(g)
}
wg.Wait()
log.Println("goodbye!")
}