123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- package main
- import (
- "flag"
- "fmt"
- "log"
- "os"
- "os/signal"
- "time"
- "github.com/bwmarrin/discordgo"
- )
- // Bot parameters
- var (
- GuildID = flag.String("guild", "", "Test guild ID. If not passed - bot registers commands globally")
- BotToken = flag.String("token", "", "Bot access token")
- RemoveCommands = flag.Bool("rmcmd", true, "Remove all commands after shutdowning or not")
- )
- var s *discordgo.Session
- func init() { flag.Parse() }
- func init() {
- var err error
- s, err = discordgo.New("Bot " + *BotToken)
- if err != nil {
- log.Fatalf("Invalid bot parameters: %v", err)
- }
- }
- var (
- commands = []*discordgo.ApplicationCommand{
- {
- Name: "basic-command",
- // All commands and options must have an description
- // Commands/options without description will fail the registration
- // of the command.
- Description: "Basic command",
- },
- {
- Name: "options",
- Description: "Command for demonstrating options",
- Options: []*discordgo.ApplicationCommandOption{
- {
- Type: discordgo.ApplicationCommandOptionString,
- Name: "string-option",
- Description: "String option",
- Required: true,
- },
- {
- Type: discordgo.ApplicationCommandOptionInteger,
- Name: "integer-option",
- Description: "Integer option",
- Required: true,
- },
- {
- Type: discordgo.ApplicationCommandOptionBoolean,
- Name: "bool-option",
- Description: "Boolean option",
- Required: true,
- },
- // Required options must be listed first, because
- // like everyone knows - optional parameters is on the back.
- // The same concept applies to Discord's Slash-commands API
- {
- Type: discordgo.ApplicationCommandOptionChannel,
- Name: "channel-option",
- Description: "Channel option",
- Required: false,
- },
- {
- Type: discordgo.ApplicationCommandOptionUser,
- Name: "user-option",
- Description: "User option",
- Required: false,
- },
- {
- Type: discordgo.ApplicationCommandOptionRole,
- Name: "role-option",
- Description: "Role option",
- Required: false,
- },
- },
- },
- {
- Name: "subcommands",
- Description: "Subcommands and command groups example",
- Options: []*discordgo.ApplicationCommandOption{
- // When command have subcommands/subcommand groups
- // It must not have top-level options, they aren't accesible in the UI
- // in this case (at least, yet), so if command is with
- // subcommands/subcommand groups registering top-level options
- // will fail the registration of the command
- {
- Name: "scmd-grp",
- Description: "Subcommands group",
- Options: []*discordgo.ApplicationCommandOption{
- // Also, subcommand groups isn't capable of
- // containg options, by the name of them, you can see
- // they can contain only subcommands
- {
- Name: "nst-subcmd",
- Description: "Nested subcommand",
- Type: discordgo.ApplicationCommandOptionSubCommand,
- },
- },
- Type: discordgo.ApplicationCommandOptionSubCommandGroup,
- },
- // Also, you can create both subcommand groups and subcommands
- // in the command at the same time. But, there's some limits to
- // nesting, count of subcommands (top level and nested) and options.
- // Read the intro of slash-commands docs on Discord dev portal
- // to get more information
- {
- Name: "subcmd",
- Description: "Top-level subcommand",
- Type: discordgo.ApplicationCommandOptionSubCommand,
- },
- },
- },
- {
- Name: "responses",
- Description: "Interaction responses testing initiative",
- Options: []*discordgo.ApplicationCommandOption{
- {
- Name: "resp-type",
- Description: "Response type",
- Type: discordgo.ApplicationCommandOptionInteger,
- Choices: []*discordgo.ApplicationCommandOptionChoice{
- {
- Name: "Acknowledge",
- Value: 2,
- },
- {
- Name: "Channel message",
- Value: 3,
- },
- {
- Name: "Channel message with source",
- Value: 4,
- },
- {
- Name: "Acknowledge with source",
- Value: 5,
- },
- },
- Required: true,
- },
- },
- },
- {
- Name: "followups",
- Description: "Followup messages",
- },
- }
- commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
- "basic-command": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- Type: discordgo.InteractionResponseChannelMessageWithSource,
- Data: &discordgo.InteractionApplicationCommandResponseData{
- Content: "Hey there! Congratulations, you just executed your first slash command",
- },
- })
- },
- "options": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- margs := []interface{}{
- // Here we need to convert raw interface{} value to wanted type.
- // Also, as you can see, here is used utility functions to convert the value
- // to particular type. Yeah, you can use just switch type,
- // but this is much simpler
- i.Data.Options[0].StringValue(),
- i.Data.Options[1].IntValue(),
- i.Data.Options[2].BoolValue(),
- }
- msgformat :=
- ` Now you just leared how to use command options. Take a look to the value of which you've just entered:
- > string_option: %s
- > integer_option: %d
- > bool_option: %v
- `
- if len(i.Data.Options) >= 4 {
- margs = append(margs, i.Data.Options[3].ChannelValue(nil).ID)
- msgformat += "> channel-option: <#%s>\n"
- }
- if len(i.Data.Options) >= 5 {
- margs = append(margs, i.Data.Options[4].UserValue(nil).ID)
- msgformat += "> user-option: <@%s>\n"
- }
- if len(i.Data.Options) >= 6 {
- margs = append(margs, i.Data.Options[5].RoleValue(nil, "").ID)
- msgformat += "> role-option: <@&%s>\n"
- }
- s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- // Ignore type for now, we'll discuss them in "responses" part
- Type: discordgo.InteractionResponseChannelMessageWithSource,
- Data: &discordgo.InteractionApplicationCommandResponseData{
- Content: fmt.Sprintf(
- msgformat,
- margs...,
- ),
- },
- })
- },
- "subcommands": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- content := ""
- // As you can see, the name of subcommand (nested, top-level) or subcommand group
- // is provided through arguments.
- switch i.Data.Options[0].Name {
- case "subcmd":
- content =
- "The top-level subcommand is executed. Now try to execute nested one."
- default:
- if i.Data.Options[0].Name != "scmd-grp" {
- return
- }
- switch i.Data.Options[0].Options[0].Name {
- case "nst-subcmd":
- content = "Nice, now you know how to execute nested commands too"
- default:
- // I added this in the case something might go wrong
- content = "Oops, something gone wrong.\n" +
- "Hol' up, you aren't supposed to see this message."
- }
- }
- s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- Type: discordgo.InteractionResponseChannelMessageWithSource,
- Data: &discordgo.InteractionApplicationCommandResponseData{
- Content: content,
- },
- })
- },
- "responses": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- // Responses to a command is really important thing.
- // First of all, because you need to react to the interaction
- // by sending the response in 3 seconds after receiving, otherwise
- // interaction will be considered invalid and you can no longer
- // use interaction token and ID for responding to the user's request
- content := ""
- // As you can see, response type names saying by themselvs
- // how they're used, but for those who want to get
- // more information - read the official documentation
- switch i.Data.Options[0].IntValue() {
- case int64(discordgo.InteractionResponseChannelMessage):
- content =
- "Well, you just responded to an interaction, and sent a message.\n" +
- "That's all what I wanted to say, yeah."
- content +=
- "\nAlso... you can edit your response, wait 5 seconds and this message will be changed"
- case int64(discordgo.InteractionResponseChannelMessageWithSource):
- content =
- "You just responded to an interaction, sent a message and showed the original one. " +
- "Congratulations!"
- content +=
- "\nAlso... you can edit your response, wait 5 seconds and this message will be changed"
- default:
- err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- Type: discordgo.InteractionResponseType(i.Data.Options[0].IntValue()),
- })
- if err != nil {
- s.FollowupMessageCreate(s.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
- Content: "Something gone wrong",
- })
- }
- return
- }
- err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- Type: discordgo.InteractionResponseType(i.Data.Options[0].IntValue()),
- Data: &discordgo.InteractionApplicationCommandResponseData{
- Content: content,
- },
- })
- if err != nil {
- s.FollowupMessageCreate(s.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
- Content: "Something gone wrong",
- })
- return
- }
- time.AfterFunc(time.Second*5, func() {
- err = s.InteractionResponseEdit(s.State.User.ID, i.Interaction, &discordgo.WebhookEdit{
- Content: content + "\n\nWell, now you know how to create and edit responses. " +
- "But you still don't know how to delete them... so... wait 10 seconds and this " +
- "message will be deleted.",
- })
- if err != nil {
- s.FollowupMessageCreate(s.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
- Content: "Something gone wrong",
- })
- return
- }
- time.Sleep(time.Second * 10)
- s.InteractionResponseDelete(s.State.User.ID, i.Interaction)
- })
- },
- "followups": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- // Followup messages is basically regular messages (you can create as many of them as you wish),
- // but working as they is created by webhooks and their functional
- // is for handling additional messages after sending response.
- s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
- Type: discordgo.InteractionResponseChannelMessageWithSource,
- Data: &discordgo.InteractionApplicationCommandResponseData{
- // Note: this isn't documented, but you can use that if you want to.
- // This flag just allows to create messages visible only for the caller (user who triggered the command)
- // of the command
- Flags: 1 << 6,
- Content: "Surprise!",
- },
- })
- msg, err := s.FollowupMessageCreate(s.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
- Content: "Followup message has created, after 5 seconds it will be edited",
- })
- if err != nil {
- s.FollowupMessageCreate(s.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
- Content: "Something gone wrong",
- })
- return
- }
- time.Sleep(time.Second * 5)
- s.FollowupMessageEdit(s.State.User.ID, i.Interaction, msg.ID, &discordgo.WebhookEdit{
- Content: "Now original message is gone and after 10 seconds this message will ~~self-destruct~~ be deleted.",
- })
- time.Sleep(time.Second * 10)
- s.FollowupMessageDelete(s.State.User.ID, i.Interaction, msg.ID)
- s.FollowupMessageCreate(s.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
- Content: "For those, who didn't skip anything and followed tutorial along fairly, " +
- "take a unicorn :unicorn: as reward!\n" +
- "Also, as bonus..., look at the original interaction response :D",
- })
- },
- }
- )
- func init() {
- s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
- if h, ok := commandHandlers[i.Data.Name]; ok {
- h(s, i)
- }
- })
- }
- func main() {
- s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
- log.Println("Bot is up!")
- })
- err := s.Open()
- if err != nil {
- log.Fatalf("Cannot open the session: %v", err)
- }
- for _, v := range commands {
- _, err := s.ApplicationCommandCreate(s.State.User.ID, *GuildID, v)
- if err != nil {
- log.Panicf("Cannot create '%v' command: %v", v.Name, err)
- }
- }
- defer s.Close()
- stop := make(chan os.Signal)
- signal.Notify(stop, os.Interrupt)
- <-stop
- log.Println("Gracefully shutdowning")
- }
|