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) }