This repository has been archived on 2019-11-22. You can view files and clone it, but cannot push or open issues or pull requests.
aocbot/aocbot.go

300 lines
7.5 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/br0xen/boltease"
)
const programName = "aocbot"
var db *boltease.DB
var boardID string
var aocYear int
var slackChannel string
func main() {
if len(os.Args) != 5 {
fmt.Fprintf(os.Stderr, "usage: "+programName+" <slack-bot-token> <slack-channel-id> <aoc-leaderboard-number> <aoc-year>\n")
os.Exit(1)
}
var err error
var slack *Slack
if db, err = getDatabase(); err != nil {
panic(err)
}
// AoC Channel
//slackChannel = "C0G3X1M5K"
// br0xen DM
//slackChannel = "D0D793N5R"
slackChannel = os.Args[2]
// DevICT Leaderboard: 3549
boardID = os.Args[3]
yearStr := os.Args[4]
if aocYear, err = strconv.Atoi(yearStr); err != nil {
panic(err)
}
if slack, err = CreateSlack(os.Args[1]); err != nil {
panic(err)
}
aocBotMain(slack)
}
// This is the main function for the statbot
func aocBotMain(slack *Slack) {
// start a websocket-based Real Time API session
fmt.Println("aoc-bot ready, ^C exits")
writeToLog("== " + time.Now().Format(time.RFC3339) + " - Bot Started ==\n")
var lastAoCUpdate time.Time
go func() {
for {
var err error
lastAoCUpdate = time.Now()
var leaderboard *Leaderboard
fmt.Println(lastAoCUpdate.Format(time.RFC3339) + ": Fetching Online Leaderboard")
leaderboard, err = getAoCLeaderboard(boardID)
if err == nil {
for _, v := range leaderboard.Members {
var mbr *Member
if mbr, err = getAoCUser(v.ID); err != nil {
// Member doesn't exist in db, add it
// Notify br0xen ( U030RD9NU, DM: D0D793N5R )
m := new(Message)
m.Type = "message"
m.Channel = "D0D793N5R"
m.Text = "AoC Leaderboard has a new member! " + v.Name
fmt.Println("New Leaderboard Member Found: " + v.Name)
if err = slack.postMessage(*m); err != nil {
fmt.Println(err.Error())
}
saveAoCUser(&v)
continue
}
if mbr.Stars != v.Stars {
// Number of stars has changed
fmt.Println(v.ID + "(" + v.Name + "): " + v.LastStarTs.Format(time.RFC3339))
m := new(Message)
m.Type = "message"
m.Channel = slackChannel
m.Text = ":christmas_tree: " + v.Name + " now has " + strconv.Itoa(v.Stars) + " stars! :christmas_tree:"
if err = slack.postMessage(*m); err != nil {
fmt.Println(err.Error())
}
saveAoCUser(&v)
}
}
}
time.Sleep(time.Minute * 10)
}
}()
for {
// read each incoming message
m, err := slack.getMessage()
if err == nil {
writeToLog(" " + time.Now().Format(time.RFC3339) + " - Received Message\n")
processMessage(slack, &m)
}
}
}
func processMessage(slack *Slack, m *Message) {
if mb, me := json.Marshal(m); me == nil {
// Write the JSON representation to the log
writeToLog(string(mb) + "\n")
}
if m.Type == "message" || m.Type == "reaction_added" {
var err error
var usr *User
// Check if we know who the user is
usr, err = getUser(m.User)
// If the user information hasn't been updated in the last day, update it.
if err != nil || usr.LastUpdated.IsZero() || time.Since(usr.LastUpdated) > (time.Hour*24) {
if u, ue := slack.getUserInfo(m.User); ue == nil {
saveUser(u)
}
usr, err = getUser(m.User)
}
if m.Channel != "" {
// Check if we know what the channel is
chnl, err := getChannelInfo(m.Channel)
var isDirectMessage bool
// If the channel information hasn't been updated in the last day, update it.
if err != nil || chnl.LastUpdated.IsZero() || time.Since(chnl.LastUpdated) > (time.Hour*24) {
// Either we don't have this channel, or it's a direct message
if c, ce := slack.getChannelInfo(m.Channel); ce == nil {
// Save channel info
saveChannelInfo(c)
chnl, err = getChannelInfo(m.Channel)
} else {
isDirectMessage = true
}
}
if isDirectMessage {
if m.User == "U030RD9NU" {
// message from br0xen
if strings.HasPrefix(m.Text, "!aoc") {
var slackId, aocNm, op string
flds := strings.Fields(m.Text)
if len(flds) < 2 {
// No operation specified
return
}
op = flds[1]
if op == "help" {
// TODO: Send help message
}
if op == "match" {
re := regexp.MustCompile("[<\"]([^>]*)[>\"]")
matchUp := re.FindAllString(m.Text, -1)
for i := range matchUp {
if matchUp[i][0] == '<' {
slackId = matchUp[i][1 : len(matchUp[i])-1]
}
if matchUp[i][0] == '"' {
aocNm = matchUp[i][1 : len(matchUp[i])-1]
}
}
var m *Member
if m, err = getAoCUserByName(aocNm); err != nil {
// AoC Member doesn't exist in DB
fmt.Println("AoC User " + aocNm + " doesn't exist in DB")
fmt.Println(err.Error())
return
}
m.SlackID = slackId
saveAoCUser(m)
} else if op == "ping" {
m := new(Message)
m.Type = "message"
m.Channel = "D0D793N5R"
m.Text = ":christmas_tree: PONG :christmas_tree:"
if err = slack.postMessage(*m); err != nil {
fmt.Println(err.Error())
}
}
}
/*
ID: 0
Type: message
Channel: D0D793N5R
User: U030RD9NU
Name:
Text: testing
Ts: 1481044675.000003
*/
}
} else {
fmt.Println("--- Channel Message Received")
fmt.Println(chnl.Name, usr.Name, m.Text)
if m.Channel == slackChannel {
// In the AoC channel
if strings.HasPrefix(m.Text, "!aoc") {
var op string
flds := strings.Fields(m.Text)
if len(flds) < 2 {
// No operation specified
return
}
op = flds[1]
if op == "ping" {
m := new(Message)
m.Type = "message"
m.Channel = slackChannel
m.Text = ":christmas_tree: PONG :christmas_tree:"
if err = slack.postMessage(*m); err != nil {
fmt.Println(err.Error())
}
}
}
}
// TODO: Process Channel Message
/*
for _, proc := range messageProcessors {
proc.ProcessChannelMessage(slack, m)
}
*/
}
}
}
}
func getAoCLeaderboard(boardId string) (*Leaderboard, error) {
var err error
var req *http.Request
var content []byte
var resp *http.Response
var body []byte
leaderboard := new(Leaderboard)
client := &http.Client{}
boardString := fmt.Sprintf("https://adventofcode.com/%d/leaderboard/private/view/%s.json", aocYear, boardId)
req, err = http.NewRequest("GET", boardString, nil)
// Read in cookies
content, err = ioutil.ReadFile("./cookies")
if err != nil {
fmt.Println("Error reading Cookies")
return nil, err
}
line := strings.TrimSpace(string(content))
req.Header.Add("Cookie", line)
resp, err = client.Do(req)
if err != nil {
fmt.Println("Error getting leaderboard")
return nil, err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body.")
return nil, err
}
strBody := string(body)
strBody = strings.ReplaceAll(strBody, "\"last_star_ts\":0", "\"last_star_ts\":\"0\"")
err = json.Unmarshal([]byte(strBody), &leaderboard)
if err != nil {
fmt.Println("Error on json unmarshal")
fmt.Println(err.Error())
return nil, err
}
for k, mbr := range leaderboard.Members {
mbr.LastStarTs, err = time.Parse("2006-01-02T15:04:05-0700", mbr.RawStarTs)
leaderboard.Members[k] = mbr
}
return leaderboard, err
}
func writeToLog(d string) {
f, err := os.OpenFile("statbot.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664)
if err != nil {
panic(err)
}
f.WriteString(d)
f.Close()
}