diff --git a/go.mod b/go.mod index 44a23d67472bde27cc99846352e0d19e04af04ed..f0174b172502b63c1928fa6ddcdc48bc8d3abd1e 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,5 @@ -module git.kuschku.de/justjanne/stateless-matrix-bot +module git.kuschku.de/justjanne/stateless-matrix-bot-framework go 1.22.2 -require ( - git.kuschku.de/justJanne/bahn-api v0.0.0-20210606022125-173e9216d8a8 - github.com/google/uuid v1.6.0 -) - -require ( - github.com/andybalholm/cascadia v1.0.0 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect - golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01 // indirect - golang.org/x/text v0.3.2 // indirect -) +require github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 0425cac525dfbe7398024a83fb691ffe627077fa..7790d7c3e03900e267f0aade3acce649895b2246 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,2 @@ -git.kuschku.de/justJanne/bahn-api v0.0.0-20210606022125-173e9216d8a8 h1:5VmfteMrWeABylH1lP46QLBEx8YWawIfw2WdfGWSv/I= -git.kuschku.de/justJanne/bahn-api v0.0.0-20210606022125-173e9216d8a8/go.mod h1:9d+hDIsjtAxjb0FPo6DLNqf9Co7CX35IHScmo9wQGlo= -github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= -github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01 h1:po1f06KS05FvIQQA2pMuOWZAUXiy1KYdIf0ElUU2Hhc= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 218ce4d7c9ac17247f694b73a4ce9e89fac34921..9a5b91d7a378b33acab4a789acfe2abc917421e5 100644 --- a/main.go +++ b/main.go @@ -1,126 +1,111 @@ package main import ( - "bytes" "fmt" - "git.kuschku.de/justJanne/bahn-api" - "git.kuschku.de/justjanne/stateless-matrix-bot/api" - "html/template" - "math/rand/v2" + "git.kuschku.de/justjanne/stateless-matrix-bot-framework/api" + "io" + "log" "net/http" "net/url" - "os" "strings" + "time" ) -func main() { - var err error +type MatrixBot struct { + token *api.Token + pushUrl *url.URL + handlers map[string]func(bot *MatrixBot, notification api.Notification) error +} + +func NewMatrixBot(pushUrl *url.URL) *MatrixBot { + return &MatrixBot{ + token: nil, + pushUrl: pushUrl, + handlers: make(map[string]func(bot *MatrixBot, notification api.Notification) error), + } +} - pushUrl, err := url.Parse(os.Getenv("BOT_PUSHURL")) +func (bot *MatrixBot) RefreshToken() error { + if bot.token == nil { + return fmt.Errorf("no refresh token available") + } + userData, err := api.Refresh(bot.token.RefreshToken) if err != nil { - panic(err) + return err + } + bot.token = &api.Token{ + AccessToken: userData.AccessToken, + Expires: time.Now().Add(time.Duration(userData.ExpiresInMs) / 2 * time.Millisecond), + RefreshToken: userData.RefreshToken, } - bot := NewMatrixBot(pushUrl) + return nil +} - // !8ball handler - bot.HandleFunc("!8ball", func(bot *MatrixBot, notification api.Notification) error { - positive := []string{ - "It is certain", - "It is decidedly so", - "Without a doubt", - "Yes – definitely", - "You may rely on it", - "As I see it, yes", - "Most Likely", - "Outlook good", - "Yes", - "Signs point to yes.", - } - negative := []string{ - "Don’t count on it", - "My reply is no", - "My sources say no", - "Outlook not so good", - "very doubtful", - } - neutral := []string{ - "Reply hazy", - "try again", - "Ask again later", - "Better not tell you now", - "Cannot predict now", - "Concentrate and ask again", - } - var answers []string - switch rand.IntN(3) { - case 0: - answers = positive - break - case 1: - answers = negative - break - case 2: - answers = neutral - break +func (bot *MatrixBot) Login(username string, password string, deviceId string) error { + userData, err := api.Login(username, password, deviceId) + if err != nil { + return err + } + bot.token = &api.Token{ + AccessToken: userData.AccessToken, + Expires: time.Now().Add(time.Duration(userData.ExpiresInMs) / 2 * time.Millisecond), + RefreshToken: userData.RefreshToken, + } + return nil +} + +func (bot *MatrixBot) RefreshTask() { + for true { + if bot.token != nil && time.Now().After(bot.token.Expires) { + if err := bot.RefreshToken(); err != nil { + log.Printf("error refresh token: %s\n", err.Error()) + } } - answer := answers[rand.IntN(len(answers))] + time.Sleep(1 * time.Second) + } +} - err = api.SendMessage(*bot.token, notification.RoomId, api.MessageContent{ - Body: answer, - MsgType: "m.text", - }) - return nil - }) +func (bot *MatrixBot) RegisterPusher() error { + return api.SetPusher(*bot.token, bot.pushUrl.String()) +} - // !trains handler - bahnTpl, err := template.New("bahn").Parse(`{{- /*gotype: bahn.Timetable*/ -}} -<b>{{.Station}}</b> Abfahrten -{{- range .Stops -}} - {{- if .Departure -}} - {{- if .Departure.Line -}} - <li>{{- if .Departure.ChangedTime -}}{{.Departure.ChangedTime}}{{- else if .Departure.PlannedTime -}}{{.Departure.PlannedTime}}{{- end -}} - · <b>{{ .Departure.Line }}</b></li> - {{- else if .TripLabel.TripCategory -}} - {{- if .TripLabel.TripNumber -}} - <li>{{- if .Departure.ChangedTime -}}{{.Departure.ChangedTime}}{{- else if .Departure.PlannedTime -}}{{.Departure.PlannedTime}}{{- end -}} - · <b>{{ .TripLabel.TripCategory }} {{ .TripLabel.TripNumber }}</b></li> - {{- end -}} - {{- end -}} - {{- end -}} -{{- end -}}`) - if err != nil { - panic(err) - } - bot.HandleFunc("!trains", func(bot *MatrixBot, notification api.Notification) error { - elements := strings.SplitN(strings.TrimSpace(notification.Content.Body), " ", 3) - response, err := http.Get(fmt.Sprintf("https://iris.noncd.db.de/iris-tts/timetable/fchg/%s", elements[1])) +func (bot *MatrixBot) HandleFunc(command string, handler func(bot *MatrixBot, notification api.Notification) error) { + bot.handlers[command] = handler +} + +func (bot *MatrixBot) Serve(endpoint string) { + http.HandleFunc("/healthz", func(writer http.ResponseWriter, request *http.Request) { + _, _ = io.WriteString(writer, "OK\n") + }) + log.Printf("listening for push notifications on %s\n", bot.pushUrl.Path) + http.HandleFunc(bot.pushUrl.Path, func(writer http.ResponseWriter, request *http.Request) { + log.Println("Received push notification") + notification, err := api.ParseNotification(request.Body) if err != nil { - return err + log.Println(err.Error()) + return + } + if notification.EventId == "" { + return } - timetable, err := bahn.TimetableFromReader(response.Body) + err = api.SetReadReceipt(*bot.token, notification.RoomId, notification.EventId) if err != nil { - return err + log.Println(err.Error()) + return } - buf := new(bytes.Buffer) - err = bahnTpl.Execute(buf, timetable) + command := strings.SplitN(notification.Content.Body, " ", 2)[0] + handler, ok := bot.handlers[command] + if !ok { + log.Printf("could not find handler for '%s'\n", command) + return + } + err = handler(bot, notification) if err != nil { - return err + log.Println(err.Error()) + return } - err = api.SendMessage(*bot.token, notification.RoomId, api.MessageContent{ - FormattedBody: buf.String(), - Format: "org.matrix.custom.html", - MsgType: "m.text", - }) - return nil + writer.WriteHeader(204) }) - err = bot.Login(os.Getenv("BOT_USERNAME"), os.Getenv("BOT_PASSWORD"), os.Getenv("BOT_DEVICEID")) - if err != nil { - panic(err) - } - err = bot.RegisterPusher() - if err != nil { - panic(err) - } - go bot.RefreshTask() - bot.Serve(":8080") + fmt.Printf("listening for requests on %s\n", endpoint) + log.Fatal(http.ListenAndServe(endpoint, nil)) } diff --git a/matrixbot.go b/matrixbot.go deleted file mode 100644 index 2604f8fcd5c59bee008f4d1be4a2e24595531342..0000000000000000000000000000000000000000 --- a/matrixbot.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "fmt" - "git.kuschku.de/justjanne/stateless-matrix-bot/api" - "io" - "log" - "net/http" - "net/url" - "strings" - "time" -) - -type MatrixBot struct { - token *api.Token - pushUrl *url.URL - handlers map[string]func(bot *MatrixBot, notification api.Notification) error -} - -func NewMatrixBot(pushUrl *url.URL) *MatrixBot { - return &MatrixBot{ - token: nil, - pushUrl: pushUrl, - handlers: make(map[string]func(bot *MatrixBot, notification api.Notification) error), - } -} - -func (bot *MatrixBot) RefreshToken() error { - if bot.token == nil { - return fmt.Errorf("no refresh token available") - } - userData, err := api.Refresh(bot.token.RefreshToken) - if err != nil { - return err - } - bot.token = &api.Token{ - AccessToken: userData.AccessToken, - Expires: time.Now().Add(time.Duration(userData.ExpiresInMs) / 2 * time.Millisecond), - RefreshToken: userData.RefreshToken, - } - return nil -} - -func (bot *MatrixBot) Login(username string, password string, deviceId string) error { - userData, err := api.Login(username, password, deviceId) - if err != nil { - return err - } - bot.token = &api.Token{ - AccessToken: userData.AccessToken, - Expires: time.Now().Add(time.Duration(userData.ExpiresInMs) / 2 * time.Millisecond), - RefreshToken: userData.RefreshToken, - } - return nil -} - -func (bot *MatrixBot) RefreshTask() { - for true { - if bot.token != nil && time.Now().After(bot.token.Expires) { - if err := bot.RefreshToken(); err != nil { - log.Printf("error refresh token: %s\n", err.Error()) - } - } - time.Sleep(1 * time.Second) - } -} - -func (bot *MatrixBot) RegisterPusher() error { - return api.SetPusher(*bot.token, bot.pushUrl.String()) -} - -func (bot *MatrixBot) HandleFunc(command string, handler func(bot *MatrixBot, notification api.Notification) error) { - bot.handlers[command] = handler -} - -func (bot *MatrixBot) Serve(endpoint string) { - http.HandleFunc("/healthz", func(writer http.ResponseWriter, request *http.Request) { - _, _ = io.WriteString(writer, "OK\n") - }) - log.Printf("listening for push notifications on %s\n", bot.pushUrl.Path) - http.HandleFunc(bot.pushUrl.Path, func(writer http.ResponseWriter, request *http.Request) { - log.Println("Received push notification") - notification, err := api.ParseNotification(request.Body) - if err != nil { - log.Println(err.Error()) - return - } - if notification.EventId == "" { - return - } - err = api.SetReadReceipt(*bot.token, notification.RoomId, notification.EventId) - if err != nil { - log.Println(err.Error()) - return - } - command := strings.SplitN(notification.Content.Body, " ", 2)[0] - handler, ok := bot.handlers[command] - if !ok { - log.Printf("could not find handler for '%s'\n", command) - return - } - err = handler(bot, notification) - if err != nil { - log.Println(err.Error()) - return - } - writer.WriteHeader(204) - }) - fmt.Printf("listening for requests on %s\n", endpoint) - log.Fatal(http.ListenAndServe(endpoint, nil)) -}