matrix-bot/bot/bot.go
shinya 64b6e71b0e
Some checks failed
Deploy Matrix Bot / deploy (push) Waiting to run
Docker Build / build (push) Failing after 20m2s
new init without secrets
2026-03-04 22:00:42 +01:00

210 lines
5.4 KiB
Go

package bot
import (
"bytes"
"context"
"log"
"matrix-bot/game"
"matrix-bot/wordleimg"
"matrix-bot/words"
"os"
"strconv"
"strings"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
)
var dict *words.Dictionary
type roomUserPair struct {
room string
user string
}
var wordleGames = make(map[roomUserPair]game.Wordle)
var userSettings = make(map[string]map[string]string)
var defaultSettings = map[string]string{
"wordletheme": "dark",
}
func Load() {
var err error
wordListPath := os.Getenv("MATRIX_WORDLIST")
if wordListPath == "" {
wordListPath = "sowpods.csv"
}
dict, err = words.New(wordListPath)
if err != nil {
log.Fatal(err)
}
}
func HandleCommand(body string, sender string, roomID string,
ctx context.Context, client *mautrix.Client, evt *event.Event, replyTo *event.RelatesTo) []interface{} {
pair := roomUserPair{roomID, sender}
part := strings.Fields(strings.ToLower(body))
_, e := userSettings[sender]
if !e {
userSettings[sender] = make(map[string]string)
for key, value := range defaultSettings {
userSettings[sender][key] = value
}
}
us := userSettings[sender]
if equals(part[0], "!settings", "!setts", "!stns", "!?") {
if len(part) < 2 {
} else if equals(part[1], "set", "add", "s", "a") {
if len(part) == 4 {
userSettings[sender][part[2]] = part[3]
us = userSettings[sender]
}
} else if equals(part[1], "delete", "del", "remove", "rem", "d", "r") {
if len(part) == 3 {
delete(userSettings[sender], part[2])
}
}
b := strings.Builder{}
b.WriteString("Your settings: ")
for k, s := range us {
b.WriteString("\n" + k + " : " + s)
}
return jia(txt(b.String(), replyTo))
} else if equals(part[0], "!wordle", "!wrdl", "!w") {
wg, wgEx := wordleGames[pair]
wordleState := func() event.MessageEventContent {
state := wg.GetState()
if setting(us, "wordletheme") == "ascii" {
return txt(state, nil)
} else {
theme, e := wordleimg.Themes[setting(us, "wordletheme")]
if !e {
theme = wordleimg.Themes["dark"]
}
imgData, w, h, err := wordleimg.RenderHistoryImage(state, theme, len(wg.GetSecret()), 6)
if err != nil {
log.Println("Image render error:", err)
}
return img(imgData, w, h, state, ctx, client)
}
}
if len(part) < 2 {
part = append(part, "daily")
}
if equals(part[1], "daily", "day", "d") {
if wgEx {
return jia(txt("Wordle:\nAlready exist", replyTo))
}
word := dict.DailyWord(5)
wordleGames[pair] = game.NewWordle(word, 6)
return jia(txt("Wordle:\nDaily started ("+roomID+" "+sender+")\nLength: "+
strconv.Itoa(len(word))+"\n[CAN BE PLAYED ONCE A DAY, DO NOT END!]", replyTo))
} else if equals(part[1], "normal", "play", "p") {
if wgEx {
return jia(txt("Wordle:\nAlready exist", replyTo))
}
length := 5
if len(part) > 2 {
if l, err := strconv.Atoi(part[2]); err == nil {
length = l
}
}
word, err := dict.RandomWord(length)
if err != nil {
return jia(txt("Wordle:\nNo words of that length", replyTo))
}
wordleGames[pair] = game.NewWordle(word, 6)
return jia(txt("Wordle:\nInitilized ("+roomID+" "+sender+")\nLength: "+strconv.Itoa(length), replyTo))
} else if equals(part[1], "guess", "gus", "g") && len(part) > 2 {
if !wgEx {
return jia(txt("Wordle:\nNot found (use !wordle play)", replyTo))
} else if !wg.ValidLength(part[2]) {
return jia(txt("Wordle:\nWord guess length is bad", replyTo))
} else if !dict.Contains(part[2]) {
return jia(txt("Wordle:\nSorry but idk that word", replyTo))
}
sig := wg.Guess(part[2])
if sig == 0 {
return jia(txt("Wordle:", replyTo), wordleState())
}
delete(wordleGames, pair)
switch sig {
case -1:
return jia(txt("Wordle:\nEnded in lost, word was "+wg.GetSecret(), replyTo), wordleState())
case 1:
return jia(txt("Wordle:\nYou guessed right!", replyTo), wordleState())
}
} else if equals(part[1], "status", "stat", "s") {
if !wgEx {
return jia(txt("Wordle:\nNone found", replyTo))
}
return jia(txt("Wordle:", replyTo), wordleState())
} else if equals(part[1], "forfit", "end", "e") {
if !wgEx {
return jia(txt("Wordle:\nNone to end", replyTo))
}
delete(wordleGames, pair)
return jia(txt("Wordle:\nEnded", replyTo))
}
}
return nil
}
func jia(i ...interface{}) []interface{} {
return i
}
func txt(body string, relates *event.RelatesTo) event.MessageEventContent {
return event.MessageEventContent{
MsgType: event.MsgText,
Body: body,
RelatesTo: relates,
}
}
func img(data []byte, width int, height int, body string, ctx context.Context, client *mautrix.Client) event.MessageEventContent {
uploadResp, err := client.UploadMedia(
ctx,
mautrix.ReqUploadMedia{
FileName: "temp.png",
Content: bytes.NewReader(data),
ContentType: "image/png",
},
)
if err != nil {
log.Println("Upload error:", err)
return event.MessageEventContent{}
}
return event.MessageEventContent{
MsgType: event.MsgImage,
Body: body,
URL: uploadResp.ContentURI.CUString(),
Info: &event.FileInfo{
MimeType: "image/png",
Size: len(data),
Height: height,
Width: width,
},
}
}
func setting(us map[string]string, key string) string {
if v, e := us[key]; e {
return v
}
return ""
}
func equals(body string, prefixes ...string) bool {
for _, prefix := range prefixes {
if strings.Compare(body, prefix) == 0 {
return true
}
}
return false
}