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))
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-1
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
|
||||||
commands "ion606_bot/Bot/Commands"
|
commands "ion606_bot/Bot/Commands"
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ var commandHandlers = map[string]func(*discordgo.Session, *discordgo.Interaction
|
|||||||
"catfact": commands.HandleCatfact,
|
"catfact": commands.HandleCatfact,
|
||||||
"animalgif": commands.HandleAnimalGif,
|
"animalgif": commands.HandleAnimalGif,
|
||||||
"action": commands.HandleAction,
|
"action": commands.HandleAction,
|
||||||
|
"reactionrole": commands.HandleReactionRole,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the Discord bot session and listens for both message and slash command events.
|
// Run starts the Discord bot session and listens for both message and slash command events.
|
||||||
@@ -61,10 +63,27 @@ func newMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
|
|||||||
if m.Author.ID == s.State.User.ID {
|
if m.Author.ID == s.State.User.ID {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
// 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
|
name := i.ApplicationCommandData().Name
|
||||||
if handler, found := commandHandlers[name]; found {
|
if handler, found := commandHandlers[name]; found {
|
||||||
handler(s, i)
|
handler(s, i)
|
||||||
@@ -72,3 +91,4 @@ func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreat
|
|||||||
log.Printf("No handler found for command: %v", name)
|
log.Printf("No handler found for command: %v", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ var commandsList = []*discordgo.ApplicationCommand{
|
|||||||
Commands.CatfactCommand,
|
Commands.CatfactCommand,
|
||||||
Commands.AnimalGifCommand,
|
Commands.AnimalGifCommand,
|
||||||
Commands.ActionCommand,
|
Commands.ActionCommand,
|
||||||
|
Commands.ReactionRoleCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterCommands(s *discordgo.Session, guildID string) {
|
func RegisterCommands(s *discordgo.Session, guildID string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user