Files
horsebot/main.go

136 lines
3.4 KiB
Go

package main
import (
"bytes"
"context"
"flag"
"log/slog"
"os"
"os/signal"
"strconv"
"strings"
"time"
"git.sunturtle.xyz/zephyr/horse/horse"
"git.sunturtle.xyz/zephyr/horse/horse/global"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/handler"
"github.com/disgoorg/disgo/handler/middleware"
"github.com/disgoorg/disgo/rest"
)
func main() {
var (
appID string
pubkey string
tokenFile string
)
flag.StringVar(&appID, "id", "", "Discord application ID")
flag.StringVar(&pubkey, "key", "", "Discord public key")
flag.StringVar(&tokenFile, "token", "", "`file` containing the Discord bot token")
flag.Parse()
token, err := os.ReadFile(tokenFile)
if err != nil {
slog.Error("reading token", slog.Any("err", err))
os.Exit(1)
}
token = bytes.TrimSuffix(token, []byte{'\n'})
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
r := handler.New()
r.DefaultContext(func() context.Context { return ctx })
r.Use(middleware.Go)
r.Use(middleware.Logger)
r.Route("/skill", func(r handler.Router) {
r.SlashCommand("/", skillHandler)
r.Autocomplete("/", skillAutocomplete)
// TODO(zeph): button handler
})
slog.Info("connect", slog.String("disgo", disgo.Version))
client, err := disgo.New(string(token), bot.WithDefaultGateway(), bot.WithEventListeners(r))
if err != nil {
slog.Error("building bot", slog.Any("err", err))
os.Exit(1)
}
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
slog.Error("syncing commands", slog.Any("err", err))
os.Exit(1)
}
if err := client.OpenGateway(ctx); err != nil {
slog.Error("opening gateway", slog.Any("err", err))
stop()
}
slog.Info("running")
<-ctx.Done()
stop()
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
defer stop()
client.Close(ctx)
}
var commands = []discord.ApplicationCommandCreate{
discord.SlashCommandCreate{
Name: "skill",
Description: "Umamusume skill data",
Options: []discord.ApplicationCommandOption{
discord.ApplicationCommandOptionString{
Name: "query",
Description: "Skill name or ID",
Required: true,
Autocomplete: true,
},
},
},
}
func skillHandler(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
q := data.String("query")
id, err := strconv.ParseInt(q, 10, 32)
var s horse.Skill
if err == nil {
// note inverted condition; this is when we have an id
s = global.AllSkills[horse.SkillID(id)]
}
if s.ID == 0 {
// Either we weren't given a number or the number doesn't match any skill ID.
id, ok := global.SkillNameToID[q]
if !ok {
// No such skill.
m := discord.MessageCreate{
Content: "No such skill.",
Flags: discord.MessageFlagEphemeral,
}
return e.CreateMessage(m)
}
s = global.AllSkills[id]
}
// TODO(zeph): search conditions and effects, give a list
m := discord.MessageCreate{
Components: []discord.LayoutComponent{RenderSkill(s)},
Flags: discord.MessageFlagIsComponentsV2,
}
return e.CreateMessage(m)
}
func skillAutocomplete(e *handler.AutocompleteEvent) error {
q := strings.ToLower(e.Data.String("query"))
r := make([]discord.AutocompleteChoice, 0, 25)
for k, _ := range global.SkillNameToID {
if strings.HasPrefix(strings.ToLower(k), q) {
r = append(r, discord.AutocompleteChoiceString{Name: k, Value: k})
if len(r) == cap(r) {
break
}
}
}
return e.AutocompleteResult(r)
}