summaryrefslogtreecommitdiff
path: root/src/i3build.go
blob: 5d7b2245f02bbfa400f6019ddb2b07ee929461a5 (plain)
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
// vim:ts=4:sw=4
// i3build - IRC bot to announce buildbot status
// © 2011 Michael Stapelberg (see also: LICENSE)
package main

import irc "github.com/fluffle/goirc/client"
import (
	"fmt"
	"http"
	"log"
	"json"
	"os"
	"flag"
)

var irc_channel *string = flag.String("channel", "#i3",
	"In which channel this bot should be in")

// Helper type: We first unmarshal the JSON into this type to get access to the
// "event" string, then we decide which concrete type to unmarshal to.
type minimalBuildbotEvent struct {
	Event string
}

// Every event type has to implement this interface so that we can print it to
// IRC.
type BuildbotPrintableEvent interface {
	AsChatLine() string
}

// A generic type to unmarshal to. Ev will either be set to some object
// conforming to BuildbotPrintableEvent or nil.
type BuildbotEvent struct {
	Ev BuildbotPrintableEvent
}

// Type corresponding to the buildFinished event which buildbot sends.
type BuildFinishedEvent struct {
	Payload struct {
		Build struct {
			// We have to use the empty interface here because buildbot sends
			// JSON values of varying type. They can be either string, number
			// or null.
			Properties [][]interface{}
		}
	}

	// Properties which are saved in StoreKeyValue and assembled to an IRC line
	// in AsChatLine.
	buildername string
	gitversion  string
	ircsuffix   string
}

func (o *BuildFinishedEvent) AsChatLine() string {
	return fmt.Sprintf("%s finished for %s%s",
		o.buildername, o.gitversion, o.ircsuffix)
}

func (o *BuildFinishedEvent) StoreKeyValue(key, value string) {
	switch key {
	case "buildername":
		o.buildername = value
	case "gitversion":
		o.gitversion = value
	case "ircsuffix":
		o.ircsuffix = value
	}
}

func (o *BuildbotEvent) UnmarshalJSON(data []byte) os.Error {
	var intermediate minimalBuildbotEvent
	if err := json.Unmarshal(data, &intermediate); err != nil {
		return err
	}
	if intermediate.Event == "buildFinished" {
		event := new(BuildFinishedEvent)
		if err := json.Unmarshal(data, event); err != nil {
			return err
		}

		for _, property := range event.Payload.Build.Properties {
			// Every property consists of a triple: key, value, something
			if len(property) != 3 {
				continue
			}

			// Try to convert key and value to string. If that fails, the
			// property might still be valid, but we are only interested in the
			// strings :).
			key, key_ok := property[0].(string)
			value, value_ok := property[1].(string)
			if key_ok && value_ok {
				event.StoreKeyValue(key, value)
			}
		}

		o.Ev = event
	}
	return nil
}

func main() {
	flag.Parse()

	// Channel on which the HTTP handler sends lines to IRC.
	to_irc := make(chan string)
	quit := make(chan bool)

	http.HandleFunc("/push_buildbot",
		func(w http.ResponseWriter, r *http.Request) {
			// Buildbot sends the packets URL-encoded.
			if err := r.ParseForm(); err != nil {
				log.Printf("Could not ParseForm: %s", err.String())
			}

			// Decode the JSON into BuildbotEvents and send them to IRC if
			// appropriate.
			var packets []BuildbotEvent
			err := json.Unmarshal([]byte(r.Form.Get("packets")), &packets)
			if err != nil {
				log.Printf("Could not parse JSON: %s\n", err.String())
			}
			for _, event := range packets {
				if event.Ev != nil {
					to_irc <- event.Ev.AsChatLine()
				}
			}
		})

	// Handle HTTP requests in a different Goroutine.
	go func() {
		if err := http.ListenAndServe("localhost:8080", nil); err != nil {
			log.Fatal("ListenAndServer: ", err.String())
		}
	}()

	c := irc.SimpleClient("i3build", "i3build", "http://build.i3wm.org/")

	c.AddHandler("connected",
		func(conn *irc.Conn, line *irc.Line) {
			log.Printf("Connected, joining channel %s\n", *irc_channel)
			conn.Join(*irc_channel)
		})

	c.AddHandler("disconnected",
		func(conn *irc.Conn, line *irc.Line) { quit <- true })

	log.Printf("Connecting...\n")
	if err := c.Connect("irc.twice-irc.de"); err != nil {
		log.Printf("Connection error: %s\n", err.String())
	}

	// program main loop
	for {
		select {
		case line, _ := <-to_irc:
			c.Privmsg(*irc_channel, line)
		case <-quit:
			log.Println("Disconnected. Reconnecting...")
			if err := c.Connect("irc.twice-irc.de"); err != nil {
				log.Printf("Connection error: %s\n", err.String())
			}
		}
	}
	log.Fatalln("Fell out of the main loop?!")
}