cmd/horsebot: allow acting only as api server

This commit is contained in:
2026-06-02 17:26:34 -04:00
parent e3dd37f515
commit 871fd5fdcc

View File

@@ -27,7 +27,6 @@ import (
"zombiezen.com/go/sqlite" "zombiezen.com/go/sqlite"
"zombiezen.com/go/sqlite/sqlitex" "zombiezen.com/go/sqlite/sqlitex"
"git.sunturtle.xyz/zephyr/horse"
"git.sunturtle.xyz/zephyr/horse/mdb" "git.sunturtle.xyz/zephyr/horse/mdb"
) )
@@ -67,14 +66,16 @@ func main() {
} }
slog.SetDefault(slog.New(lh)) slog.SetDefault(slog.New(lh))
stat, err := os.Stat(public) if public != "" {
if err != nil { stat, err := os.Stat(public)
slog.Error("public", slog.Any("err", err)) if err != nil {
os.Exit(1) slog.Error("public", slog.Any("err", err))
} os.Exit(1)
if !stat.IsDir() { }
slog.Error("public", slog.String("err", "not a directory")) if !stat.IsDir() {
os.Exit(1) slog.Error("public", slog.String("err", "not a directory"))
os.Exit(1)
}
} }
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
@@ -85,43 +86,48 @@ func main() {
os.Exit(1) os.Exit(1)
} }
skills, err := mdb.Skills(ctx, db) var client *bot.Client
slog.Info("loaded skills", slog.Int("count", len(skills))) if tokenFile != "" {
groups, err2 := mdb.SkillGroups(ctx, db) skills, err := mdb.Skills(ctx, db)
slog.Info("loaded skill groups", slog.Int("count", len(groups))) slog.Info("loaded skills", slog.Int("count", len(skills)))
if err = errors.Join(err, err2); err != nil { groups, err2 := mdb.SkillGroups(ctx, db)
slog.Error("loading data", slog.Any("err", err)) slog.Info("loaded skill groups", slog.Int("count", len(groups)))
os.Exit(1) if err = errors.Join(err, err2); err != nil {
} slog.Error("loading data", slog.Any("err", err))
skillSrv := newSkillServer(skills, groups) os.Exit(1)
slog.Info("skill server ready") }
skillSrv := newSkillServer(skills, groups)
slog.Info("skill server ready")
token, err := os.ReadFile(tokenFile) r := handler.New()
if err != nil { r.DefaultContext(func() context.Context { return ctx })
slog.Error("reading token", slog.Any("err", err)) r.Use(middleware.Go)
os.Exit(1) r.Use(logMiddleware)
} r.Route("/skill", func(r handler.Router) {
token = bytes.TrimSuffix(token, []byte{'\n'}) r.SlashCommand("/", skillSrv.slash)
r.Autocomplete("/", skillSrv.autocomplete)
r.SelectMenuComponent("/swap", skillSrv.menu)
r.ButtonComponent("/swap/{id}", skillSrv.button)
})
r := handler.New() slog.Info("connect", slog.String("disgo", disgo.Version))
r.DefaultContext(func() context.Context { return ctx }) token, err := os.ReadFile(tokenFile)
r.Use(middleware.Go) if err != nil {
r.Use(logMiddleware) slog.Error("reading token", slog.Any("err", err))
r.Route("/skill", func(r handler.Router) { os.Exit(1)
r.SlashCommand("/", skillSrv.slash) }
r.Autocomplete("/", skillSrv.autocomplete) token = bytes.TrimSuffix(token, []byte{'\n'})
r.SelectMenuComponent("/swap", skillSrv.menu) client, err = disgo.New(string(token), bot.WithDefaultGateway(), bot.WithEventListeners(r))
r.ButtonComponent("/swap/{id}", skillSrv.button) if err != nil {
}) slog.Error("building bot", slog.Any("err", err))
slog.Info("connect", slog.String("disgo", disgo.Version)) os.Exit(1)
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 := 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/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/character", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Characters)))
mux.Handle("GET /api/global/conversation", httpmiddle.Compress(5)(dataroute(ctx, db, mdb.Conversations))) 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)) slog.Error("HTTP server closed", slog.Any("err", err))
}() }()
if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil { if client != nil {
slog.Error("syncing commands", slog.Any("err", err)) if err := handler.SyncCommands(client, commands, nil, rest.WithCtx(ctx)); err != nil {
os.Exit(1) 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") slog.Info("ready")
<-ctx.Done() <-ctx.Done()
stop() stop()
ctx, stop = context.WithTimeout(context.Background(), 5*time.Second) ctx, stop = context.WithTimeout(context.Background(), 5*time.Second)
defer stop() defer stop()
client.Close(ctx) if client != nil {
client.Close(ctx)
}
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
slog.Error("HTTP API shutdown", slog.Any("err", err)) slog.Error("HTTP API shutdown", slog.Any("err", err))
os.Exit(1) 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 { 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) { return func(w http.ResponseWriter, r *http.Request) {
data, err := f(ctx, db) data, err := f(ctx, db)