diff --git a/Dockerfile b/Dockerfile index 2020de4..2a3d0c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,8 @@ ENTRYPOINT ["/work/backend"] CMD ["serve"] FROM docker.io/library/debian:13-slim AS native +RUN apt-get -y update && apt-get -y upgrade && \ + apt-get -y install ca-certificates COPY backend /work/backend WORKDIR /work diff --git a/Makefile b/Makefile index 050fd92..b44a360 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ docker-build-run: -t go2025/backend:latest docker: - $(COMPOSE) up -d --force-recreate backend + $(COMPOSE) up -d --force-recreate backend dcbot $(TARGET): $(SOURCE) $(GO_ENV) go build -o $@ diff --git a/bot/bot.go b/bot/bot.go new file mode 100644 index 0000000..a4b5fe8 --- /dev/null +++ b/bot/bot.go @@ -0,0 +1,168 @@ +package bot + +import ( + "context" + "log" + + "gitea.konchin.com/go2025/backend/tracing" + "github.com/bwmarrin/discordgo" + "github.com/go-resty/resty/v2" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +type CommandHandler = func( + *discordgo.Session, *discordgo.InteractionCreate) + +type Command interface { + ApplicationCommand() *discordgo.ApplicationCommand + Handler() CommandHandler +} + +type Bot struct { + session *discordgo.Session + commands map[string]Command + aliases map[string]int64 + + Client *resty.Client +} + +func New() (*Bot, error) { + // Create Discord session + session, err := discordgo.New( + "Bot " + viper.GetString("discord-bot-token")) + if err != nil { + return nil, err + } + client := resty.New() + client.SetBaseURL(viper.GetString("api-endpoint")) + client.SetAuthToken(viper.GetString("preshared-key")) + + bot := &Bot{ + session: session, + commands: make(map[string]Command), + + Client: client, + } + + bot.registerHandlers() + + go bot.fetchAliases() + + // Set intents - only need guild messages for the ciallo listener + session.Identify.Intents = discordgo.IntentsGuildMessages | + discordgo.IntentsDirectMessages | + discordgo.IntentsMessageContent + + return bot, nil +} + +func (b *Bot) registerSlashCommands(ctx context.Context) error { + for _, cmd := range b.commands { + _, err := b.session.ApplicationCommandCreate( + b.session.State.User.ID, "", cmd.ApplicationCommand()) + if err != nil { + tracing.Logger.Ctx(ctx). + Error("failed to create command", + zap.String("command", cmd.ApplicationCommand().Name), + zap.Error(err)) + return err + } + } + + return nil +} + +func (b *Bot) clearSlashCommands(guildID string) error { + commands, err := b.session.ApplicationCommands(b.session.State.User.ID, guildID) + if err != nil { + return err + } + + for _, cmd := range commands { + err := b.session.ApplicationCommandDelete(b.session.State.User.ID, guildID, cmd.ID) + if err != nil { + log.Printf("Failed to delete command %s: %v", cmd.Name, err) + } + } + return nil +} + +func (b *Bot) registerHandlers() { + b.session.AddHandler(b.onReady) + b.session.AddHandler(b.onMessageCreate) + b.session.AddHandler(b.onInteractionCreate) +} + +func (b *Bot) RegisterCommand(newCommand func(*Bot) Command) { + command := newCommand(b) + b.commands[command.ApplicationCommand().Name] = command +} + +func (b *Bot) onReady( + s *discordgo.Session, + event *discordgo.Ready, +) { + ctx := context.Background() + tracing.Logger.Ctx(ctx). + Info("logged in", + zap.String("username", s.State.User.Username), + zap.String("discriminator", s.State.User.Discriminator)) + + // For development: set your guild ID here for instant updates + // For production: use "" for global commands + guildID := "1377176828833169468" // Replace with your Discord server ID for faster testing + + // clear slash commands + if err := b.clearSlashCommands(guildID); err != nil { + log.Printf("Error clearing slash commands: %v", err) + } + + // Register slash commands + if err := b.registerSlashCommands(ctx); err != nil { + tracing.Logger.Ctx(ctx). + Error("failed to register slash commands", + zap.Error(err)) + return + } + + // Set bot status + err := s.UpdateGameStatus(0, "/ping to check status") + if err != nil { + tracing.Logger.Ctx(ctx). + Error("failed to set status", + zap.Error(err)) + return + } +} + +func (b *Bot) onInteractionCreate( + s *discordgo.Session, + i *discordgo.InteractionCreate, +) { + if i.Type != discordgo.InteractionApplicationCommand { + return + } + + command, ok := b.commands[i.ApplicationCommandData().Name] + if ok { + tracing.Logger.Ctx(context.Background()). + Info("run command", + zap.String("name", i.ApplicationCommandData().Name)) + command.Handler()(s, i) + } else { + tracing.Logger.Ctx(context.Background()). + Error("command not exist", + zap.String("name", i.ApplicationCommandData().Name)) + } +} + +func (b *Bot) Start() error { + return b.session.Open() +} + +func (b *Bot) Stop() { + if b.session != nil { + b.session.Close() + } +} diff --git a/bot/commands/echo.go b/bot/commands/echo.go new file mode 100644 index 0000000..c61a294 --- /dev/null +++ b/bot/commands/echo.go @@ -0,0 +1,53 @@ +package commands + +import ( + "gitea.konchin.com/go2025/backend/bot" + "github.com/bwmarrin/discordgo" +) + +type EchoCommand struct { + bot *bot.Bot +} + +func NewEchoCommand(bot *bot.Bot) bot.Command { + return &EchoCommand{bot: bot} +} + +func (self *EchoCommand) ApplicationCommand() *discordgo.ApplicationCommand { + return &discordgo.ApplicationCommand{ + Name: "echo", + Description: "Bot repeats what you say", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "message", + Description: "Message to echo", + Required: true, + }, + }, + } +} + +func (self *EchoCommand) Handler() bot.CommandHandler { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) { + options := i.ApplicationCommandData().Options + if len(options) == 0 { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "No message provided!", + }, + }) + return + } + + message := options[0].StringValue() + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: message, + }, + }) + } +} diff --git a/bot/commands/greet.go b/bot/commands/greet.go new file mode 100644 index 0000000..340beee --- /dev/null +++ b/bot/commands/greet.go @@ -0,0 +1,43 @@ +package commands + +import ( + "fmt" + + "gitea.konchin.com/go2025/backend/bot" + "github.com/bwmarrin/discordgo" +) + +type GreetCommand struct { + bot *bot.Bot +} + +func NewGreetCommand(bot *bot.Bot) bot.Command { + return &GreetCommand{bot: bot} +} + +func (self *GreetCommand) ApplicationCommand() *discordgo.ApplicationCommand { + return &discordgo.ApplicationCommand{ + Name: "greet", + Description: "Get a friendly greeting", + } +} + +func (self *GreetCommand) Handler() bot.CommandHandler { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) { + var username string + if i.Member != nil { + username = i.Member.User.Username + } else if i.User != nil { + username = i.User.Username + } else { + username = "Unknown" + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Ciallo, %s!", username), + }, + }) + } +} diff --git a/bot/commands/ping.go b/bot/commands/ping.go new file mode 100644 index 0000000..29d5f79 --- /dev/null +++ b/bot/commands/ping.go @@ -0,0 +1,32 @@ +package commands + +import ( + "gitea.konchin.com/go2025/backend/bot" + "github.com/bwmarrin/discordgo" +) + +type PingCommand struct { + bot *bot.Bot +} + +func NewPingCommand(bot *bot.Bot) bot.Command { + return &PingCommand{bot: bot} +} + +func (self *PingCommand) ApplicationCommand() *discordgo.ApplicationCommand { + return &discordgo.ApplicationCommand{ + Name: "ping", + Description: "Check if bot is responsive", + } +} + +func (self *PingCommand) Handler() bot.CommandHandler { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "pong", + }, + }) + } +} diff --git a/bot/commands/web.go b/bot/commands/web.go new file mode 100644 index 0000000..76c574f --- /dev/null +++ b/bot/commands/web.go @@ -0,0 +1,68 @@ +package commands + +import ( + "context" + "net/http" + + "gitea.konchin.com/go2025/backend/bot" + "gitea.konchin.com/go2025/backend/handlers/auth" + "gitea.konchin.com/go2025/backend/tracing" + "github.com/bwmarrin/discordgo" + "go.uber.org/zap" +) + +type WebCommand struct { + bot *bot.Bot +} + +func NewWebCommand(bot *bot.Bot) bot.Command { + return &WebCommand{bot: bot} +} + +func (self *WebCommand) ApplicationCommand() *discordgo.ApplicationCommand { + return &discordgo.ApplicationCommand{ + Name: "web", + Description: "Get a login link to the web interface", + } +} + +func (self *WebCommand) Handler() bot.CommandHandler { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) { + var userID string + if i.Member != nil { + userID = i.Member.User.ID + } else if i.User != nil { + userID = i.User.ID + } + + // Call backend API + var res auth.PostGenLoginUrlOutput + resp, err := self.bot.Client.R(). + SetBody(auth.PostGenLoginUrlInput{UserId: userID}). + SetResult(&res). + Post("/bot/auth/gen-login-url") + if err != nil || resp.StatusCode() != http.StatusOK { + tracing.Logger.Ctx(context.Background()). + Error("failed to generate login url", + zap.Error(err)) + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "❌ Failed to generate login URL", + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + return + } + + content := "🔗 **Click here to access the web page:**\n"+ + res.LoginUrl + "\n\n" + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: content, + Flags: discordgo.MessageFlagsEphemeral, + }, + }) + } +} diff --git a/bot/onMessageCreate.go b/bot/onMessageCreate.go new file mode 100644 index 0000000..ad78233 --- /dev/null +++ b/bot/onMessageCreate.go @@ -0,0 +1,69 @@ +package bot + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "gitea.konchin.com/go2025/backend/handlers/api" + "gitea.konchin.com/go2025/backend/tracing" + "github.com/bwmarrin/discordgo" + "github.com/spf13/viper" + "go.uber.org/zap" + "golang.org/x/exp/rand" +) + +func (b *Bot) fetchAliases() { + for { + var res []api.GetAliasesOutputAlias + resp, err := b.Client.R(). + SetResult(&res). + Get("/bot/api/aliases") + if err == nil && resp.StatusCode() == http.StatusOK { + aliases := make(map[string]int64) + for _, alias := range res { + aliases[alias.Name] = alias.Id + } + b.aliases = aliases + + tracing.Logger.Ctx(context.Background()). + Info("nmsl", + zap.String("aliases", fmt.Sprintf("%+v", aliases))) + } + + time.Sleep(10 * time.Second) + } +} + +func (b *Bot) onMessageCreate( + s *discordgo.Session, + m *discordgo.MessageCreate, +) { + // Ignore messages from the bot itself + if m.Author.ID == s.State.User.ID { + return + } + + key := strings.ToLower(strings.TrimSpace(m.Content)) + tracing.Logger.Ctx(context.Background()). + Info("wtf", + zap.String("key", key)) + + if id, ok := b.aliases[key]; ok { + var res []api.GetImagesOutputImage + resp, err := b.Client.R(). + SetResult(&res). + SetQueryParam("aliases", strconv.FormatInt(id, 10)). + Get("/bot/api/images") + if err == nil && resp.StatusCode() == http.StatusOK { + image := res[rand.Intn(len(res))] + s.ChannelMessageSend(m.ChannelID, + fmt.Sprintf("%s/img/%d.%s", + viper.GetString("external-url"), + image.Id, image.Extension)) + } + } +} diff --git a/cmds/dcbot.go b/cmds/dcbot.go new file mode 100644 index 0000000..90a4a69 --- /dev/null +++ b/cmds/dcbot.go @@ -0,0 +1,75 @@ +package cmds + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "gitea.konchin.com/go2025/backend/bot" + "gitea.konchin.com/go2025/backend/bot/commands" + "gitea.konchin.com/go2025/backend/tracing" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +var dcbotCmd = &cobra.Command{ + Use: "dcbot", + Run: func(cmd *cobra.Command, args []string) { + ctx := cmd.Context() + + appname := "go2025-dcbot" + tracing.InitTracer(appname) + if viper.GetString("uptrace-dsn") != "" { + tracing.InitUptrace(appname) + defer tracing.DeferUptrace(ctx) + } + + // Initialize bot + discordBot, err := bot.New() + if err != nil { + tracing.Logger.Ctx(ctx). + Panic("failed to create bot", + zap.Error(err)) + panic(err) + } + + discordBot.RegisterCommand(commands.NewEchoCommand) + discordBot.RegisterCommand(commands.NewGreetCommand) + discordBot.RegisterCommand(commands.NewPingCommand) + discordBot.RegisterCommand(commands.NewWebCommand) + + // Start bot + if err := discordBot.Start(); err != nil { + tracing.Logger.Ctx(ctx). + Panic("failed to start bot", + zap.Error(err)) + panic(err) + } + fmt.Println("Bot is now running. Press CTRL-C to exit.") + defer discordBot.Stop() + + // Wait for interrupt signal + sc := make(chan os.Signal, 1) + signal.Notify(sc, + syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-sc + }, +} + +func init() { + dcbotCmd.Flags(). + String("preshared-key", "poop", "preshared key") + dcbotCmd.Flags(). + String("discord-bot-token", "", "discord bot token") + dcbotCmd.Flags(). + String("api-endpoint", "http://backend:8080", "api endpoint") + dcbotCmd.Flags(). + String("external-url", "http://localhost:8080", "external url") + + dcbotCmd.Flags(). + Bool("zap-production", true, "Toggle production log format") + dcbotCmd.Flags(). + String("uptrace-dsn", "", "Uptrace DSN (disabled by default)") +} diff --git a/cmds/root.go b/cmds/root.go index 9d097c0..5367c32 100644 --- a/cmds/root.go +++ b/cmds/root.go @@ -22,4 +22,5 @@ func init() { RootCmd.AddCommand(serveCmd) RootCmd.AddCommand(genTokenCmd) + RootCmd.AddCommand(dcbotCmd) } diff --git a/cmds/serve.go b/cmds/serve.go index fed585d..36e559b 100644 --- a/cmds/serve.go +++ b/cmds/serve.go @@ -116,8 +116,16 @@ var serveCmd = &cobra.Command{ authGroup := backend.NewGroup("/auth") authGroup.POST("/login", auths.PostLogin) - authGroup.POST("/gen-login-url", - midHandlers.CheckPresharedKey(auths.PostGenLoginUrl)) + + botGroup := backend.NewGroup("/bot"). + Use(midHandlers.CheckPresharedKey) + + botApiGroup := botGroup.NewGroup("/api") + botApiGroup.GET("/aliases", apis.GetAliases) + botApiGroup.GET("/images", apis.GetImages) + + botAuthGroup := botGroup.NewGroup("/auth") + botAuthGroup.POST("/gen-login-url", auths.PostGenLoginUrl) imgGroup := backend.NewGroup("/img") imgGroup.GET("/:filename", imgs.Get) diff --git a/docker-compose.yml b/docker-compose.yml index 9316c1b..7cdf096 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,6 +74,17 @@ services: - uptrace restart: unless-stopped + dcbot: + image: go2025/backend:latest + command: ["dcbot"] + env_file: + - path: ./.env + required: false + depends_on: + - backend + restart: unless-stopped + + volumes: redis: {} postgres: {} diff --git a/docs/docs.go b/docs/docs.go index b79cf31..1ad7a24 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -55,7 +55,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/api.getAliasesOutputAlias" + "$ref": "#/definitions/api.GetAliasesOutputAlias" } } }, @@ -186,7 +186,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/api.getImagesOutputImage" + "$ref": "#/definitions/api.GetImagesOutputImage" } } }, @@ -208,7 +208,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/auth.postGenLoginUrlInput" + "$ref": "#/definitions/auth.PostGenLoginUrlInput" } } ], @@ -216,7 +216,7 @@ const docTemplate = `{ "200": { "description": "Payload", "schema": { - "$ref": "#/definitions/auth.postGenLoginUrlOutput" + "$ref": "#/definitions/auth.PostGenLoginUrlOutput" } }, "400": { @@ -247,7 +247,7 @@ const docTemplate = `{ } }, "definitions": { - "api.getAliasesOutputAlias": { + "api.GetAliasesOutputAlias": { "type": "object", "properties": { "id": { @@ -258,7 +258,7 @@ const docTemplate = `{ } } }, - "api.getImagesOutputImage": { + "api.GetImagesOutputImage": { "type": "object", "properties": { "aliasesIds": { @@ -309,7 +309,7 @@ const docTemplate = `{ } } }, - "auth.postGenLoginUrlInput": { + "auth.PostGenLoginUrlInput": { "type": "object", "properties": { "userId": { @@ -317,7 +317,7 @@ const docTemplate = `{ } } }, - "auth.postGenLoginUrlOutput": { + "auth.PostGenLoginUrlOutput": { "type": "object", "properties": { "loginUrl": { diff --git a/docs/swagger.json b/docs/swagger.json index 0e07c19..8ae47dd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -47,7 +47,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/api.getAliasesOutputAlias" + "$ref": "#/definitions/api.GetAliasesOutputAlias" } } }, @@ -178,7 +178,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/api.getImagesOutputImage" + "$ref": "#/definitions/api.GetImagesOutputImage" } } }, @@ -200,7 +200,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/auth.postGenLoginUrlInput" + "$ref": "#/definitions/auth.PostGenLoginUrlInput" } } ], @@ -208,7 +208,7 @@ "200": { "description": "Payload", "schema": { - "$ref": "#/definitions/auth.postGenLoginUrlOutput" + "$ref": "#/definitions/auth.PostGenLoginUrlOutput" } }, "400": { @@ -239,7 +239,7 @@ } }, "definitions": { - "api.getAliasesOutputAlias": { + "api.GetAliasesOutputAlias": { "type": "object", "properties": { "id": { @@ -250,7 +250,7 @@ } } }, - "api.getImagesOutputImage": { + "api.GetImagesOutputImage": { "type": "object", "properties": { "aliasesIds": { @@ -301,7 +301,7 @@ } } }, - "auth.postGenLoginUrlInput": { + "auth.PostGenLoginUrlInput": { "type": "object", "properties": { "userId": { @@ -309,7 +309,7 @@ } } }, - "auth.postGenLoginUrlOutput": { + "auth.PostGenLoginUrlOutput": { "type": "object", "properties": { "loginUrl": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 081a8c7..95e0059 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,13 +1,13 @@ basePath: / definitions: - api.getAliasesOutputAlias: + api.GetAliasesOutputAlias: properties: id: type: integer name: type: string type: object - api.getImagesOutputImage: + api.GetImagesOutputImage: properties: aliasesIds: items: @@ -40,12 +40,12 @@ definitions: type: string type: array type: object - auth.postGenLoginUrlInput: + auth.PostGenLoginUrlInput: properties: userId: type: string type: object - auth.postGenLoginUrlOutput: + auth.PostGenLoginUrlOutput: properties: loginUrl: type: string @@ -88,7 +88,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/api.getAliasesOutputAlias' + $ref: '#/definitions/api.GetAliasesOutputAlias' type: array "400": description: Bad Request @@ -174,7 +174,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/api.getImagesOutputImage' + $ref: '#/definitions/api.GetImagesOutputImage' type: array "400": description: Bad Request @@ -188,12 +188,12 @@ paths: name: payload required: true schema: - $ref: '#/definitions/auth.postGenLoginUrlInput' + $ref: '#/definitions/auth.PostGenLoginUrlInput' responses: "200": description: Payload schema: - $ref: '#/definitions/auth.postGenLoginUrlOutput' + $ref: '#/definitions/auth.PostGenLoginUrlOutput' "400": description: Bad Request /auth/login: diff --git a/go.mod b/go.mod index 0dba92c..165ea99 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitea.konchin.com/go2025/backend go 1.25.4 require ( + github.com/bwmarrin/discordgo v0.29.0 github.com/go-resty/resty/v2 v2.17.0 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/minio/minio-go/v7 v7.0.97 @@ -21,6 +22,7 @@ require ( go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/zap v1.27.1 + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 ) require ( @@ -38,6 +40,7 @@ require ( github.com/go-openapi/swag v0.19.15 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -80,13 +83,13 @@ require ( go.opentelemetry.io/proto/otlp v1.8.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect + golang.org/x/tools v0.40.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect google.golang.org/grpc v1.75.1 // indirect diff --git a/go.sum b/go.sum index 9f0b8f2..fd4c0df 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= +github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -44,6 +46,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -186,28 +190,33 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ= diff --git a/go.work.sum b/go.work.sum index 71bc6cd..db3398b 100644 --- a/go.work.sum +++ b/go.work.sum @@ -29,8 +29,12 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/handlers/api/getAliases.go b/handlers/api/getAliases.go index 7442ff3..62b313c 100644 --- a/handlers/api/getAliases.go +++ b/handlers/api/getAliases.go @@ -7,7 +7,7 @@ import ( "github.com/uptrace/bunrouter" ) -type getAliasesOutputAlias struct { +type GetAliasesOutputAlias struct { Id int64 `json:"id"` Name string `json:"name"` } @@ -16,7 +16,7 @@ type getAliasesOutputAlias struct { // // @summary Get aliases // @description get alias ids and names -// @success 200 {object} []getAliasesOutputAlias +// @success 200 {object} []GetAliasesOutputAlias // @failure 400 // @failure 401 // @router /api/aliases [get] @@ -34,10 +34,10 @@ func (self *Handlers) GetAliases( } } - var output []getAliasesOutputAlias + var output []GetAliasesOutputAlias for _, alias := range aliases { - output = append(output, getAliasesOutputAlias{ + output = append(output, GetAliasesOutputAlias{ Id: alias.Id, Name: alias.Name, }) diff --git a/handlers/api/getImages.go b/handlers/api/getImages.go index c9f77b3..cbd6a9e 100644 --- a/handlers/api/getImages.go +++ b/handlers/api/getImages.go @@ -13,7 +13,7 @@ import ( "go.uber.org/zap" ) -type getImagesOutputImage struct { +type GetImagesOutputImage struct { Id int64 `json:"id"` Extension string `json:"extension"` Uploader string `json:"uploadedUserId"` @@ -25,7 +25,7 @@ type getImagesOutputImage struct { // // @param images query []int64 false "Image Ids" attribute(optional) // @param aliases query []int64 false "Alias Ids" attribute(optional) -// @success 200 {object} []getImagesOutputImage +// @success 200 {object} []GetImagesOutputImage // @failure 400 // @failure 401 // @router /api/images [get] @@ -83,13 +83,13 @@ func (self *Handlers) GetImages( } } - var output []getImagesOutputImage + var output []GetImagesOutputImage for _, img := range images { var aliases []int64 for _, alias := range img.Aliases { aliases = append(aliases, alias.Id) } - output = append(output, getImagesOutputImage{ + output = append(output, GetImagesOutputImage{ Id: img.Id, Extension: img.Extension, Uploader: img.Uploader, diff --git a/handlers/auth/postGenLoginUrl.go b/handlers/auth/postGenLoginUrl.go index abc85de..8272baa 100644 --- a/handlers/auth/postGenLoginUrl.go +++ b/handlers/auth/postGenLoginUrl.go @@ -10,18 +10,18 @@ import ( "github.com/uptrace/bunrouter" ) -type postGenLoginUrlInput struct { +type PostGenLoginUrlInput struct { UserId string `json:"userId"` } -type postGenLoginUrlOutput struct { +type PostGenLoginUrlOutput struct { LoginUrl string `json:"loginUrl"` } // PostGenLoginUrl // -// @param payload body postGenLoginUrlInput true "Payload" -// @success 200 {object} postGenLoginUrlOutput "Payload" +// @param payload body PostGenLoginUrlInput true "Payload" +// @success 200 {object} PostGenLoginUrlOutput "Payload" // @failure 400 // @router /auth/gen-login-url [post] func (self *Handlers) PostGenLoginUrl( @@ -38,7 +38,7 @@ func (self *Handlers) PostGenLoginUrl( } } - var input postGenLoginUrlInput + var input PostGenLoginUrlInput if err := json.Unmarshal(b, &input); err != nil { return middlewares.HTTPError{ StatusCode: http.StatusBadRequest, @@ -56,7 +56,7 @@ func (self *Handlers) PostGenLoginUrl( } } - return bunrouter.JSON(w, postGenLoginUrlOutput{ + return bunrouter.JSON(w, PostGenLoginUrlOutput{ LoginUrl: viper.GetString("external-url") + "/login?" + "token=" + token, diff --git a/tests/01_login_test.go b/tests/01_login_test.go index 291b274..0cf15a9 100644 --- a/tests/01_login_test.go +++ b/tests/01_login_test.go @@ -18,7 +18,7 @@ func Test_01_Login(t *testing.T) { t.Run("check preshared key failed", func(t *testing.T) { resp, err := client.R(). SetBody(`{"userId": "testuser1"}`). - Post("/auth/gen-login-url") + Post("/bot/auth/gen-login-url") if err != nil { t.Fatal("request failed") } @@ -32,7 +32,7 @@ func Test_01_Login(t *testing.T) { SetBody(`{"userId": "testuser1"}`). SetAuthToken("poop"). SetResult(&payload). - Post("/auth/gen-login-url") + Post("/bot/auth/gen-login-url") if err != nil || resp.StatusCode() != http.StatusOK { t.Fatal("failed to get login url") @@ -46,7 +46,8 @@ func Test_01_Login(t *testing.T) { resp, err = client.R(). SetBody(loginPayload{Token: loginUrl.Query().Get("token")}). - Post(loginUrl.Scheme + "://" + loginUrl.Host + "/auth/login") + SetAuthToken("poop"). + Post("/auth/login") if err != nil || resp.StatusCode() != http.StatusOK { t.Fatal("failed to login") } diff --git a/tests/04_getAliases_test.go b/tests/04_getAliases_test.go index d73ca24..75830bf 100644 --- a/tests/04_getAliases_test.go +++ b/tests/04_getAliases_test.go @@ -15,7 +15,7 @@ var aliases []aliasPayload func Test_04_GetAliases(t *testing.T) { resp, err := client.R(). SetResult(&aliases). - Get("http://localhost:8080/api/aliases") + Get("/api/aliases") if err != nil || resp.StatusCode() != http.StatusOK { t.Logf("%+v", resp) t.Fatal("failed to get aliases")