From 29f3fe463b0a3e8579e594207c8c9aac595ee8b1 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Sat, 4 Apr 2015 19:01:12 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + README.md | 4 ++ main.go | 23 ++++++++ plugins/plugin.go | 8 +++ plugins/sqwiggle/sqwiggle.go | 111 +++++++++++++++++++++++++++++++++++ server/server.go | 81 +++++++++++++++++++++++++ slack/api.go | 64 ++++++++++++++++++++ 7 files changed, 292 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 main.go create mode 100644 plugins/plugin.go create mode 100644 plugins/sqwiggle/sqwiggle.go create mode 100644 server/server.go create mode 100644 slack/api.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa9e161 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +slackbot diff --git a/README.md b/README.md new file mode 100644 index 0000000..42861cd --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +slackbot +======== + +A slackbot written in go. diff --git a/main.go b/main.go new file mode 100644 index 0000000..c5fb5ab --- /dev/null +++ b/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "flag" + "log" + + "github.com/brettlangdon/slackbot/plugins/sqwiggle" + "github.com/brettlangdon/slackbot/server" +) + +var bind string + +func main() { + flag.StringVar(&bind, "bind", "127.0.0.1:8000", "which address to bind to [defualt: 127.0.0.1:8000]") + flag.Parse() + + s := server.NewServer() + + s.AddPlugin(sqwiggle.NewSqwiggle()) + + s.SetListenAddress(bind) + log.Fatal(s.Start()) +} diff --git a/plugins/plugin.go b/plugins/plugin.go new file mode 100644 index 0000000..5ac869e --- /dev/null +++ b/plugins/plugin.go @@ -0,0 +1,8 @@ +package plugins + +import "github.com/brettlangdon/slackbot/slack" + +type Plugin interface { + GetName() string + HandleRequest(slack.APIRequest, []string) (slack.APIResponse, error) +} diff --git a/plugins/sqwiggle/sqwiggle.go b/plugins/sqwiggle/sqwiggle.go new file mode 100644 index 0000000..030dc0c --- /dev/null +++ b/plugins/sqwiggle/sqwiggle.go @@ -0,0 +1,111 @@ +package sqwiggle + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + + "github.com/brettlangdon/slackbot/slack" +) + +type SqwiggleResponse struct { + Snapshot string `json:"snapshot"` + Status string `json:"status"` + Name string `json:"name"` + EMail string `json:"email"` + TimeZone string `json:"time_zone"` + Avatar string `json:"avatar"` + Message string `json:"message"` + SnapshotInterval int `json:"snapshot_interval"` +} + +type Sqwiggle struct { + name string +} + +func NewSqwiggle() Sqwiggle { + return Sqwiggle{ + name: "sqwiggle", + } +} + +func (this Sqwiggle) GetName() string { + return this.name +} + +func (this Sqwiggle) SetName(name string) { + this.name = name +} + +func (this Sqwiggle) callSqwiggle(token string) (response []SqwiggleResponse, err error) { + client := &http.Client{} + request, err := http.NewRequest("GET", "https://api.sqwiggle.com/users", nil) + if err != nil { + return response, err + } + request.SetBasicAuth(token, "slackbot") + sqwiggleResponse, err := client.Do(request) + if err != nil || sqwiggleResponse.StatusCode != 200 { + err = errors.New(fmt.Sprintf("API call to sqwiggle with token %s failed", token)) + return response, err + } + + defer sqwiggleResponse.Body.Close() + + contents, err := ioutil.ReadAll(sqwiggleResponse.Body) + if err != nil { + err = errors.New("Could not read sqwiggle api response") + return response, err + } + err = json.Unmarshal(contents, &response) + + if err != nil { + err = errors.New(fmt.Sprintf("Could not parse sqwiggle api response: %s", string(contents))) + return response, err + } + + return response, nil +} + +func (this Sqwiggle) HandleRequest(request slack.APIRequest, args []string) (response slack.APIResponse, err error) { + if request.Text != "#sqwiggle" { + err := errors.New(fmt.Sprintf("Incorrect trigger text: %s", request.Text)) + return response, err + } + + if len(args) <= 1 { + err := errors.New("Missing sqwiggle token argument") + return response, err + } + + users, err := this.callSqwiggle(args[1]) + + if err != nil { + return response, err + } + + response.Text = "Sqwiggle Time" + + for _, user := range users { + attachment := slack.Attachment{} + switch user.Status { + case "offline": + attachment.Color = "#000000" + case "available": + attachment.Color = "#00CC33" + case "busy": + attachment.Color = "#FF3333" + } + + attachment.Title = user.Name + attachment.TitleLink = user.Snapshot + attachment.ImageUrl = user.Snapshot + attachment.Fallback = user.Snapshot + "#.jpg" + + response.Attachments = append(response.Attachments, attachment) + } + + return response, err +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..18ea0df --- /dev/null +++ b/server/server.go @@ -0,0 +1,81 @@ +package server + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + + "github.com/brettlangdon/slackbot/plugins" + "github.com/brettlangdon/slackbot/slack" +) + +type Server struct { + plugs map[string]plugins.Plugin + address string + server *http.Server +} + +func NewServer() *Server { + server := &Server{ + address: "0.0.0.0:80", + } + + server.plugs = make(map[string]plugins.Plugin) + + return server +} + +func (this *Server) SetListenAddress(address string) { + this.address = address +} + +func (this *Server) AddPlugin(plug plugins.Plugin) { + this.plugs[strings.ToLower(plug.GetName())] = plug +} + +func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // we always want to write back something + defer fmt.Fprintf(w, "") + path := strings.Trim(r.URL.Path, "/") + if path == "" { + log.Println("Empty path given") + return + } + args := strings.Split(path, "/") + + name := strings.ToLower(args[0]) + var plug plugins.Plugin + var ok bool + if plug, ok = this.plugs[name]; !ok { + log.Println("No plugin gin found with name: ", name) + return + } + + apiRequest := slack.ParseAPIRequest(r) + apiResponse, err := plug.HandleRequest(apiRequest, args) + + if err != nil { + log.Println(err) + return + } + + content, err := json.Marshal(apiResponse) + if err != nil { + log.Println(err) + return + } + + fmt.Fprintf(w, "%s", content) +} + +func (this *Server) Start() error { + this.server = &http.Server{ + Addr: this.address, + Handler: this, + } + + log.Println("Listening to: ", this.address) + return this.server.ListenAndServe() +} diff --git a/slack/api.go b/slack/api.go new file mode 100644 index 0000000..9a1dc8c --- /dev/null +++ b/slack/api.go @@ -0,0 +1,64 @@ +package slack + +import ( + "net/http" + "strconv" +) + +type APIRequest struct { + Token string + TeamId string + TeamDomain string + ChannelId string + ChannelName string + Timestamp float64 + UserId string + UserName string + Text string + TriggerWord string +} + +func ParseAPIRequest(httpReq *http.Request) (response APIRequest) { + response.Token = httpReq.PostFormValue("token") + response.TeamId = httpReq.PostFormValue("team_id") + response.TeamDomain = httpReq.PostFormValue("team_domain") + response.ChannelId = httpReq.PostFormValue("channel_id") + response.ChannelName = httpReq.PostFormValue("channel_name") + + ts := httpReq.PostFormValue("timestamp") + response.Timestamp, _ = strconv.ParseFloat(ts, 32) + response.UserId = httpReq.PostFormValue("user_id") + response.UserName = httpReq.PostFormValue("user_name") + response.Text = httpReq.PostFormValue("text") + response.TriggerWord = httpReq.PostFormValue("trigger_word") + return response +} + +type APIResponse struct { + Text string `json:"text,omitempty"` + Attachments []Attachment `json:"attachments,omitempty"` + UserName string `json:"user_name,omitempty"` + IconEmoji string `json:"icon_emoji,omitempty"` + Channel string `json:"channel,omitempty"` + Markdown bool `json:"mrkdwn,omitempty"` +} + +type Attachment struct { + AuthorIcon string `json:"author_icon,omitempty"` + AuthorLink string `json:"author_link,omitempty"` + AuthorName string `json:"author_name,omitempty"` + Color string `json:"color,omitempty"` + Fallback string `json:"fallback,omitempty"` + Fields []Fields `json:"fields,omitempty"` + ImageUrl string `json:"image_url,omitempty"` + MarkdownIn string `json:"mrkdwn_in,omitempty"` + Pretext string `json:"pretext,omitempty"` + Title string `json:"title,omitempty"` + TitleLink string `json:"title_link,omitempty"` +} + +type Fields struct { + Title string `json:"title,omitempty"` + Value string `json:"value,omitempty"` + Short bool `json:"short,omitempty"` +}