Skip to content

Instantly share code, notes, and snippets.

@zeisss
Created July 16, 2016 23:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeisss/5943fcadca3f992ac4f883eb58fcf4e9 to your computer and use it in GitHub Desktop.
Save zeisss/5943fcadca3f992ac4f883eb58fcf4e9 to your computer and use it in GitHub Desktop.
FROM golang:1.6
RUN go get github.com/nlopes/slack
COPY main.go /go/src/main.go
CMD ["go", "run", "/go/src/main.go"]
package main
import (
"github.com/nlopes/slack"
"syscall"
"os/signal"
"fmt"
"log"
"strings"
"os"
)
type CommunityConfig struct {
Comment string
BotToken string
ChannelName string
}
type Message struct {
Text string
Username string
}
// FindChannelID resolves the given channelName to either a real channel or a group/private channel
func FindChannelID(api *slack.Client, channelName string) (string, error) {
channels, err := api.GetChannels(true)
if err != nil {
return "", err
}
for _, c := range channels {
if c.Name == channelName {
return c.ID, nil
}
}
groups, err := api.GetGroups(true)
if err != nil {
return "", err
}
for _, g := range groups {
if g.Name == channelName {
return g.ID, nil
}
}
return "", fmt.Errorf("No channel found")
}
func Listen(cfg CommunityConfig, broadcast chan<- Message, publish <-chan Message) {
api := slack.New(cfg.BotToken)
channelID, err := FindChannelID(api, cfg.ChannelName)
if err != nil {
panic("Failed to read channel '" + cfg.ChannelName + "': " + err.Error())
}
fmt.Printf("%s: %s => %#v\n", cfg.Comment, cfg.ChannelName, channelID)
rtm := api.NewRTM()
go rtm.ManageConnection()
Loop:
for {
select {
case outgoing := <- publish:
fmt.Printf("%s: Sending %s\n", cfg.Comment, outgoing.Text)
api.PostMessage(
channelID,
outgoing.Text,
slack.PostMessageParameters{
Username: outgoing.Username,
UnfurlMedia: true,
UnfurlLinks: true,
},
)
case msg := <-rtm.IncomingEvents:
switch ev := msg.Data.(type) {
case *slack.HelloEvent, *slack.ReconnectUrlEvent, *slack.PresenceChangeEvent,
*slack.ConnectedEvent, *slack.ConnectingEvent, *slack.LatencyReport,
*slack.UserTypingEvent, *slack.ChannelCreatedEvent,
*slack.ChannelJoinedEvent:
case *slack.MessageEvent:
if ev.Channel != channelID { // we only want to listen to the specified channels
continue
}
if ev.User == "" { // we only want to forward real user message
continue
}
// rtm.SendMessage(rtm.NewOutgoingMessage(ev.Text, ev.Channel))
user := rtm.GetInfo().GetUserByID(ev.User)
if user.IsBot {
continue
}
fmt.Printf("Message: %#v\n", ev)
fmt.Printf("user: %#v\n", user)
broadcast <- Message{
Text: ev.Text,
Username: user.Name,
}
case *slack.RTMError:
fmt.Printf("Error: %s\n", ev.Error())
case *slack.InvalidAuthEvent:
fmt.Printf("Invalid credentials")
break Loop
default:
// Ignore other events..
fmt.Printf("Unhandled: %#v\n", msg.Data)
}
}
}
}
func RunSlackBouncer(configs []CommunityConfig) {
publishers := make([]chan Message, len(configs))
for id, comm := range configs {
broadcast := make(chan Message, 1) // The listener will write messages in here for the other channels
publisher := make(chan Message, 1) // The listener will read from here for publishing messages to his channel
publishers[id] = publisher
go Listen(comm, broadcast, publisher)
go func(id int, broadcast <-chan Message) {
for message := range broadcast {
// redistribute the message to all other channels
for idx, publisher := range publishers {
if idx == id {
continue
}
publisher <- message
}
}
}(id, broadcast)
}
}
func main() {
logger := log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags)
slack.SetLogger(logger)
communities := []CommunityConfig{}
for _, arg := range os.Args[1:] {
s := strings.Split(arg, ":")
if len(s) != 3 {
panic("Unexpected argument format, expected 'name:token:channel': " + arg)
}
communities = append(communities, CommunityConfig{
Comment: s[0],
BotToken: s[1],
ChannelName: s[2],
})
}
if len(communities) <= 1 {
panic("Expected at least 2 arguments.")
}
RunSlackBouncer(communities)
sigc := make(chan os.Signal, 1)
signal.Notify(sigc,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
<-sigc
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment