diff --git a/cmd_ask.go b/cmd_ask.go index ff4f4f0..b2d8c19 100644 --- a/cmd_ask.go +++ b/cmd_ask.go @@ -16,7 +16,7 @@ var cmd_ask Command = Command{ }, }, Interact: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - respondEmbed(s, i.Interaction, discordgo.MessageEmbed{ + respondEmbed(i.Interaction, discordgo.MessageEmbed{ Type: discordgo.EmbedTypeImage, Color: hexToDecimal(color["primary"]), Image: &discordgo.MessageEmbedImage{ diff --git a/cmd_autojoinroles.go b/cmd_autojoinroles.go index 453c87d..ceee1d7 100644 --- a/cmd_autojoinroles.go +++ b/cmd_autojoinroles.go @@ -40,7 +40,7 @@ var cmd_autojoinroles Command = Command{ if len(i.ApplicationCommandData().Options[0].Options) == 1 { var givenRole *discordgo.Role = i.ApplicationCommandData().Options[0].Options[0].RoleValue(s, i.GuildID) role = givenRole.ID - botrole, _ := getHighestRole(s, i.GuildID) + botrole, _ := getHighestRole(i.GuildID) if givenRole.Position >= botrole.Position { content = "<@&" + role + "> is not below the Bot's current highest role(<@&" + botrole.ID + ">). That makes it unable to manage it." } else { @@ -53,7 +53,7 @@ var cmd_autojoinroles Command = Command{ } else if setAutoJoinRole(i.GuildID, option, role) { content = "Deleted auto join role for " + option + "s" } - respond(s, i.Interaction, content, true) + respond(i.Interaction, content, true) purgeUnusedAutoJoinRoles(i.GuildID) }, } diff --git a/cmd_autopublish.go b/cmd_autopublish.go index 653e846..b62a52f 100644 --- a/cmd_autopublish.go +++ b/cmd_autopublish.go @@ -11,12 +11,12 @@ var cmd_autopublish Command = Command{ channel, _ := s.State.Channel(i.ChannelID) if channel.Type == discordgo.ChannelTypeGuildNews { if toggleAutoPublish(i.GuildID, i.ChannelID) { - respond(s, i.Interaction, "Autopublishing is now disabled on <#"+i.ChannelID+">", true) + respond(i.Interaction, "Autopublishing is now disabled on <#"+i.ChannelID+">", true) } else { - respond(s, i.Interaction, "Autopublishing is now enabled on <#"+i.ChannelID+">", true) + respond(i.Interaction, "Autopublishing is now enabled on <#"+i.ChannelID+">", true) } } else { - respond(s, i.Interaction, "This is not an announcement channel!", true) + respond(i.Interaction, "This is not an announcement channel!", true) } }, } diff --git a/cmd_cat.go b/cmd_cat.go index d0294be..5fa8046 100644 --- a/cmd_cat.go +++ b/cmd_cat.go @@ -15,7 +15,7 @@ var cmd_cat Command = Command{ Description: "Random cat pictures", }, Interact: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - respondEmbed(s, i.Interaction, discordgo.MessageEmbed{ + respondEmbed(i.Interaction, discordgo.MessageEmbed{ Type: discordgo.EmbedTypeImage, Color: hexToDecimal(color["primary"]), Image: &discordgo.MessageEmbedImage{ diff --git a/cmd_dadjoke.go b/cmd_dadjoke.go index 075e126..68577a2 100644 --- a/cmd_dadjoke.go +++ b/cmd_dadjoke.go @@ -10,6 +10,6 @@ var cmd_dadjoke Command = Command{ Description: "Gives you a random joke that is as bad as your dad would tell them", }, Interact: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - respond(s, i.Interaction, simpleGetFromAPI("joke", "https://icanhazdadjoke.com/").(string), false) + respond(i.Interaction, simpleGetFromAPI("joke", "https://icanhazdadjoke.com/").(string), false) }, } diff --git a/cmd_form.go b/cmd_form.go index 72a590f..3f45dde 100644 --- a/cmd_form.go +++ b/cmd_form.go @@ -13,8 +13,9 @@ var fileData []byte var cmd_form Command = Command{ Definition: discordgo.ApplicationCommand{ - Name: "form", - Description: "Create custom forms right inside Discord", + Name: "form", + DefaultMemberPermissions: int64Ptr(discordgo.PermissionManageChannels), + Description: "Create custom forms right inside Discord", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionSubCommand, @@ -118,7 +119,7 @@ var cmd_form Command = Command{ }, }) case "custom": - respond(s, i.Interaction, "Feature not available yet use `/form add` instead", true) + respond(i.Interaction, "Feature not available yet use `/form add` instead", true) case "add": var title, formID, overwriteTitle, acceptChannelID string var modsCanComment bool @@ -180,63 +181,22 @@ var cmd_form Command = Command{ }, }) addFormButton(i.GuildID, i.ChannelID, message.ID, formManageID.String(), formID, options.Options[0].ChannelValue(s).ID, overwriteTitle, acceptChannelID, modsCanComment) - respond(s, i.Interaction, "Successfully added form button!", true) + respond(i.Interaction, "Successfully added form button!", true) } }, ComponentInteract: func(s *discordgo.Session, i *discordgo.InteractionCreate) { if strings.HasPrefix(i.Interaction.MessageComponentData().CustomID, "form:") { - respond(s, i.Interaction, getFormType(strings.TrimPrefix(i.Interaction.MessageComponentData().CustomID, "form:")), true) + jsonStringShowModal(i.Interaction, i.Interaction.MessageComponentData().CustomID, getFormType(strings.TrimPrefix(i.Interaction.MessageComponentData().CustomID, "form:"))) } if i.Interaction.MessageComponentData().CustomID == "form_demo" { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseModal, - Data: &discordgo.InteractionResponseData{ - CustomID: "form_demo" + i.Interaction.Member.User.ID, - Title: "Demo form", - Components: []discordgo.MessageComponent{ - discordgo.ActionsRow{ - Components: []discordgo.MessageComponent{ - discordgo.TextInput{ - CustomID: "demo_short", - Label: "This is a simple textline", - Style: discordgo.TextInputShort, - Placeholder: "...and it is required!", - Value: "", - Required: true, - MaxLength: 20, - MinLength: 0, - }, - }, - }, - discordgo.ActionsRow{ - Components: []discordgo.MessageComponent{ - discordgo.TextInput{ - CustomID: "demo_paragraph", - Label: "This is a paragraph", - Style: discordgo.TextInputParagraph, - Placeholder: "...and it is not required!", - Value: "We already have some input here", - Required: false, - MaxLength: 2000, - MinLength: 0, - }, - }, - }, - }, - }, - }) + jsonStringShowModal(i.Interaction, "form_demo", "form_demo") } }, - ModalIDs: getFormTypes(), ModalSubmit: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - respond(s, i.Interaction, "The form data would be send to a specified channel. 🤲", true) + respond(i.Interaction, "The form data would be send to a specified channel. 🤲", true) }, Autocomplete: func(s *discordgo.Session, i *discordgo.InteractionCreate) { choices := []*discordgo.ApplicationCommandOptionChoice{ - { - Name: "Feedback", - Value: "template_feedback", - }, { Name: "Support Ticket", Value: "template_ticket", diff --git a/cmd_ping.go b/cmd_ping.go index 69dedb5..8a13a8e 100644 --- a/cmd_ping.go +++ b/cmd_ping.go @@ -36,7 +36,7 @@ var cmd_ping Command = Command{ } else { ping_color = "red" } - respondEmbed(s, i.Interaction, discordgo.MessageEmbed{ + respondEmbed(i.Interaction, discordgo.MessageEmbed{ Title: s.State.User.Username + " ping", Description: fmt.Sprintf("# %.2fms", ping.Seconds()*1000), Type: discordgo.EmbedTypeArticle, diff --git a/cmd_sticky.go b/cmd_sticky.go index 1fbf932..a3b32bd 100644 --- a/cmd_sticky.go +++ b/cmd_sticky.go @@ -53,9 +53,9 @@ var cmd_sticky Command = Command{ if hasSticky(i.GuildID, i.ChannelID) { s.ChannelMessageDelete(i.ChannelID, getStickyMessageID(i.GuildID, i.ChannelID)) removeSticky(i.GuildID, i.ChannelID) - respond(s, i.Interaction, "The sticky message was removed from this channel!", true) + respond(i.Interaction, "The sticky message was removed from this channel!", true) } else { - respond(s, i.Interaction, "This channel has no sticky message!", true) + respond(i.Interaction, "This channel has no sticky message!", true) } } }, @@ -74,9 +74,9 @@ var cmd_sticky Command = Command{ log.Println(err) } if addSticky(i.GuildID, i.ChannelID, text, message.ID) { - respond(s, i.Interaction, "Sticky message in this channel was updated!", true) + respond(i.Interaction, "Sticky message in this channel was updated!", true) } else { - respond(s, i.Interaction, "Message sticked to the channel!", true) + respond(i.Interaction, "Message sticked to the channel!", true) } }, } diff --git a/cmd_tag.go b/cmd_tag.go index 7d0b078..49f193f 100644 --- a/cmd_tag.go +++ b/cmd_tag.go @@ -9,7 +9,7 @@ import ( var cmd_tag Command = Command{ Definition: discordgo.ApplicationCommand{ Name: "tag", - DefaultMemberPermissions: int64Ptr(discordgo.PermissionManageServer), + DefaultMemberPermissions: int64Ptr(discordgo.PermissionManageMessages), Description: "A command to show and edit saved presaved messages.", Options: []*discordgo.ApplicationCommandOption{ { @@ -50,7 +50,7 @@ var cmd_tag Command = Command{ Interact: func(s *discordgo.Session, i *discordgo.InteractionCreate) { switch i.ApplicationCommandData().Options[0].Name { case "get": - GetTagCommand(s, i, i.ApplicationCommandData().Options[0].Options[0]) + GetTagCommand(i, i.ApplicationCommandData().Options[0].Options[0]) case "add": s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseModal, @@ -88,7 +88,7 @@ var cmd_tag Command = Command{ }) case "remove": removeTag(i.GuildID, i.ApplicationCommandData().Options[0].Options[0].StringValue()) - respond(s, i.Interaction, "Tag removed!", true) + respond(i.Interaction, "Tag removed!", true) } }, ModalIDs: []string{"tag_add_modal"}, @@ -96,10 +96,10 @@ var cmd_tag Command = Command{ tagName := i.ModalSubmitData().Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value tagContent := i.ModalSubmitData().Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value addTag(i.GuildID, tagName, tagContent) - respond(s, i.Interaction, "Tag \""+tagName+"\" added!", true) + respond(i.Interaction, "Tag \""+tagName+"\" added!", true) }, Autocomplete: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - AutocompleteTag(s, i) + AutocompleteTag(i) }, } @@ -118,19 +118,19 @@ var cmd_tag_short Command = Command{ }, }, Interact: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - GetTagCommand(s, i, i.ApplicationCommandData().Options[0]) + GetTagCommand(i, i.ApplicationCommandData().Options[0]) }, Autocomplete: func(s *discordgo.Session, i *discordgo.InteractionCreate) { - AutocompleteTag(s, i) + AutocompleteTag(i) }, } -func GetTagCommand(s *discordgo.Session, i *discordgo.InteractionCreate, option *discordgo.ApplicationCommandInteractionDataOption) { - respond(s, i.Interaction, getTagContent(i.GuildID, option.Value.(string)), false) +func GetTagCommand(i *discordgo.InteractionCreate, option *discordgo.ApplicationCommandInteractionDataOption) { + respond(i.Interaction, getTagContent(i.GuildID, option.Value.(string)), false) } -func AutocompleteTag(s *discordgo.Session, i *discordgo.InteractionCreate) { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ +func AutocompleteTag(i *discordgo.InteractionCreate) { + bot.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionApplicationCommandAutocompleteResult, Data: &discordgo.InteractionResponseData{ Choices: generateTagChoices(i.GuildID), diff --git a/form_templates/template_ticket.json b/form_templates/template_ticket.json index 98cc71d..56e83c9 100644 --- a/form_templates/template_ticket.json +++ b/form_templates/template_ticket.json @@ -12,7 +12,7 @@ { "label": "Ticket information", "is_paragraph": true, - "placeholder": "Fill in what you need or have issues with and a moderator will reply.", + "placeholder": "Fill in for what you need help or have issues with and a moderator will reply.", "required": true, "max_length": 2000 } diff --git a/handlers.go b/handlers.go index b7b88e1..e51718b 100644 --- a/handlers.go +++ b/handlers.go @@ -11,13 +11,15 @@ import ( ) type Command struct { - Definition discordgo.ApplicationCommand - Interact func(s *discordgo.Session, i *discordgo.InteractionCreate) - ComponentInteract func(s *discordgo.Session, i *discordgo.InteractionCreate) - ComponentIDs []string - Autocomplete func(s *discordgo.Session, i *discordgo.InteractionCreate) - ModalSubmit func(s *discordgo.Session, i *discordgo.InteractionCreate) - ModalIDs []string + Definition discordgo.ApplicationCommand + Interact func(s *discordgo.Session, i *discordgo.InteractionCreate) + ComponentInteract func(s *discordgo.Session, i *discordgo.InteractionCreate) + Autocomplete func(s *discordgo.Session, i *discordgo.InteractionCreate) + ModalSubmit func(s *discordgo.Session, i *discordgo.InteractionCreate) + ComponentIDs []string + ModalIDs []string + DynamicComponentIDs func() []string + DynamicModalIDs func() []string } var commands []Command = []Command{cmd_form, cmd_tag, cmd_tag_short, cmd_dadjoke, cmd_ping, cmd_ask, cmd_sticky, cmd_cat, cmd_autojoinroles, cmd_autopublish} @@ -61,15 +63,21 @@ func interactionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { command.Autocomplete(s, i) } case discordgo.InteractionModalSubmit: - var hasID bool = false if command.ModalSubmit != nil { - for _, modalID := range command.ModalIDs { - if strings.HasPrefix(i.ModalSubmitData().CustomID, modalID) { - hasID = true - } + // FIXME: Makes it dynamic i don't know why it isn't otherwise + if command.Definition.Name == "form" { + command.ModalIDs = getFormButtonIDs() } - if hasID { - command.ModalSubmit(s, i) + var hasID bool = false + if command.ModalSubmit != nil { + for _, modalID := range command.ModalIDs { + if strings.HasPrefix(i.ModalSubmitData().CustomID, modalID) { + hasID = true + } + } + if hasID { + command.ModalSubmit(s, i) + } } } case discordgo.InteractionMessageComponent: diff --git a/main.go b/main.go index 5643951..a8e3fa6 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ import ( //TODO: add more error handlings var db *sql.DB +var bot *discordgo.Session func main() { godotenv.Load() @@ -29,27 +30,27 @@ func main() { log.Fatal(err) } initTables() - discord, err := discordgo.New("Bot " + os.Getenv("BOT_TOKEN")) + bot, err = discordgo.New("Bot " + os.Getenv("BOT_TOKEN")) if err != nil { fmt.Println("error creating Discord session,", err) return } else { fmt.Println("Discord session created") } - discord.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsGuilds | discordgo.IntentMessageContent | discordgo.IntentGuildMembers - discord.AddHandler(ready) - discord.AddHandler(interactionCreate) - discord.AddHandler(messageCreate) - discord.AddHandler(guildMemberJoin) - err = discord.Open() + bot.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsGuilds | discordgo.IntentMessageContent | discordgo.IntentGuildMembers + bot.AddHandler(ready) + bot.AddHandler(interactionCreate) + bot.AddHandler(messageCreate) + bot.AddHandler(guildMemberJoin) + err = bot.Open() if err != nil { fmt.Println("error opening connection,", err) return } - fmt.Printf("\nBot is now running as \"%s\"!", discord.State.User.Username) + fmt.Printf("\nBot is now running as \"%s\"!", bot.State.User.Username) sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-sc fmt.Println("\nShutting down...") - discord.Close() + bot.Close() } diff --git a/tool.go b/tool.go index 6519048..1633436 100644 --- a/tool.go +++ b/tool.go @@ -2,6 +2,9 @@ package main import ( "encoding/json" + "fmt" + "log" + "os" "strconv" "strings" @@ -23,17 +26,66 @@ type ModalJson struct { Form []ModalJsonField `json:"form"` } -func jsonStringShowModal(jsonString string, id string) { - var modal ModalJson - json.Unmarshal([]byte(jsonString), &modal) +func jsonStringShowModal(interaction *discordgo.Interaction, manageID string, formID string) { + var modal ModalJson = getModalByFormID(formID) + var components []discordgo.MessageComponent + for index, component := range modal.Form { + var style discordgo.TextInputStyle = discordgo.TextInputShort + if component.IsParagraph { + style = discordgo.TextInputParagraph + } + components = append(components, discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.TextInput{ + CustomID: fmt.Sprint(index), + Label: component.Label, + Style: style, + Required: component.Required, + MaxLength: component.MaxLength, + MinLength: component.MinLength, + Value: component.Value, + }, + }, + }) + } + err := bot.InteractionRespond(interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseModal, + Data: &discordgo.InteractionResponseData{ + CustomID: manageID + interaction.Member.User.ID, + Title: modal.Title, + Components: components, + }, + }) + if err != nil { + log.Print(err) + } } -func getHighestRole(s *discordgo.Session, guildID string) (*discordgo.Role, error) { - botMember, err := s.GuildMember(guildID, s.State.User.ID) +func getModalByFormID(formID string) ModalJson { + var modal ModalJson + //TODO: add custom forms + entries, err := os.ReadDir("./form_templates") + if err != nil { + log.Print(err) + } + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), formID) { + json_file, err := os.ReadFile("./form_templates/" + entry.Name()) + if err != nil { + log.Print(err) + } + json.Unmarshal(json_file, &modal) + } + } + return modal +} + +func getHighestRole(guildID string) (*discordgo.Role, error) { + botMember, err := bot.GuildMember(guildID, bot.State.User.ID) if err != nil { return nil, err } - roles, err := s.GuildRoles(guildID) + roles, err := bot.GuildRoles(guildID) if err != nil { return nil, err } @@ -66,12 +118,12 @@ func hexToDecimal(hexColor string) int { return int(decimal) } -func respond(s *discordgo.Session, interaction *discordgo.Interaction, content string, ephemeral bool) { +func respond(interaction *discordgo.Interaction, content string, ephemeral bool) { var flag discordgo.MessageFlags if ephemeral { flag = discordgo.MessageFlagsEphemeral } - s.InteractionRespond(interaction, &discordgo.InteractionResponse{ + bot.InteractionRespond(interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: content, @@ -80,12 +132,12 @@ func respond(s *discordgo.Session, interaction *discordgo.Interaction, content s }) } -func respondEmbed(s *discordgo.Session, interaction *discordgo.Interaction, embed discordgo.MessageEmbed, ephemeral bool) { +func respondEmbed(interaction *discordgo.Interaction, embed discordgo.MessageEmbed, ephemeral bool) { var flag discordgo.MessageFlags if ephemeral { flag = discordgo.MessageFlagsEphemeral } - s.InteractionRespond(interaction, &discordgo.InteractionResponse{ + bot.InteractionRespond(interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: flag,