Permalink
Browse files

Simple annoying echo bot.

  • Loading branch information...
1 parent c22692c commit 7edd5bbab7018d1591ade18aaa38d3f7238679db @cespare committed Jan 10, 2013
Showing with 248 additions and 17 deletions.
  1. +50 −8 pratbot.go
  2. +37 −0 src/bot/bot.go
  3. +39 −0 src/bot/echo.go
  4. +74 −9 src/connection/connection.go
  5. +48 −0 src/dispatcher/dispatcher.go
View
58 pratbot.go
@@ -1,24 +1,36 @@
package main
import (
- "log"
"flag"
"fmt"
+ "log"
"os"
+ "strings"
+ "bot"
"connection"
+ "dispatcher"
)
var (
- server = flag.String("server", "", "Prat server")
- apiKey = flag.String("apikey", "", "Prat API key")
- secret = flag.String("secret", "", "Prat API secret")
- tls = flag.Bool("tls", true, "Connect via TLS")
- port = flag.Int("port", 0, "Port (defaults to 80/443)")
+ server = flag.String("server", "", "Prat server")
+ apiKey = flag.String("apikey", "", "Prat API key")
+ secret = flag.String("secret", "", "Prat API secret")
+ tls = flag.Bool("tls", true, "Connect via TLS")
+ port = flag.Int("port", 0, "Port (defaults to 80/443)")
+ botsString = flag.String("bots", "", "Comma-separated list of bots to initialize")
addrString string
)
+type newBotFunc func(*connection.Conn) bot.Bot
+
+var (
+ botNameToFunc = map[string]newBotFunc{"echo": bot.NewEcho}
+ bots = make(map[string]newBotFunc)
+ disp = dispatcher.New()
+)
+
func init() {
flag.Parse()
@@ -42,14 +54,44 @@ func init() {
proto = "wss"
}
addrString = fmt.Sprintf("%s://%s:%d", proto, *server, *port)
+
+ botList := strings.Split(*botsString, ",")
+ for _, bs := range botList {
+ if bs == "" {
+ continue
+ }
+ f, ok := botNameToFunc[bs]
+ if !ok {
+ log.Fatalln("Unrecognized bot:", bs)
+ }
+ bots[bs] = f
+ }
+ if len(bots) == 0 {
+ log.Fatalln("Must specify one or more bots to run.")
+ }
}
func main() {
+ // Connect
conn, err := connection.Connect(addrString, *apiKey, *secret)
if err != nil {
log.Fatal(err)
}
- if conn.SendMessage("general", "Hello from bot!") != nil {
- log.Fatal(err)
+
+ // Register bots
+ for _, f := range bots {
+ disp.Register(f(conn))
+ }
+
+ // Send 'connected' message
+ connectedMsg := &bot.Event{
+ Type: bot.EventConnect,
+ }
+ disp.Send(connectedMsg)
+
+ // Loop, receiving messages, and send them through the dispatcher
+ for {
+ msg := <-conn.In
+ disp.SendRaw(msg)
}
}
View
37 src/bot/bot.go
@@ -0,0 +1,37 @@
+package bot
+
+type EventType int
+
+const (
+ EventConnect EventType = iota
+ EventPublishMessage
+)
+
+// Events should not be modified by bots.
+type Event struct {
+ // Type indicates the contents of Payload, which might be nil.
+ Type EventType
+ // Payload is a struct defined in dispatcher, e.g., PublishMessage
+ Payload interface{}
+}
+
+type Bot interface {
+ Handle(e *Event)
+}
+
+type MessageType struct {
+ Type string `json:"action"`
+}
+
+type PublishMessage struct {
+ Data struct {
+ Username string
+ Author string
+ Email string
+ Channel string
+ Gravatar string
+ Datetime int
+ Message string
+ RenderedMessage string
+ }
+}
View
39 src/bot/echo.go
@@ -0,0 +1,39 @@
+package bot
+
+import (
+ "connection"
+ "strings"
+)
+
+var channels = []string{"bot-test"}
+
+type Echo struct {
+ conn *connection.Conn
+}
+
+func NewEcho(conn *connection.Conn) Bot {
+ return &Echo{
+ conn: conn,
+ }
+}
+
+func (b *Echo) Handle(e *Event) {
+ switch e.Type {
+ case EventConnect:
+ for _, c := range channels {
+ b.conn.Join(c)
+ }
+ case EventPublishMessage:
+ m := e.Payload.(PublishMessage)
+
+ // Ignore our own messages. Hard-code username until we make an API to retrieve this info.
+ if m.Data.Username == "foo" {
+ return
+ }
+
+ msg := m.Data.Message
+ channel := m.Data.Channel
+ newMessage := "**" + strings.ToUpper(msg) + "**"
+ b.conn.SendMessage(channel, newMessage)
+ }
+}
View
83 src/connection/connection.go
@@ -1,36 +1,89 @@
package connection
import (
- "encoding/json"
"code.google.com/p/go.net/websocket"
"crypto/tls"
+ "encoding/json"
+ "log"
+ "time"
"authutil"
)
+var PingFrequency = 30 * time.Second
+
+func (c *Conn) receive() {
+ for {
+ var msg string
+ if err := websocket.Message.Receive(c.ws, &msg); err != nil {
+ log.Println("Error receiving message:", err)
+ continue
+ }
+ c.In <- msg
+ }
+}
+
+func (c *Conn) ping() {
+ ping := &Message{
+ Action: "ping",
+ Data: map[string]string{"message": "PING"},
+ }
+ j, err := json.Marshal(ping)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // Send a heartbeat ping every N seconds.
+ ticker := time.NewTicker(PingFrequency)
+ for _ = range ticker.C {
+ c.out <- string(j)
+ }
+}
+
+func (c *Conn) send() {
+ for msg := range c.out {
+ if err := websocket.Message.Send(c.ws, msg); err != nil {
+ log.Println("Error sending message:", err)
+ }
+ }
+}
+
type Conn struct {
ws *websocket.Conn
+ // Messages come out here
+ In chan string
+ out chan string
}
type Message struct {
Action string `json:"action"`
Data map[string]string `json:"data"`
}
+func (c *Conn) sendJsonData(d interface{}) error {
+ j, err := json.Marshal(d)
+ if err != nil {
+ return err
+ }
+ c.out <- string(j)
+ return nil
+}
+
func (c *Conn) SendMessage(channel, msg string) error {
data := map[string]string{"channel": channel, "message": msg}
m := &Message{
Action: "publish_message",
Data: data,
}
- j, err := json.Marshal(m)
- if err != nil {
- return err
- }
- if _, err := c.ws.Write(j); err != nil {
- return err
+ return c.sendJsonData(m)
+}
+
+func (c *Conn) Join(channel string) error {
+ data := map[string]string{"channel": channel}
+ m := &Message{
+ Action: "join_channel",
+ Data: data,
}
- return nil
+ return c.sendJsonData(m)
}
func Connect(addrString, apiKey, secret string) (*Conn, error) {
@@ -45,5 +98,17 @@ func Connect(addrString, apiKey, secret string) (*Conn, error) {
if err != nil {
return nil, err
}
- return &Conn{ws}, nil
+
+ conn := &Conn{
+ ws: ws,
+ In: make(chan string),
+ out: make(chan string),
+ }
+
+ // Start goroutines
+ go conn.receive()
+ go conn.ping()
+ go conn.send()
+
+ return conn, nil
}
View
48 src/dispatcher/dispatcher.go
@@ -0,0 +1,48 @@
+package dispatcher
+
+import (
+ "bot"
+ "encoding/json"
+ "log"
+)
+
+type Dispatcher struct {
+ bots []bot.Bot
+}
+
+func New() *Dispatcher {
+ return &Dispatcher{}
+}
+
+func (d *Dispatcher) Register(b bot.Bot) {
+ d.bots = append(d.bots, b)
+}
+
+func (d *Dispatcher) Send(e *bot.Event) {
+ for _, b := range d.bots {
+ b.Handle(e)
+ }
+}
+
+func (d *Dispatcher) SendRaw(msg string) {
+ typ := bot.MessageType{}
+ if err := json.Unmarshal([]byte(msg), &typ); err != nil {
+ log.Println("Warning: received bad message:", err)
+ return
+ }
+ event := &bot.Event{}
+ switch typ.Type {
+ case "publish_message":
+ m := new(bot.PublishMessage)
+ if err := json.Unmarshal([]byte(msg), m); err != nil {
+ log.Println("Warning: bad publish message:", err)
+ return
+ }
+ event.Type = bot.EventPublishMessage
+ event.Payload = *m
+ default:
+ log.Println("Received unhandled message type:", typ.Type)
+ return
+ }
+ d.Send(event)
+}

0 comments on commit 7edd5bb

Please sign in to comment.