From 871fd5fdcc4195e0cd9ca383e7ece382aba76a4e Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Tue, 2 Jun 2026 17:26:34 -0400 Subject: [PATCH] cmd/horsebot: allow acting only as api server --- cmd/horsebot/main.go | 135 +++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 74 deletions(-) diff --git a/cmd/horsebot/main.go b/cmd/horsebot/main.go index 9d7b4c4..e26d50c 100644 --- a/cmd/horsebot/main.go +++ b/cmd/horsebot/main.go @@ -27,7 +27,6 @@ import ( "zombiezen.com/go/sqlite" "zombiezen.com/go/sqlite/sqlitex" - "git.sunturtle.xyz/zephyr/horse" "git.sunturtle.xyz/zephyr/horse/mdb" ) @@ -67,14 +66,16 @@ func main() { } slog.SetDefault(slog.New(lh)) - stat, err := os.Stat(public) - if err != nil { - slog.Error("public", slog.Any("err", err)) - os.Exit(1) - } - if !stat.IsDir() { - slog.Error("public", slog.String("err", "not a directory")) - os.Exit(1) + if public != "" { + stat, err := os.Stat(public) + if err != nil { + slog.Error("public", slog.Any("err", err)) + os.Exit(1) + } + if !stat.IsDir() { + slog.Error("public", slog.String("err", "not a directory")) + os.Exit(1) + } } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) @@ -85,43 +86,48 @@ func main() { os.Exit(1) } - skills, err := mdb.Skills(ctx, db) - slog.Info("loaded skills", slog.Int("count", len(skills))) - groups, err2 := mdb.SkillGroups(ctx, db) - slog.Info("loaded skill groups", slog.Int("count", len(groups))) - if err = errors.Join(err, err2); err != nil { - slog.Error("loading data", slog.Any("err", err)) - os.Exit(1) - } - skillSrv := newSkillServer(skills, groups) - slog.Info("skill server ready") + var client *bot.Client + if tokenFile != "" { + skills, err := mdb.Skills(ctx, db) + slog.Info("loaded skills", slog.Int("count", len(skills))) + groups, err2 := mdb.SkillGroups(ctx, db) + slog.Info("loaded skill groups", slog.Int("count", len(groups))) + if err = errors.Join(err, err2); err != nil { + slog.Error("loading data", slog.Any("err", err)) + os.Exit(1) + } + skillSrv := newSkillServer(skills, groups) + slog.Info("skill server ready") - 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'}) + r := handler.New() + r.DefaultContext(func() context.Context { return ctx }) + r.Use(middleware.Go) + r.Use(logMiddleware) + r.Route("/skill", func(r handler.Router) { + r.SlashCommand("/", skillSrv.slash) + r.Autocomplete("/", skillSrv.autocomplete) + r.SelectMenuComponent("/swap", skillSrv.menu) + r.ButtonComponent("/swap/{id}", skillSrv.button) + }) - r := handler.New() - r.DefaultContext(func() context.Context { return ctx }) - r.Use(middleware.Go) - r.Use(logMiddleware) - r.Route("/skill", func(r handler.Router) { - r.SlashCommand("/", skillSrv.slash) - r.Autocomplete("/", skillSrv.autocomplete) - r.SelectMenuComponent("/swap", skillSrv.menu) - r.ButtonComponent("/swap/{id}", skillSrv.button) - }) - 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) + slog.Info("connect", slog.String("disgo", disgo.Version)) + 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'}) + 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) + } } mux := http.NewServeMux() - mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public)))) + if public != "" { + mux.Handle("GET /", httpmiddle.Compress(5)(http.FileServerFS(os.DirFS(public)))) + } mux.Handle("GET /api/global/affinity", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.AffinitySummary))) mux.Handle("GET /api/global/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters))) mux.Handle("GET /api/global/conversation", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Conversations))) @@ -162,23 +168,28 @@ func main() { slog.Error("HTTP server closed", slog.Any("err", err)) }() - if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil { - slog.Error("syncing commands", slog.Any("err", err)) - os.Exit(1) + if client != nil { + if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil { + slog.Error("syncing commands", slog.Any("err", err)) + os.Exit(1) + } + + slog.Info("start gateway") + if err := client.OpenGateway(ctx); err != nil { + slog.Error("starting gateway", slog.Any("err", err)) + stop() + } } - slog.Info("start gateway") - if err := client.OpenGateway(ctx); err != nil { - slog.Error("starting gateway", slog.Any("err", err)) - stop() - } slog.Info("ready") <-ctx.Done() stop() ctx, stop = context.WithTimeout(context.Background(), 5*time.Second) defer stop() - client.Close(ctx) + if client != nil { + client.Close(ctx) + } if err := srv.Shutdown(ctx); err != nil { slog.Error("HTTP API shutdown", slog.Any("err", err)) os.Exit(1) @@ -204,30 +215,6 @@ var commands = []discord.ApplicationCommandCreate{ }, } -func loadSkills(file string) ([]horse.Skill, error) { - b, err := os.ReadFile(file) - if err != nil { - return nil, err - } - var skills []horse.Skill - if err := json.Unmarshal(b, &skills); err != nil { - return nil, err - } - return skills, nil -} - -func loadSkillGroups(file string) ([]horse.SkillGroup, error) { - b, err := os.ReadFile(file) - if err != nil { - return nil, err - } - var groups []horse.SkillGroup - if err := json.Unmarshal(b, &groups); err != nil { - return nil, err - } - return groups, nil -} - func dataroute[T any](ctx context.Context, db *sqlitex.Pool, f func(context.Context, *sqlitex.Pool) ([]T, error)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { data, err := f(ctx, db)