what
This commit is contained in:
+158
-25
@@ -1,7 +1,10 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
helpers "ion606_bot/Bot/Helpers"
|
helpers "ion606_bot/Bot/Helpers"
|
||||||
@@ -9,17 +12,50 @@ import (
|
|||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// reactionRoleTarget maps an interaction’s ID to its target message ID
|
||||||
|
var reactionRoleTarget = make(map[string]string)
|
||||||
|
|
||||||
|
// SaveReactionRoleTarget saves the reactionRoleTarget map to a JSON file.
|
||||||
|
func SaveReactionRoleTarget(filename string) error {
|
||||||
|
data, err := json.Marshal(reactionRoleTarget)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal reactionRoleTarget: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filename, data, 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write reactionRoleTarget file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadReactionRoleTarget loads the reactionRoleTarget map from a JSON file.
|
||||||
|
func LoadReactionRoleTarget(filename string) error {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read reactionRoleTarget file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &reactionRoleTarget); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal reactionRoleTarget: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the manage roles permission into an int64 pointer for command registration.
|
// Convert the manage roles permission into an int64 pointer for command registration.
|
||||||
func manageRolesPerm() *int64 {
|
func manageRolesPerm() *int64 {
|
||||||
p := int64(discordgo.PermissionManageRoles)
|
fmt.Println("checking perms")
|
||||||
|
|
||||||
|
p := int64(discordgo.PermissionManageRoles | discordgo.PermissionAll)
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReactionRoleCommand defines the reaction role slash command with options for count and style.
|
// ReactionRoleCommand defines the reaction role slash command with options for count, style, and an optional message ID.
|
||||||
// Only members with the "Manage Roles" permission may use this command.
|
// Only members with the "Manage Roles" permission may use this command.
|
||||||
var ReactionRoleCommand = &discordgo.ApplicationCommand{
|
var ReactionRoleCommand = &discordgo.ApplicationCommand{
|
||||||
Name: "reactionrole",
|
Name: "reactionrole",
|
||||||
Description: "Setup a reaction role message using buttons. Provide a number (max 10) for role-button pairs.",
|
Description: "Setup a reaction role message using buttons. Provide a number (max 10) for role-button pairs",
|
||||||
DefaultMemberPermissions: manageRolesPerm(),
|
DefaultMemberPermissions: manageRolesPerm(),
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
{
|
{
|
||||||
@@ -54,6 +90,12 @@ var ReactionRoleCommand = &discordgo.ApplicationCommand{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "message_id",
|
||||||
|
Description: "Optional: Message ID of the message to edit and add buttons to.",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +121,16 @@ func memberHasManageRoles(s *discordgo.Session, guildID string, member *discordg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guild, err := s.Guild(guildID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.User.ID == guild.OwnerID {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
var perms int64 = 0
|
var perms int64 = 0
|
||||||
for _, roleID := range member.Roles {
|
for _, roleID := range member.Roles {
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
@@ -87,13 +139,12 @@ func memberHasManageRoles(s *discordgo.Session, guildID string, member *discordg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return perms&discordgo.PermissionManageRoles != 0, nil
|
|
||||||
|
return (perms&discordgo.PermissionManageRoles != 0 || perms&discordgo.PermissionAdministrator != 0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleReactionRole shows a modal with inputs for each role/button pair.
|
|
||||||
// It verifies that the invoking member has the Manage Roles permission.
|
|
||||||
func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
// Check that the invoking member has the Manage Roles permission.
|
// Ensure the command is used in a guild and member data is present.
|
||||||
if i.GuildID == "" || i.Member == nil {
|
if i.GuildID == "" || i.Member == nil {
|
||||||
helpers.HandleError(s, i, fmt.Errorf("command must be used in a guild"))
|
helpers.HandleError(s, i, fmt.Errorf("command must be used in a guild"))
|
||||||
return
|
return
|
||||||
@@ -103,6 +154,7 @@ func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
helpers.HandleError(s, i, fmt.Errorf("failed to check user permissions: %w", err))
|
helpers.HandleError(s, i, fmt.Errorf("failed to check user permissions: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasPerm {
|
if !hasPerm {
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
@@ -126,6 +178,15 @@ func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetMsgID := ""
|
||||||
|
if len(options) > 2 {
|
||||||
|
opt := options[2]
|
||||||
|
if val := opt.StringValue(); val != "" {
|
||||||
|
targetMsgID = val
|
||||||
|
reactionRoleTarget[i.Member.User.ID] = targetMsgID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build modal components: for each pair, add two text inputs (each in its own action row).
|
// Build modal components: for each pair, add two text inputs (each in its own action row).
|
||||||
modalComponents := []discordgo.MessageComponent{}
|
modalComponents := []discordgo.MessageComponent{}
|
||||||
for idx := 1; idx <= count; idx++ {
|
for idx := 1; idx <= count; idx++ {
|
||||||
@@ -137,7 +198,6 @@ func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
Placeholder: "Enter Role ID",
|
Placeholder: "Enter Role ID",
|
||||||
Required: true,
|
Required: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input for Button Label.
|
// Input for Button Label.
|
||||||
labelInput := discordgo.TextInput{
|
labelInput := discordgo.TextInput{
|
||||||
CustomID: fmt.Sprintf("role_label_%d", idx),
|
CustomID: fmt.Sprintf("role_label_%d", idx),
|
||||||
@@ -149,17 +209,26 @@ func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
modalComponents = append(modalComponents, discordgo.ActionsRow{
|
modalComponents = append(modalComponents, discordgo.ActionsRow{
|
||||||
Components: []discordgo.MessageComponent{&roleInput},
|
Components: []discordgo.MessageComponent{&roleInput},
|
||||||
})
|
})
|
||||||
|
|
||||||
modalComponents = append(modalComponents, discordgo.ActionsRow{
|
modalComponents = append(modalComponents, discordgo.ActionsRow{
|
||||||
Components: []discordgo.MessageComponent{&labelInput},
|
Components: []discordgo.MessageComponent{&labelInput},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the style to the modal's custom ID.
|
// Build a custom ID that contains the style and, if provided, the target message ID.
|
||||||
|
customID := "reactionrole_modal"
|
||||||
|
if targetMsgID != "" {
|
||||||
|
customID += fmt.Sprintf(":%s-%s", i.ChannelID, targetMsgID)
|
||||||
|
} else {
|
||||||
|
customID += ":NA"
|
||||||
|
}
|
||||||
|
|
||||||
|
if style != "primary" {
|
||||||
|
customID += fmt.Sprintf(":%s", style)
|
||||||
|
}
|
||||||
|
|
||||||
modalData := &discordgo.InteractionResponseData{
|
modalData := &discordgo.InteractionResponseData{
|
||||||
Title: "Reaction Role Setup",
|
Title: "Reaction Role Setup",
|
||||||
// Append the style value after a colon so it is available on submit.
|
CustomID: customID,
|
||||||
CustomID: fmt.Sprintf("reactionrole_modal:%s", style),
|
|
||||||
Components: modalComponents,
|
Components: modalComponents,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,22 +236,37 @@ func HandleReactionRole(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|||||||
Type: discordgo.InteractionResponseModal,
|
Type: discordgo.InteractionResponseModal,
|
||||||
Data: modalData,
|
Data: modalData,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helpers.HandleError(s, i, fmt.Errorf("failed to send modal: %w", err))
|
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.
|
// HandleReactionRoleModalSubmit processes the modal submission, uses the style from the modal custom ID,
|
||||||
|
// and if a target message ID is provided, edits that message to add the buttons.
|
||||||
func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
// Extract the style from the modal's CustomID.
|
// Extract the style from the modal's CustomID.
|
||||||
customID := i.ModalSubmitData().CustomID
|
customID := i.ModalSubmitData().CustomID
|
||||||
parts := strings.Split(customID, ":")
|
parts := strings.Split(customID, ":")
|
||||||
styleStr := "primary"
|
styleStr := "primary"
|
||||||
if len(parts) == 2 {
|
|
||||||
styleStr = parts[1]
|
targetMsgID := ""
|
||||||
|
targetChannelID := ""
|
||||||
|
|
||||||
|
if len(parts) >= 3 {
|
||||||
|
styleStr = parts[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(parts) >= 2 && parts[1] != "NA" {
|
||||||
|
spl := strings.Split(parts[1], "-")
|
||||||
|
targetChannelID = spl[0]
|
||||||
|
targetMsgID = spl[1]
|
||||||
|
}
|
||||||
|
|
||||||
btnStyle := mapStyle(styleStr)
|
btnStyle := mapStyle(styleStr)
|
||||||
|
|
||||||
|
log.Default().Println(targetMsgID, targetChannelID)
|
||||||
|
|
||||||
data := i.ModalSubmitData()
|
data := i.ModalSubmitData()
|
||||||
// There will be two action rows per pair.
|
// There will be two action rows per pair.
|
||||||
count := len(data.Components) / 2
|
count := len(data.Components) / 2
|
||||||
@@ -194,7 +278,6 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
var err error
|
var err error
|
||||||
guildRoles, err = s.GuildRoles(i.GuildID)
|
guildRoles, err = s.GuildRoles(i.GuildID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Send an ephemeral error message.
|
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
@@ -209,15 +292,11 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
// For each pair, extract the Role ID and label.
|
// For each pair, extract the Role ID and label.
|
||||||
for idx := 1; idx <= count; idx++ {
|
for idx := 1; idx <= count; idx++ {
|
||||||
var roleID, label string
|
var roleID, label string
|
||||||
|
|
||||||
// Iterate over the action rows.
|
|
||||||
for _, comp := range data.Components {
|
for _, comp := range data.Components {
|
||||||
row, ok := comp.(*discordgo.ActionsRow)
|
row, ok := comp.(*discordgo.ActionsRow)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, innerComp := range row.Components {
|
for _, innerComp := range row.Components {
|
||||||
textInput, ok := innerComp.(*discordgo.TextInput)
|
textInput, ok := innerComp.(*discordgo.TextInput)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -231,7 +310,7 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for cancellation request.
|
// Allow a cancellation action.
|
||||||
if strings.ToLower(roleID) == "cancel" {
|
if strings.ToLower(roleID) == "cancel" {
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
@@ -243,7 +322,7 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the given role exists in the guild.
|
// Verify that the given role exists.
|
||||||
roleExists := false
|
roleExists := false
|
||||||
if i.GuildID != "" {
|
if i.GuildID != "" {
|
||||||
for _, r := range guildRoles {
|
for _, r := range guildRoles {
|
||||||
@@ -253,7 +332,6 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If not in a guild, we cannot verify role existence.
|
|
||||||
roleExists = true
|
roleExists = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +357,7 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discord supports up to 5 buttons per row.
|
// Arrange buttons into rows (up to 5 per row).
|
||||||
var components []discordgo.MessageComponent
|
var components []discordgo.MessageComponent
|
||||||
for j := 0; j < len(buttons); j += 5 {
|
for j := 0; j < len(buttons); j += 5 {
|
||||||
end := j + 5
|
end := j + 5
|
||||||
@@ -292,15 +370,70 @@ func HandleReactionRoleModalSubmit(s *discordgo.Session, i *discordgo.Interactio
|
|||||||
components = append(components, row)
|
components = append(components, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a target message ID was provided, edit that message. Otherwise, send a new message.
|
||||||
|
if targetMsgID != "" {
|
||||||
|
// First, get the existing message to preserve its content.
|
||||||
|
originalMsg, err := s.ChannelMessage(targetChannelID, targetMsgID)
|
||||||
|
if err != nil {
|
||||||
|
helpers.HandleError(s, i, fmt.Errorf("failed to fetch target message: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit the target message with the original content and new buttons.
|
||||||
|
_, err = s.ChannelMessageEditComplex(&discordgo.MessageEdit{
|
||||||
|
Channel: targetChannelID,
|
||||||
|
ID: targetMsgID,
|
||||||
|
Content: &originalMsg.Content, // Preserve original content.
|
||||||
|
Components: &components,
|
||||||
|
});
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
helpers.HandleError(s, i, fmt.Errorf("failed to edit target message: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledge the update with an ephemeral confirmation.
|
||||||
|
err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Content: fmt.Sprintf("Successfully added reaction roles to [this message](%s).",
|
||||||
|
helpers.CreateMessageLink(i.GuildID, i.ChannelID, targetMsgID)),
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
helpers.HandleError(s, i, fmt.Errorf("failed to send confirmation: %w", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For new messages, first respond to the interaction.
|
||||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
Content: "Click a button to receive the corresponding role.",
|
Content: "Click a button to receive the corresponding role:",
|
||||||
Components: components,
|
Components: components,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helpers.HandleError(s, i, fmt.Errorf("failed to send reaction role message: %w", err))
|
helpers.HandleError(s, i, fmt.Errorf("failed to send reaction role message: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the message we just sent to provide a reference.
|
||||||
|
msg, err := s.InteractionResponse(i.Interaction)
|
||||||
|
if err != nil {
|
||||||
|
helpers.HandleError(s, i, fmt.Errorf("failed to get sent message: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a follow-up with the message link.
|
||||||
|
_, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
|
||||||
|
Content: fmt.Sprintf("Reaction roles setup complete! [Jump to message](%s)",
|
||||||
|
helpers.CreateMessageLink(i.GuildID, i.ChannelID, msg.ID)),
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
helpers.HandleError(s, i, fmt.Errorf("failed to send follow-up: %w", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateMessageLink(guildID, channelID, messageID string) string {
|
||||||
|
return fmt.Sprintf("https://discord.com/channels/%s/%s/%s", guildID, channelID, messageID)
|
||||||
|
}
|
||||||
+15
-2
@@ -1,7 +1,6 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -36,6 +35,12 @@ func Run() {
|
|||||||
log.Fatal("Error creating Discord session: ", err)
|
log.Fatal("Error creating Discord session: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load reaction role target map from file.
|
||||||
|
err = commands.LoadReactionRoleTarget("reactionRoleTarget.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("No reaction role target config loaded: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// add event handlers for messages and interactions.
|
// add event handlers for messages and interactions.
|
||||||
discord.AddHandler(newMessage)
|
discord.AddHandler(newMessage)
|
||||||
discord.AddHandler(handleInteractionCreate)
|
discord.AddHandler(handleInteractionCreate)
|
||||||
@@ -47,11 +52,19 @@ func Run() {
|
|||||||
|
|
||||||
RegisterCommands(discord, "")
|
RegisterCommands(discord, "")
|
||||||
|
|
||||||
fmt.Println("Bot running....")
|
log.Println("Bot running....")
|
||||||
|
|
||||||
|
// Wait for interrupt signal.
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
signal.Notify(c, os.Interrupt)
|
signal.Notify(c, os.Interrupt)
|
||||||
<-c
|
<-c
|
||||||
|
|
||||||
|
// Save reaction role target map to file on shutdown.
|
||||||
|
err = commands.SaveReactionRoleTarget("reactionRoleTarget.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error saving reaction role target:", err)
|
||||||
|
}
|
||||||
|
|
||||||
err = discord.Close()
|
err = discord.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error closing Discord session: ", err)
|
log.Println("Error closing Discord session: ", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user