From 595e7c6f12dd3912fbcc5704e02448af2d3c530e Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Fri, 28 Feb 2025 18:03:30 +0100
Subject: [PATCH] feat: split framework from prototype

---
 .gitlab-ci.yml |   9 ---
 Dockerfile     |  12 ----
 go.mod         |  14 +---
 go.sum         |  14 ----
 main.go        | 189 +++++++++++++++++++++++--------------------------
 matrixbot.go   | 111 -----------------------------
 6 files changed, 89 insertions(+), 260 deletions(-)
 delete mode 100644 .gitlab-ci.yml
 delete mode 100644 Dockerfile
 delete mode 100644 matrixbot.go

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 167716e..0000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-container:
-  stage: build
-  image:
-    name: gcr.io/kaniko-project/executor:debug
-    entrypoint: [ "" ]
-  script:
-    - mkdir -p /kaniko/.docker
-    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
-    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA} --destination $CI_REGISTRY_IMAGE:latest
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index ea5101d..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,12 +0,0 @@
-FROM golang:1.24-alpine AS go_builder
-
-WORKDIR /src
-COPY go.* ./
-RUN go mod download
-COPY . ./
-RUN CGO_ENABLED=0 GOOS=linux go build -o app
-
-FROM scratch
-COPY --from=go_builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
-COPY --from=go_builder /src/app /app
-ENTRYPOINT ["/app"]
diff --git a/go.mod b/go.mod
index 44a23d6..f0174b1 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 0425cac..7790d7c 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 218ce4d..9a5b91d 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 2604f8f..0000000
--- 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))
-}
-- 
GitLab