From abd373916ea25c4baaa1951e38b557c34d6cae6b Mon Sep 17 00:00:00 2001 From: Layla Manley Date: Sat, 9 Mar 2024 13:29:48 +0100 Subject: [PATCH] Improved role selection --- core/configuration.go | 5 ++- core/image.go | 33 ++++++++++++++++ discord/component.go | 24 ++++++++++++ discord/component_button.go | 21 +++++++++- discord/emoji.go | 78 +++++++++++++++++++++++++++++++++++++ discord/user.go | 13 +------ modules/role_selection.go | 46 +++++++++++++++++++--- 7 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 core/image.go create mode 100644 discord/emoji.go diff --git a/core/configuration.go b/core/configuration.go index 016361d..593d2e7 100644 --- a/core/configuration.go +++ b/core/configuration.go @@ -39,8 +39,9 @@ type StatusPortal struct { } type RoleSelectionConfig struct { - Title string `yaml:"title"` - Description string `yaml:"description"` + Title string `yaml:"title"` + Description string `yaml:"description"` + GenerateColorEmoji Feature `yaml:"generate_color_emoji"` SelectionChannel string `yaml:"discord_channel"` Roles []RoleConfig `yaml:"roles"` diff --git a/core/image.go b/core/image.go new file mode 100644 index 0000000..bca0610 --- /dev/null +++ b/core/image.go @@ -0,0 +1,33 @@ +package core + +import ( + "bytes" + "encoding/base64" + "fmt" + "image" + "image/color" + "image/draw" + "image/jpeg" + "log" +) + +func ImageToBase64(img image.Image) string { + data := &bytes.Buffer{} + if err := jpeg.Encode(data, img, nil); err != nil { + log.Println("Error encoding avatar: ", err) + return "" + } + + return fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(data.Bytes())) +} + +// ColorToImage creates a 16x16 image filled with the specified color. +func ColorToImage(c color.Color) image.Image { + // Create a new rectangle with the desired dimensions. + rect := image.Rect(0, 0, 16, 16) + // Create a new RGBA image with the specified rectangle. + img := image.NewRGBA(rect) + // Use the draw.Draw function to fill the image with the specified color. + draw.Draw(img, img.Bounds(), &image.Uniform{C: c}, image.Point{}, draw.Src) + return img +} diff --git a/discord/component.go b/discord/component.go index a5264bd..2a5c29a 100644 --- a/discord/component.go +++ b/discord/component.go @@ -30,3 +30,27 @@ func (discord *Discord) CreateMessageComponent(channelID string, content string, return result.ID } + +// UpdateMessageComponent updates a discord component +func (discord *Discord) UpdateMessageComponent(messageID string, channelID string, content string, components []Component) string { + dComponents := make([]discordgo.MessageComponent, len(components)) + for i, v := range components { + dComponents[i] = v.toMessageComponent() + } + + result, err := discord.session.ChannelMessageEditComplex(&discordgo.MessageEdit{ + Components: dComponents, + Content: &content, + Channel: channelID, + ID: messageID, + }) + if err != nil { + log.Printf("Error updating message component: %s", err) + } + + if result != nil { + return result.ID + } + + return "" +} diff --git a/discord/component_button.go b/discord/component_button.go index 3e18948..dd74bfc 100644 --- a/discord/component_button.go +++ b/discord/component_button.go @@ -9,6 +9,7 @@ type Button struct { Label string ID string + Emoji *Emoji discord *Discord } @@ -18,6 +19,17 @@ func (discord *Discord) NewButton(id string, label string) *Button { discord: discord, ID: id, Label: label, + Emoji: nil, + } +} + +// NewButtonWithEmoji creates a new button component with a emoji +func (discord *Discord) NewButtonWithEmoji(id string, label string, emoji *Emoji) *Button { + return &Button{ + discord: discord, + ID: id, + Label: label, + Emoji: emoji, } } @@ -41,9 +53,16 @@ func (button *Button) OnClick(action func(user common.User)) { } func (button *Button) toMessageComponent() discordgo.MessageComponent { - return discordgo.Button{ + cmp := discordgo.Button{ Label: button.Label, CustomID: button.ID, Style: discordgo.PrimaryButton, } + if button.Emoji != nil { + cmp.Emoji = &discordgo.ComponentEmoji{ + Name: button.Emoji.Name, + ID: button.Emoji.ID, + } + } + return cmp } diff --git a/discord/emoji.go b/discord/emoji.go new file mode 100644 index 0000000..881227b --- /dev/null +++ b/discord/emoji.go @@ -0,0 +1,78 @@ +package discord + +import ( + "image" + "log" + + "github.com/bwmarrin/discordgo" + "github.com/yeslayla/birdbot/core" +) + +type Emoji struct { + discord *Discord + ID string + + Name string + Roles []string +} + +// GetEmoji returns a emoji that exists on Discord +func (discord *Discord) GetEmoji(name string) *Emoji { + + emojis, err := discord.session.GuildEmojis(discord.guildID) + if err != nil { + log.Printf("Error occured listing roles: %s", err) + return nil + } + + for _, emoji := range emojis { + if emoji.Managed { + continue + } + if emoji.Name == name { + + return &Emoji{ + ID: emoji.ID, + discord: discord, + Name: emoji.Name, + Roles: emoji.Roles, + } + } + } + + return nil +} + +// CreateEmoji creates a new emoji on Discord +func (discord *Discord) CreateEmoji(name string, image image.Image) *Emoji { + result, err := discord.session.GuildEmojiCreate(discord.guildID, &discordgo.EmojiParams{ + Name: name, + Image: core.ImageToBase64(image), + }) + if err != nil { + log.Printf("Failed to create emoji: %s", err) + return nil + } + + if result == nil { + log.Print("Failed to create emoji: result is nil") + return nil + } + + return &Emoji{ + ID: result.ID, + Name: result.Name, + Roles: result.Roles, + discord: discord, + } +} + +// Save updates the emoji on Discord +func (emoji *Emoji) Save() { + if _, err := emoji.discord.session.GuildEmojiEdit(emoji.discord.guildID, emoji.ID, &discordgo.EmojiParams{ + Name: emoji.Name, + Roles: emoji.Roles, + }); err != nil { + log.Printf("Failed to save role: %s", err) + } +} diff --git a/discord/user.go b/discord/user.go index 94de77f..9abf0ad 100644 --- a/discord/user.go +++ b/discord/user.go @@ -1,15 +1,12 @@ package discord import ( - "bytes" - "encoding/base64" - "fmt" "image" - "image/jpeg" "log" "github.com/bwmarrin/discordgo" "github.com/yeslayla/birdbot-common/common" + "github.com/yeslayla/birdbot/core" ) // NewUser creates a new user object from a discordgo.User object @@ -49,13 +46,7 @@ func (discord *Discord) GetAvatar(user common.User) image.Image { func (discord *Discord) GetAvatarBase64(user common.User) string { avatar := discord.GetAvatar(user) - fmtAvatar := &bytes.Buffer{} - if err := jpeg.Encode(fmtAvatar, avatar, nil); err != nil { - log.Println("Error encoding avatar: ", err) - return "" - } - - return fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(fmtAvatar.Bytes())) + return core.ImageToBase64(avatar) } // AssignRole adds a role to a user diff --git a/modules/role_selection.go b/modules/role_selection.go index efe0ee1..a0a67ce 100644 --- a/modules/role_selection.go +++ b/modules/role_selection.go @@ -40,13 +40,43 @@ func (c *roleSelectionModule) Initialize(birdbot common.ModuleManager) error { role := c.session.GetRoleAndCreate(roleConfig.RoleName) configColor, _ := core.HexToColor(roleConfig.Color) + var emoji *discord.Emoji = nil + if c.cfg.GenerateColorEmoji.IsEnabledByDefault() { + emoji = c.session.GetEmoji(roleConfig.RoleName) + if emoji == nil { + emoji = c.session.CreateEmoji(roleConfig.RoleName, core.ColorToImage(configColor)) + if emoji == nil { + log.Printf("Failed to create emoji for role: %s", roleConfig.RoleName) + return nil + } + } + + // If role.ID in emoji.Roles + var found bool + for _, r := range emoji.Roles { + if r == role.ID { + found = true + break + } + } + if !found { + emoji.Roles = append(emoji.Roles, role.ID) + emoji.Save() + } + } + if role.Color != configColor { role.Color = configColor role.Save() } // Create button - btn := c.session.NewButton(fmt.Sprint(c.cfg.Title, role.Name), role.Name) + var btn *discord.Button + if emoji != nil { + btn = c.session.NewButtonWithEmoji(fmt.Sprint(c.cfg.Title, roleConfig.RoleName), roleConfig.RoleName, emoji) + } else { + btn = c.session.NewButton(fmt.Sprint(c.cfg.Title, role.Name), role.Name) + } btn.OnClick(func(user common.User) { // Assign the roles asynchronously to avoid Discord's response timeout @@ -97,10 +127,16 @@ func (c *roleSelectionModule) Initialize(birdbot common.ModuleManager) error { return err } - if messageID == "" { - messageID = c.session.CreateMessageComponent(c.cfg.SelectionChannel, fmt.Sprintf("**%s**\n%s", c.cfg.Title, c.cfg.Description), components) - return c.db.SetDiscordMessage(localID, messageID) + // Update message + if messageID != "" { + resultID := c.session.UpdateMessageComponent(messageID, c.cfg.SelectionChannel, fmt.Sprintf("**%s**\n%s", c.cfg.Title, c.cfg.Description), components) + if resultID != "" { + return nil + } } - return nil + // Create new message + messageID = c.session.CreateMessageComponent(c.cfg.SelectionChannel, fmt.Sprintf("**%s**\n%s", c.cfg.Title, c.cfg.Description), components) + return c.db.SetDiscordMessage(localID, messageID) + }