From 512e977d0d2a58ae6f9b405f4e81ed133bc0a556 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 17 Jun 2022 15:49:16 +0200 Subject: [PATCH] feat: discord commands --- backend/routes.go | 2 + backend/routes/bot/bot.go | 167 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 backend/routes/bot/bot.go diff --git a/backend/routes.go b/backend/routes.go index a9159e8..d23f5f9 100644 --- a/backend/routes.go +++ b/backend/routes.go @@ -2,6 +2,7 @@ package main import ( "codeberg.org/u1f320/pronouns.cc/backend/routes/auth" + "codeberg.org/u1f320/pronouns.cc/backend/routes/bot" "codeberg.org/u1f320/pronouns.cc/backend/routes/user" "codeberg.org/u1f320/pronouns.cc/backend/server" "github.com/go-chi/chi/v5" @@ -14,5 +15,6 @@ func mountRoutes(s *server.Server) { s.Router.Route("/v1", func(r chi.Router) { auth.Mount(s, r) user.Mount(s, r) + bot.Mount(s, r) }) } diff --git a/backend/routes/bot/bot.go b/backend/routes/bot/bot.go new file mode 100644 index 0000000..5d3b6aa --- /dev/null +++ b/backend/routes/bot/bot.go @@ -0,0 +1,167 @@ +package bot + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "os" + + "codeberg.org/u1f320/pronouns.cc/backend/db" + "codeberg.org/u1f320/pronouns.cc/backend/log" + "codeberg.org/u1f320/pronouns.cc/backend/server" + "github.com/bwmarrin/discordgo" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +type Bot struct { + *server.Server + + publicKey ed25519.PublicKey + baseURL string +} + +func Mount(srv *server.Server, r chi.Router) { + publicKey, err := hex.DecodeString(os.Getenv("DISCORD_PUBLIC_KEY")) + if err != nil { + return + } + + b := &Bot{ + Server: srv, + publicKey: publicKey, + baseURL: os.Getenv("BASE_URL"), + } + + r.HandleFunc("/interactions", b.handle) +} + +func (bot *Bot) handle(w http.ResponseWriter, r *http.Request) { + if !discordgo.VerifyInteraction(r, bot.publicKey) { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + + var ev *discordgo.InteractionCreate + + if err := json.NewDecoder(r.Body).Decode(&ev); err != nil { + http.Error(w, "Bad Request", http.StatusBadRequest) + } + + // we can always respond to ping with pong + if ev.Type == discordgo.InteractionPing { + log.Debug("received ping interaction") + render.JSON(w, r, discordgo.InteractionResponse{ + Type: discordgo.InteractionResponsePong, + }) + return + } + + if ev.Type != discordgo.InteractionApplicationCommand { + return + } + data := ev.ApplicationCommandData() + + switch data.Name { + case "Show user's pronouns": + bot.userPronouns(w, r, ev) + case "Show author's pronouns": + } +} + +func (bot *Bot) userPronouns(w http.ResponseWriter, r *http.Request, ev *discordgo.InteractionCreate) { + ctx := r.Context() + + var du *discordgo.User + for _, user := range ev.ApplicationCommandData().Resolved.Users { + du = user + break + } + if du == nil { + return + } + + u, err := bot.DB.DiscordUser(ctx, du.ID) + if err != nil { + if err == db.ErrUserNotFound { + respond(w, r, &discordgo.MessageEmbed{ + Description: du.String() + " does not have any pronouns set.", + }) + return + } + + log.Errorf("getting discord user: %v", err) + return + } + + avatarURL := du.AvatarURL("") + if u.AvatarURL != nil { + avatarURL = *u.AvatarURL + } + name := u.Username + if u.DisplayName != nil { + name = fmt.Sprintf("%s (%s)", *u.DisplayName, u.Username) + } + url := bot.baseURL + if url != "" { + url += "/@" + u.Username + } + + e := &discordgo.MessageEmbed{ + Author: &discordgo.MessageEmbedAuthor{ + Name: name, + IconURL: avatarURL, + URL: url, + }, + } + + if u.Bio != nil { + e.Fields = append(e.Fields, &discordgo.MessageEmbedField{ + Name: "Bio", + Value: *u.Bio, + }) + } + + fields, err := bot.DB.UserFields(ctx, u.ID) + if err != nil { + respond(w, r, e) + + log.Errorf("getting user fields: %v", err) + return + } + + for _, field := range fields { + if len(field.Favourite) == 0 { + continue + } + + var value string + for _, fav := range field.Favourite { + if len(value) > 500 { + break + } + + value += fav + "\n" + } + + e.Fields = append(e.Fields, &discordgo.MessageEmbedField{ + Name: field.Name, + Value: value, + Inline: true, + }) + } + + respond(w, r, e) +} + +func respond(w http.ResponseWriter, r *http.Request, embeds ...*discordgo.MessageEmbed) { + render.JSON(w, r, discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Embeds: embeds, + Flags: uint64(discordgo.MessageFlagsEphemeral), + }, + }) +}