package main import ( "bytes" "encoding/json" "fmt" jwt "github.com/dgrijalva/jwt-go" "io" "log" "math/rand" "net/http" "os" "regexp" "time" ) type echoHandler struct { Config Config } type msgHandler struct { Counts map[string]int Config Config MessageSender chan simpleMessage } type glanceHandler struct { Config Config Glances map[glanceState]glanceState } type sidebarHandler struct { Config Config } type conversation struct { ID string AvatarURL string IsArchived bool Name string Privacy string Topic string Type string Modified time.Time Created time.Time } type messagePart struct { Type string Content []messagePart Text string } type sender struct { ID string } type message struct { ID string Body struct { messagePart Version int } Text string Sender sender Time time.Time `json:"ts"` } type outgoingMessage struct { Body bodyType } type bodyType struct { Version int Type string Content []messagePart Text string } type incomingMessage struct { CloudID string Conversation conversation Message message Recipients []sender Sender sender Type string } type glanceState struct { CloudID string ConversationID string } type glanceStateData struct { Label string `json:"label"` Metadata map[string]string `json:"metadata"` } func (h *echoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() log.Println(r.Header) log.Println(*r.URL) log.Println("MESSAGE:") _, err := io.Copy(os.Stdout, r.Body) if err != nil { log.Println(err) } fmt.Fprintf(w, "ok\n") } type contextClaim struct { Context contextClaims `json:"context"` jwt.StandardClaims } type contextClaims struct { CloudID string `json:"cloudId"` ResourceID string `json:"resourceID"` ResourceType string `json:"resourceType"` } func (h *glanceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { tokenString := r.URL.Query().Get("jwt") token, err := jwt.ParseWithClaims(tokenString, &contextClaim{}, func(token *jwt.Token) (interface{}, error) { return []byte(h.Config.ClientSecret), nil }) if err != nil { w.WriteHeader(401) fmt.Fprintf(w, "wrong jwt: %q", err) return } if claims, ok := token.Claims.(*contextClaim); ok && token.Valid { if _, e := h.Glances[glanceState{claims.Context.CloudID, claims.Context.ResourceID}]; !e { gs := glanceState{claims.Context.CloudID, claims.Context.ResourceID} h.Glances[gs] = gs } log.Println(h.Glances) } else { w.WriteHeader(401) fmt.Fprintf(w, "wrong jwt: %q", err) return } w.Header().Add("Content-Type", "application/json") fmt.Fprint(w, `{"label":"Value of the glance","metadata":{"count":"1"}}`) } func (h *sidebarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/html") fmt.Fprint(w, `

Hello

`) } var ( karmaRegex = regexp.MustCompile(`(\S+)([+-]{2})`) karmaStatsRegex = regexp.MustCompile(`karma\s+(\S+)`) ) func (h *msgHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() log.Println(r.Header) log.Println(*r.URL) var msg incomingMessage dec := json.NewDecoder(r.Body) err := dec.Decode(&msg) if err != nil { log.Println(err) } log.Printf("MESSAGE: %#v\n", msg) if res := karmaRegex.FindAllStringSubmatch(msg.Message.Text, -1); len(res) >= 1 { for _, u := range res { count := 1 username := u[1] if u[2] == "--" { count = -1 } if c, e := h.Counts[username]; e { count += c } h.Counts[username] = count // Reply in the same room, to the same user text := fmt.Sprintf("%s has %d karma\n", username, count) h.MessageSender <- simpleMessage{text, msg.CloudID, msg.Conversation.ID} f, err := os.Create("counts.json") if err != nil { log.Println(err) } json.NewEncoder(f).Encode(&h.Counts) } } else if matches := karmaStatsRegex.FindAllStringSubmatch(msg.Message.Text, -1); len(matches) >= 1 { go func(msg incomingMessage) { username := matches[0][1] var text string if c, e := h.Counts[username]; e { text = fmt.Sprintf("%s has %d karma\n", username, c) } else { text = fmt.Sprintf("%s has no karma, add with %s++\n", username, username) } h.MessageSender <- simpleMessage{text, msg.CloudID, msg.Conversation.ID} }(msg) } fmt.Fprintf(w, "ok\n") } type simpleMessage struct { Message string CloudID string ConversationID string } func main() { config, err := loadConfig("config.json") if err != nil { log.Fatalf("error while loading config: %s", err) } config.Token, err = getToken(config) if err != nil { log.Fatalf("error while getting token: %q", err) } config.TokenCreated = time.Now() saveConfig("config.json", config) messageSender := make(chan simpleMessage) go func() { for { select { case msg := <-messageSender: var b bytes.Buffer b.WriteString(msg.Message) sendMessage(&b, msg.CloudID, msg.ConversationID, config.Token.AccessToken) break } } }() eh := &echoHandler{Config: *config} mh := &msgHandler{Config: *config, MessageSender: messageSender} gh := &glanceHandler{Config: *config} sh := &sidebarHandler{Config: *config} mh.Counts = make(map[string]int) f, err := os.Open("counts.json") if err != nil { log.Fatalf("missing file: counts.json: %q", err) } json.NewDecoder(f).Decode(&mh.Counts) gh.Glances = make(map[glanceState]glanceState) ticker := time.Tick(20 * time.Second) go func() { for { select { case <-ticker: client := http.Client{} for _, gs := range gh.Glances { log.Println("Updating glances") var b bytes.Buffer u := fmt.Sprintf( "https://api.atlassian.com/site/%s/conversation/%s/app/module/chat:glance/%s/state", gs.CloudID, gs.ConversationID, "bot-glance", ) var data glanceStateData data.Label = fmt.Sprintf("%d items", rand.Intn(10)+1) data.Metadata = make(map[string]string) data.Metadata["count"] = "8" err = json.NewEncoder(&b).Encode(&data) if err != nil { log.Println(err) return } req, err := http.NewRequest("PUT", u, &b) if err != nil { log.Println(err) return } req.Header.Add("Authorization", "Bearer "+config.Token.AccessToken) req.Header.Add("Content-Type", "application/json") res, err := client.Do(req) if err != nil { log.Println(err) return } defer res.Body.Close() io.Copy(os.Stderr, res.Body) } break } } }() http.Handle("/installed", eh) http.Handle("/uninstalled", eh) http.Handle("/bot/mention", mh) http.Handle("/bot/dm", mh) http.Handle("/bot/msg", mh) http.Handle("/glance", gh) http.Handle("/sidebar", sh) http.Handle("/", http.FileServer(http.Dir("."))) log.Fatal(http.ListenAndServe(":8080", nil)) }