added reactionrole command
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
helpers "ion606_bot/Bot/Helpers"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
// ReactionRoleCommand defines the reaction role slash command with options for count and style.
|
||||
var ReactionRoleCommand = &discordgo.ApplicationCommand{
|
||||
Name: "reactionrole",
|
||||
Description: "Setup a reaction role message using buttons. Provide a number (max 10) for role-button pairs.",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionInteger,
|
||||
Name: "count",
|
||||
Description: "Number of role-button pairs to setup (max 10)",
|
||||
Required: true,
|
||||
MinValue: func(v float64) *float64 { return &v }(1),
|
||||
MaxValue: func(v float64) float64 { return v }(10),
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "style",
|
||||
Description: "Button style",
|
||||
Required: false,
|
||||
Choices: []*discordgo.ApplicationCommandOptionChoice{
|
||||
{
|
||||
Name: "Blue",
|
||||
Value: "primary",
|
||||
},
|
||||
{
|
||||
Name: "Gray",
|
||||
Value: "secondary",
|
||||
},
|
||||
{
|
||||
Name: "Green",
|
||||
Value: "success",
|
||||
},
|
||||
{
|
||||
Name: "Red",
|
||||
Value: "danger",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// mapStyle converts a style string to a discordgo.ButtonStyle.
|
||||
func mapStyle(style string) discordgo.ButtonStyle {
|
||||
switch strings.ToLower(style) {
|
||||
case "primary", "blue":
|
||||
return discordgo.PrimaryButton
|
||||
case "secondary", "gray":
|
||||
return discordgo.SecondaryButton
|
||||
case "success", "green":
|
||||
return discordgo.SuccessButton
|
||||
case "danger", "red":
|
||||
return discordgo.DangerButton
|
||||
default:
|
||||
return discordgo.PrimaryButton
|
||||
}
|
||||
}
|
||||
|
||||
// HandleReactionRole shows a modal with inputs for each role/button pair.
|
||||
// It appends the chosen style to the modal's custom ID so that it is available during modal submit.
|
||||
func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
options := i.ApplicationCommandData().Options
|
||||
count := int(options[0].IntValue())
|
||||
|
||||
// Determine style based on the additional option; default to primary.
|
||||
style := "primary"
|
||||
if len(options) > 1 {
|
||||
styleOpt := options[1]
|
||||
if val := styleOpt.StringValue(); val != "" {
|
||||
style = val
|
||||
}
|
||||
}
|
||||
|
||||
// Build modal components: for each pair, add two text inputs (each in its own action row).
|
||||
modalComponents := []discordgo.MessageComponent{}
|
||||
for idx := 1; idx <= count; idx++ {
|
||||
// Input for Role ID.
|
||||
roleInput := discordgo.TextInput{
|
||||
CustomID: fmt.Sprintf("role_id_%d", idx),
|
||||
Label: fmt.Sprintf("Role ID for pair %d", idx),
|
||||
Style: discordgo.TextInputShort,
|
||||
Placeholder: "Enter Role ID",
|
||||
Required: true,
|
||||
}
|
||||
|
||||
// Input for Button Label.
|
||||
labelInput := discordgo.TextInput{
|
||||
CustomID: fmt.Sprintf("role_label_%d", idx),
|
||||
Label: fmt.Sprintf("Button label for pair %d", idx),
|
||||
Style: discordgo.TextInputShort,
|
||||
Placeholder: "Button Text",
|
||||
Required: true,
|
||||
}
|
||||
modalComponents = append(modalComponents, discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{&roleInput},
|
||||
})
|
||||
|
||||
modalComponents = append(modalComponents, discordgo.ActionsRow{
|
||||
Components: []discordgo.MessageComponent{&labelInput},
|
||||
})
|
||||
}
|
||||
|
||||
// Append the style to the modal's custom ID.
|
||||
modalData := &discordgo.InteractionResponseData{
|
||||
Title: "Reaction Role Setup",
|
||||
// Append the style value after a colon so it is available on submit.
|
||||
CustomID: fmt.Sprintf("reactionrole_modal:%s", style),
|
||||
Components: modalComponents,
|
||||
}
|
||||
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseModal,
|
||||
Data: modalData,
|
||||
})
|
||||
if err != nil {
|
||||
helpers.HandleError(s, i, fmt.Errorf("failed to send modal: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// HandleReactionRoleModalSubmit processes the modal submission and uses the style passed via the modal custom ID.
|
||||
func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
// Extract the style from the modal's CustomID.
|
||||
customID := i.ModalSubmitData().CustomID
|
||||
parts := strings.Split(customID, ":")
|
||||
styleStr := "primary"
|
||||
if len(parts) == 2 {
|
||||
styleStr = parts[1]
|
||||
}
|
||||
btnStyle := mapStyle(styleStr)
|
||||
|
||||
data := i.ModalSubmitData()
|
||||
// There will be two action rows per pair.
|
||||
count := len(data.Components) / 2
|
||||
var buttons []discordgo.MessageComponent
|
||||
|
||||
// Retrieve guild roles to verify existence.
|
||||
var guildRoles []*discordgo.Role
|
||||
if i.GuildID != "" {
|
||||
var err error
|
||||
guildRoles, err = s.GuildRoles(i.GuildID)
|
||||
if err != nil {
|
||||
// Send an ephemeral error message.
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error fetching guild roles.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// For each pair, extract the Role ID and label.
|
||||
for idx := 1; idx <= count; idx++ {
|
||||
var roleID, label string
|
||||
|
||||
// Iterate over the action rows.
|
||||
for _, comp := range data.Components {
|
||||
row, ok := comp.(*discordgo.ActionsRow)
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, innerComp := range row.Components {
|
||||
textInput, ok := innerComp.(*discordgo.TextInput)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if textInput.CustomID == fmt.Sprintf("role_id_%d", idx) {
|
||||
roleID = textInput.Value
|
||||
} else if textInput.CustomID == fmt.Sprintf("role_label_%d", idx) {
|
||||
label = textInput.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cancellation request.
|
||||
if strings.ToLower(roleID) == "cancel" {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Reaction role setup has been cancelled.",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the given role exists in the guild.
|
||||
roleExists := false
|
||||
if i.GuildID != "" {
|
||||
for _, r := range guildRoles {
|
||||
if r.ID == roleID {
|
||||
roleExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If not in a guild, we cannot verify role existence.
|
||||
roleExists = true
|
||||
}
|
||||
|
||||
if !roleExists {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf("Error: Role with ID `%s` does not exist in this server.", roleID),
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Create the button if valid.
|
||||
if roleID != "" && label != "" {
|
||||
btn := discordgo.Button{
|
||||
Label: label,
|
||||
Style: btnStyle,
|
||||
CustomID: "rr:" + roleID,
|
||||
}
|
||||
buttons = append(buttons, btn)
|
||||
}
|
||||
}
|
||||
|
||||
// Discord supports up to 5 buttons per row.
|
||||
var components []discordgo.MessageComponent
|
||||
for j := 0; j < len(buttons); j += 5 {
|
||||
end := j + 5
|
||||
if end > len(buttons) {
|
||||
end = len(buttons)
|
||||
}
|
||||
row := discordgo.ActionsRow{
|
||||
Components: buttons[j:end],
|
||||
}
|
||||
components = append(components, row)
|
||||
}
|
||||
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Click a button to receive the corresponding role.",
|
||||
Components: components,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
helpers.HandleError(s, i, fmt.Errorf("failed to send reaction role message: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// HandleReactionRoleButton processes button clicks for reaction roles.
|
||||
func HandleReactionRoleButton(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
customID := i.MessageComponentData().CustomID
|
||||
// Expect customID in the format "rr:<roleID>"
|
||||
roleID := strings.TrimPrefix(customID, "rr:")
|
||||
if roleID == "" {
|
||||
helpers.HandleError(s, i, fmt.Errorf("invalid role ID"))
|
||||
return
|
||||
}
|
||||
|
||||
if i.GuildID == "" {
|
||||
helpers.HandleError(s, i, fmt.Errorf("cannot assign roles in DMs"))
|
||||
return
|
||||
}
|
||||
member := i.Member
|
||||
if member == nil {
|
||||
helpers.HandleError(s, i, fmt.Errorf("member not found"))
|
||||
return
|
||||
}
|
||||
|
||||
// Assign the role.
|
||||
if err := s.GuildMemberRoleAdd(i.GuildID, member.User.ID, roleID); err != nil {
|
||||
helpers.HandleError(s, i, fmt.Errorf("error assigning role: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Respond to acknowledge the action.
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Role successfully assigned!",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
helpers.HandleError(s, i, fmt.Errorf("failed to respond to button interaction: %w", err))
|
||||
}
|
||||
}
|
||||
+35
-15
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
commands "ion606_bot/Bot/Commands"
|
||||
|
||||
@@ -15,15 +16,16 @@ var BotToken string
|
||||
|
||||
// commandHandlers maps command names to their interaction handler functions.
|
||||
var commandHandlers = map[string]func(*discordgo.Session, *discordgo.InteractionCreate){
|
||||
"meow": commands.HandleMeow,
|
||||
"purr": commands.HandlePurr,
|
||||
"boop": commands.HandleBoop,
|
||||
"hug": commands.HandleHug,
|
||||
"cuddle": commands.HandleCuddle,
|
||||
"snuggle": commands.HandleSnuggle,
|
||||
"catfact": commands.HandleCatfact,
|
||||
"animalgif": commands.HandleAnimalGif,
|
||||
"action": commands.HandleAction,
|
||||
"meow": commands.HandleMeow,
|
||||
"purr": commands.HandlePurr,
|
||||
"boop": commands.HandleBoop,
|
||||
"hug": commands.HandleHug,
|
||||
"cuddle": commands.HandleCuddle,
|
||||
"snuggle": commands.HandleSnuggle,
|
||||
"catfact": commands.HandleCatfact,
|
||||
"animalgif": commands.HandleAnimalGif,
|
||||
"action": commands.HandleAction,
|
||||
"reactionrole": commands.HandleReactionRole,
|
||||
}
|
||||
|
||||
// Run starts the Discord bot session and listens for both message and slash command events.
|
||||
@@ -61,14 +63,32 @@ func newMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
if m.Author.ID == s.State.User.ID {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
name := i.ApplicationCommandData().Name
|
||||
if handler, found := commandHandlers[name]; found {
|
||||
handler(s, i)
|
||||
} else {
|
||||
log.Printf("No handler found for command: %v", name)
|
||||
// Check for modal submissions first.
|
||||
if i.Type == discordgo.InteractionModalSubmit {
|
||||
if strings.HasPrefix(i.ModalSubmitData().CustomID, "reactionrole_modal:") {
|
||||
commands.HandleReactionRoleModalSubmit(s, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for message components (buttons).
|
||||
if i.Type == discordgo.InteractionMessageComponent {
|
||||
if strings.HasPrefix(i.MessageComponentData().CustomID, "rr:") {
|
||||
commands.HandleReactionRoleButton(s, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, process application commands.
|
||||
if i.Type == discordgo.InteractionApplicationCommand {
|
||||
name := i.ApplicationCommandData().Name
|
||||
if handler, found := commandHandlers[name]; found {
|
||||
handler(s, i)
|
||||
} else {
|
||||
log.Printf("No handler found for command: %v", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ var commandsList = []*discordgo.ApplicationCommand{
|
||||
Commands.CatfactCommand,
|
||||
Commands.AnimalGifCommand,
|
||||
Commands.ActionCommand,
|
||||
Commands.ReactionRoleCommand,
|
||||
}
|
||||
|
||||
func RegisterCommands(s *discordgo.Session, guildID string) {
|
||||
|
||||
Reference in New Issue
Block a user