Permalink
Browse files

Add a github commit notification bot.

  • Loading branch information...
1 parent 7edd5bb commit 5c9fdf3f4c131b6eb8ed4ab51a0a03e35f5d28d5 @cespare committed Jan 10, 2013
Showing with 162 additions and 3 deletions.
  1. +7 −0 deploy.sh
  2. +8 −3 pratbot.go
  3. +1 −0 src/bot/bot.go
  4. +133 −0 src/bot/commit.go
  5. +1 −0 src/bot/echo.go
  6. +9 −0 src/connection/connection.go
  7. +3 −0 src/dispatcher/dispatcher.go
View
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -e
+
+glp build
+scp pratbot pratbot.ctrl-c.us:~/servers/pratbot
+rm pratbot
View
@@ -26,9 +26,12 @@ var (
type newBotFunc func(*connection.Conn) bot.Bot
var (
- botNameToFunc = map[string]newBotFunc{"echo": bot.NewEcho}
- bots = make(map[string]newBotFunc)
- disp = dispatcher.New()
+ botNameToFunc = map[string]newBotFunc{
+ "echo": bot.NewEcho,
+ "commit": bot.NewCommit,
+ }
+ bots = make(map[string]newBotFunc)
+ disp = dispatcher.New()
)
func init() {
@@ -83,6 +86,8 @@ func main() {
disp.Register(f(conn))
}
+ log.Println("Bots started.")
+
// Send 'connected' message
connectedMsg := &bot.Event{
Type: bot.EventConnect,
View
@@ -3,6 +3,7 @@ package bot
type EventType int
const (
+ _ = iota // So that the uninitialized message isn't accidentally valid
EventConnect EventType = iota
EventPublishMessage
)
View
@@ -0,0 +1,133 @@
+package bot
+
+// A bot that listens for github commit notifications and posts them to channels.
+
+import (
+ "bytes"
+ "connection"
+ "encoding/json"
+ "log"
+ "net/http"
+ "strings"
+ "text/template"
+)
+
+const (
+ addr = "localhost:9898"
+)
+
+// TODO: configuration for bots should probably be in config files
+var repoToChans = map[string][]string{
+ "oochat": {"general", "oochat"},
+ "barkeep": {"barkeep"},
+ "pratbot": {"pratbot", "bot-test"},
+}
+var chans = make(map[string]struct{})
+var templ *template.Template
+
+func init() {
+ // Get all unique channels
+ for _, cs := range repoToChans {
+ for _, c := range cs {
+ chans[c] = struct{}{}
+ }
+ }
+
+ // Set up template
+ funcMap := template.FuncMap{
+ "shortenSha": shortenSha,
+ "shortenMessage": shortenMessage,
+ }
+ var err error
+ templ, err = template.New("message").Funcs(funcMap).Parse(messageTemplate)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+// Set up server that gets github post-receive hook POST requests.
+
+// Only the fields we care about
+type GithubNotification struct {
+ Repository struct {
+ Name string
+ Url string
+ }
+ Commits []struct {
+ Id string
+ Message string
+ Url string
+ Author struct {
+ Name string
+ Username string
+ }
+ }
+}
+
+func shortenMessage(msg string) string {
+ subject := strings.SplitN(msg, "\n", 2)[0]
+ if len(subject) > 80 {
+ return subject[:77] + "..."
+ }
+ return subject
+}
+
+func shortenSha(sha string) string {
+ return sha[:8]
+}
+
+var messageTemplate = `
+{{$repo := .Repository}}
+{{range .Commits}}
+**[CommitBot]** [{{.Author.Name}}](https://github.com/{{.Author.Username}}) authored [{{.Id | shortenSha}}]({{.Url}}) in [{{$repo.Name}}]({{$repo.Url}}): "{{.Message | shortenMessage}}"
+{{end}}
+`
+
+func NotificationHandler(conn *connection.Conn) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ payload := r.Form["payload"]
+ if len(payload) < 1 || payload[0] == "" {
+ return
+ }
+ var notification GithubNotification
+ if err := json.Unmarshal([]byte(payload[0]), &notification); err != nil {
+ log.Println(err)
+ log.Println("CommitBot warning: couldn't parse payload:", payload)
+ return
+ }
+ var buf bytes.Buffer
+ if err := templ.Execute(&buf, &notification); err != nil {
+ log.Println("CommitBot warning: couldn't construct message", err)
+ return
+ }
+ message := strings.TrimSpace(buf.String())
+ for _, c := range repoToChans[notification.Repository.Name] {
+ conn.SendMessage(c, message)
+ }
+ }
+}
+
+type Commit struct {
+ conn *connection.Conn
+}
+
+func NewCommit(conn *connection.Conn) Bot {
+ return &Commit{conn}
+}
+
+func (b *Commit) Handle(e *Event) {
+ switch e.Type {
+ case EventConnect:
+ b.conn.Leave("general") // quick hack until there's an API for current channels
+ // We don't really need to join these channels, but whatever.
+ for c, _ := range chans {
+ b.conn.Join(c)
+ }
+
+ // Start server
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", NotificationHandler(b.conn))
+ go http.ListenAndServe(addr, mux)
+ }
+}
View
@@ -20,6 +20,7 @@ func NewEcho(conn *connection.Conn) Bot {
func (b *Echo) Handle(e *Event) {
switch e.Type {
case EventConnect:
+ b.conn.Leave("general") // quick hack until there's an API for current channels
for _, c := range channels {
b.conn.Join(c)
}
@@ -86,6 +86,15 @@ func (c *Conn) Join(channel string) error {
return c.sendJsonData(m)
}
+func (c *Conn) Leave(channel string) error {
+ data := map[string]string{"channel": channel}
+ m := &Message{
+ Action: "leave_channel",
+ Data: data,
+ }
+ return c.sendJsonData(m)
+}
+
func Connect(addrString, apiKey, secret string) (*Conn, error) {
origin := "http://localhost/"
config, err := websocket.NewConfig(authutil.ConnectionString(addrString, apiKey, secret), origin)
@@ -40,6 +40,9 @@ func (d *Dispatcher) SendRaw(msg string) {
}
event.Type = bot.EventPublishMessage
event.Payload = *m
+ // Ignore these message types for now
+ case "pong", "join_channel", "leave_channel", "user_active", "user_offline":
+ return
default:
log.Println("Received unhandled message type:", typ.Type)
return

0 comments on commit 5c9fdf3

Please sign in to comment.