Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: cespare/pratbot
base: 0be9eadb4b
...
head fork: cespare/pratbot
compare: 7edd5bbab7
  • 2 commits
  • 6 files changed
  • 0 commit comments
  • 1 contributor
View
154 pratbot.go
@@ -1,129 +1,97 @@
package main
import (
- "bytes"
- "code.google.com/p/go.net/websocket"
- "crypto/sha256"
- "crypto/tls"
- "encoding/base64"
- "encoding/json"
"flag"
"fmt"
"log"
"os"
- "sort"
- "strconv"
"strings"
- "time"
+
+ "bot"
+ "connection"
+ "dispatcher"
)
-const (
- server = "pratchat.com:443"
+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)")
+ botsString = flag.String("bots", "", "Comma-separated list of bots to initialize")
+
+ addrString string
)
+type newBotFunc func(*connection.Conn) bot.Bot
+
var (
- apikey = flag.String("apikey", "", "Prat API key")
- secret = flag.String("secret", "", "Prat API secret")
- ws *websocket.Conn
+ botNameToFunc = map[string]newBotFunc{"echo": bot.NewEcho}
+ bots = make(map[string]newBotFunc)
+ disp = dispatcher.New()
)
func init() {
flag.Parse()
- if *apikey == "" || *secret == "" {
- flag.Usage()
- os.Exit(-1)
+ for _, f := range []string{*server, *apiKey, *secret} {
+ if f == "" {
+ flag.Usage()
+ os.Exit(-1)
+ }
}
-}
-
-type ByPair [][]string
-func (p ByPair) Len() int { return len(p) }
-func (p ByPair) Less(i, j int) bool { return p[i][0] < p[j][0] }
-func (p ByPair) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
-
-func prepareQueryString(params map[string]string, exclude []string) string {
- var result [][]string
- for k, v := range params {
- excluded := false
- for _, e := range exclude {
- if k == e {
- excluded = true
- break
- }
- }
- if !excluded {
- result = append(result, []string{k, v})
+ if *port == 0 {
+ if *tls {
+ *port = 443
+ } else {
+ *port = 80
}
}
- sort.Sort(ByPair(result))
- var pairs []string
- for _, p := range result {
- pairs = append(pairs, strings.Join(p, "="))
- }
- return strings.Join(pairs, "")
-}
-func Signature(secret, method, path, body string, params map[string]string) string {
- exclude := []string{"signature"}
- signature := secret + strings.ToUpper(method) + path + prepareQueryString(params, exclude) + body
- h := sha256.New()
- h.Write([]byte(signature))
- var buf bytes.Buffer
- enc := base64.NewEncoder(base64.URLEncoding, &buf)
- enc.Write(h.Sum(nil))
- enc.Close()
- return buf.String()[:43]
-}
-
-func ConnectionString() string {
- expires := int(time.Now().Unix() + 300)
- params := map[string]string{"api_key": *apikey, "expires": strconv.Itoa(expires)}
- signature := Signature(*secret, "GET", "/eventhub", "", params)
- params["signature"] = signature
- var kv []string
- for k, v := range params {
- kv = append(kv, k+"="+v)
+ proto := "ws"
+ if *tls {
+ proto = "wss"
}
- return fmt.Sprintf("wss://%s/eventhub?%s", server, strings.Join(kv, "&"))
-}
+ addrString = fmt.Sprintf("%s://%s:%d", proto, *server, *port)
-type Message struct {
- Action string `json:"action"`
- Data map[string]string `json:"data"`
-}
-
-func 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
+ 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 _, err := ws.Write(j); err != nil {
- return err
+ if len(bots) == 0 {
+ log.Fatalln("Must specify one or more bots to run.")
}
- return nil
}
func main() {
- origin := "http://localhost/"
- var err error
- config, err := websocket.NewConfig(ConnectionString(), origin)
+ // Connect
+ conn, err := connection.Connect(addrString, *apiKey, *secret)
if err != nil {
log.Fatal(err)
}
- // Ignore certs for now
- config.TlsConfig = &tls.Config{InsecureSkipVerify: true}
- ws, err = websocket.DialConfig(config)
- if err != nil {
- log.Fatal(err)
+
+ // Register bots
+ for _, f := range bots {
+ disp.Register(f(conn))
}
- fmt.Println("Connected.")
- if SendMessage("general", "Hello from bot!") != nil {
- log.Fatal(err)
+
+ // 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
64 src/authutil/authutil.go
@@ -0,0 +1,64 @@
+package authutil
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type ByPair [][]string
+
+func (p ByPair) Len() int { return len(p) }
+func (p ByPair) Less(i, j int) bool { return p[i][0] < p[j][0] }
+func (p ByPair) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+func prepareQueryString(params map[string]string, exclude []string) string {
+ var result [][]string
+ for k, v := range params {
+ excluded := false
+ for _, e := range exclude {
+ if k == e {
+ excluded = true
+ break
+ }
+ }
+ if !excluded {
+ result = append(result, []string{k, v})
+ }
+ }
+ sort.Sort(ByPair(result))
+ var pairs []string
+ for _, p := range result {
+ pairs = append(pairs, strings.Join(p, "="))
+ }
+ return strings.Join(pairs, "")
+}
+
+func Signature(secret, method, path, body string, params map[string]string) string {
+ exclude := []string{"signature"}
+ signature := secret + strings.ToUpper(method) + path + prepareQueryString(params, exclude) + body
+ h := sha256.New()
+ h.Write([]byte(signature))
+ var buf bytes.Buffer
+ enc := base64.NewEncoder(base64.URLEncoding, &buf)
+ enc.Write(h.Sum(nil))
+ enc.Close()
+ return buf.String()[:43]
+}
+
+func ConnectionString(addrString, apiKey, secret string) string {
+ expires := int(time.Now().Unix() + 300)
+ params := map[string]string{"api_key": apiKey, "expires": strconv.Itoa(expires)}
+ signature := Signature(secret, "GET", "/eventhub", "", params)
+ params["signature"] = signature
+ var kv []string
+ for k, v := range params {
+ kv = append(kv, k+"="+v)
+ }
+ return fmt.Sprintf("%s/eventhub?%s", addrString, strings.Join(kv, "&"))
+}
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
114 src/connection/connection.go
@@ -0,0 +1,114 @@
+package connection
+
+import (
+ "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,
+ }
+ 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 c.sendJsonData(m)
+}
+
+func Connect(addrString, apiKey, secret string) (*Conn, error) {
+ origin := "http://localhost/"
+ config, err := websocket.NewConfig(authutil.ConnectionString(addrString, apiKey, secret), origin)
+ if err != nil {
+ return nil, err
+ }
+ // Ignore certs for now
+ config.TlsConfig = &tls.Config{InsecureSkipVerify: true}
+ ws, err := websocket.DialConfig(config)
+ if err != nil {
+ return nil, err
+ }
+
+ 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)
+}

No commit comments for this range

Something went wrong with that request. Please try again.