You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
7.1 KiB

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, `
<html>
<head>
<script src='https://dev-lib.stride.com/javascript/simple-xdm.js'></script>
</head>
<body>
<h1>Hello</h1>
<script>
AP.register({
"message-received": function(data) {console.log(data);},
"glance-update": function(data) {console.log(data);}
});
</script>
</body>
</html>
`)
}
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))
}