main.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "log"
  6. "os"
  7. "os/signal"
  8. "strings"
  9. "time"
  10. "github.com/bwmarrin/discordgo"
  11. )
  12. // Bot parameters
  13. var (
  14. GuildID = flag.String("guild", "", "Test guild ID")
  15. BotToken = flag.String("token", "", "Bot access token")
  16. AppID = flag.String("app", "", "Application ID")
  17. )
  18. var s *discordgo.Session
  19. func init() { flag.Parse() }
  20. func init() {
  21. var err error
  22. s, err = discordgo.New("Bot " + *BotToken)
  23. if err != nil {
  24. log.Fatalf("Invalid bot parameters: %v", err)
  25. }
  26. }
  27. // Important note: call every command in order it's placed in the example.
  28. var (
  29. componentsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
  30. "fd_no": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  31. err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
  32. Type: discordgo.InteractionResponseChannelMessageWithSource,
  33. Data: &discordgo.InteractionResponseData{
  34. Content: "Huh. I see, maybe some of these resources might help you?",
  35. Flags: 1 << 6,
  36. Components: []discordgo.MessageComponent{
  37. discordgo.ActionsRow{
  38. Components: []discordgo.MessageComponent{
  39. discordgo.Button{
  40. Emoji: discordgo.ComponentEmoji{
  41. Name: "📜",
  42. },
  43. Label: "Documentation",
  44. Style: discordgo.LinkButton,
  45. URL: "https://discord.com/developers/docs/interactions/message-components#buttons",
  46. },
  47. discordgo.Button{
  48. Emoji: discordgo.ComponentEmoji{
  49. Name: "🔧",
  50. },
  51. Label: "Discord developers",
  52. Style: discordgo.LinkButton,
  53. URL: "https://discord.gg/discord-developers",
  54. },
  55. discordgo.Button{
  56. Emoji: discordgo.ComponentEmoji{
  57. Name: "🦫",
  58. },
  59. Label: "Discord Gophers",
  60. Style: discordgo.LinkButton,
  61. URL: "https://discord.gg/7RuRrVHyXF",
  62. },
  63. },
  64. },
  65. },
  66. },
  67. })
  68. if err != nil {
  69. panic(err)
  70. }
  71. },
  72. "fd_yes": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  73. err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
  74. Type: discordgo.InteractionResponseChannelMessageWithSource,
  75. Data: &discordgo.InteractionResponseData{
  76. Content: "Great! If you wanna know more or just have questions, feel free to visit Discord Devs and Discord Gophers server. " +
  77. "But now, when you know how buttons work, let's move onto select menus (execute `/selects single`)",
  78. Flags: 1 << 6,
  79. Components: []discordgo.MessageComponent{
  80. discordgo.ActionsRow{
  81. Components: []discordgo.MessageComponent{
  82. discordgo.Button{
  83. Emoji: discordgo.ComponentEmoji{
  84. Name: "🔧",
  85. },
  86. Label: "Discord developers",
  87. Style: discordgo.LinkButton,
  88. URL: "https://discord.gg/discord-developers",
  89. },
  90. discordgo.Button{
  91. Emoji: discordgo.ComponentEmoji{
  92. Name: "🦫",
  93. },
  94. Label: "Discord Gophers",
  95. Style: discordgo.LinkButton,
  96. URL: "https://discord.gg/7RuRrVHyXF",
  97. },
  98. },
  99. },
  100. },
  101. },
  102. })
  103. if err != nil {
  104. panic(err)
  105. }
  106. },
  107. "select": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  108. var response *discordgo.InteractionResponse
  109. data := i.MessageComponentData()
  110. switch data.Values[0] {
  111. case "go":
  112. response = &discordgo.InteractionResponse{
  113. Type: discordgo.InteractionResponseChannelMessageWithSource,
  114. Data: &discordgo.InteractionResponseData{
  115. Content: "This is the way.",
  116. Flags: 1 << 6,
  117. },
  118. }
  119. default:
  120. response = &discordgo.InteractionResponse{
  121. Type: discordgo.InteractionResponseChannelMessageWithSource,
  122. Data: &discordgo.InteractionResponseData{
  123. Content: "It is not the way to go.",
  124. Flags: 1 << 6,
  125. },
  126. }
  127. }
  128. err := s.InteractionRespond(i.Interaction, response)
  129. if err != nil {
  130. panic(err)
  131. }
  132. time.Sleep(time.Second) // Doing that so user won't see instant response.
  133. _, err = s.FollowupMessageCreate(*AppID, i.Interaction, true, &discordgo.WebhookParams{
  134. Content: "Anyways, now when you know how to use single select menus, let's see how multi select menus work. " +
  135. "Try calling `/selects multi` command.",
  136. Flags: 1 << 6,
  137. })
  138. if err != nil {
  139. panic(err)
  140. }
  141. },
  142. "stackoverflow_tags": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  143. data := i.MessageComponentData()
  144. const stackoverflowFormat = `https://stackoverflow.com/questions/tagged/%s`
  145. err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
  146. Type: discordgo.InteractionResponseChannelMessageWithSource,
  147. Data: &discordgo.InteractionResponseData{
  148. Content: "Here is your stackoverflow URL: " + fmt.Sprintf(stackoverflowFormat, strings.Join(data.Values, "+")),
  149. Flags: 1 << 6,
  150. },
  151. })
  152. if err != nil {
  153. panic(err)
  154. }
  155. time.Sleep(time.Second) // Doing that so user won't see instant response.
  156. _, err = s.FollowupMessageCreate(*AppID, i.Interaction, true, &discordgo.WebhookParams{
  157. Content: "Now you know everything about select component. If you want to know more or ask a question - feel free to.",
  158. Components: []discordgo.MessageComponent{
  159. discordgo.ActionsRow{
  160. Components: []discordgo.MessageComponent{
  161. discordgo.Button{
  162. Emoji: discordgo.ComponentEmoji{
  163. Name: "📜",
  164. },
  165. Label: "Documentation",
  166. Style: discordgo.LinkButton,
  167. URL: "https://discord.com/developers/docs/interactions/message-components#select-menus",
  168. },
  169. discordgo.Button{
  170. Emoji: discordgo.ComponentEmoji{
  171. Name: "🔧",
  172. },
  173. Label: "Discord developers",
  174. Style: discordgo.LinkButton,
  175. URL: "https://discord.gg/discord-developers",
  176. },
  177. discordgo.Button{
  178. Emoji: discordgo.ComponentEmoji{
  179. Name: "🦫",
  180. },
  181. Label: "Discord Gophers",
  182. Style: discordgo.LinkButton,
  183. URL: "https://discord.gg/7RuRrVHyXF",
  184. },
  185. },
  186. },
  187. },
  188. Flags: 1 << 6,
  189. })
  190. if err != nil {
  191. panic(err)
  192. }
  193. },
  194. }
  195. commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
  196. "buttons": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  197. err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
  198. Type: discordgo.InteractionResponseChannelMessageWithSource,
  199. Data: &discordgo.InteractionResponseData{
  200. Content: "Are you comfortable with buttons and other message components?",
  201. Flags: 1 << 6,
  202. // Buttons and other components are specified in Components field.
  203. Components: []discordgo.MessageComponent{
  204. // ActionRow is a container of all buttons within the same row.
  205. discordgo.ActionsRow{
  206. Components: []discordgo.MessageComponent{
  207. discordgo.Button{
  208. // Label is what the user will see on the button.
  209. Label: "Yes",
  210. // Style provides coloring of the button. There are not so many styles tho.
  211. Style: discordgo.SuccessButton,
  212. // Disabled allows bot to disable some buttons for users.
  213. Disabled: false,
  214. // CustomID is a thing telling Discord which data to send when this button will be pressed.
  215. CustomID: "fd_yes",
  216. },
  217. discordgo.Button{
  218. Label: "No",
  219. Style: discordgo.DangerButton,
  220. Disabled: false,
  221. CustomID: "fd_no",
  222. },
  223. discordgo.Button{
  224. Label: "I don't know",
  225. Style: discordgo.LinkButton,
  226. Disabled: false,
  227. // Link buttons don't require CustomID and do not trigger the gateway/HTTP event
  228. URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  229. Emoji: discordgo.ComponentEmoji{
  230. Name: "🤷",
  231. },
  232. },
  233. },
  234. },
  235. // The message may have multiple actions rows.
  236. discordgo.ActionsRow{
  237. Components: []discordgo.MessageComponent{
  238. discordgo.Button{
  239. Label: "Discord Developers server",
  240. Style: discordgo.LinkButton,
  241. Disabled: false,
  242. URL: "https://discord.gg/discord-developers",
  243. },
  244. },
  245. },
  246. },
  247. },
  248. })
  249. if err != nil {
  250. panic(err)
  251. }
  252. },
  253. "selects": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  254. var response *discordgo.InteractionResponse
  255. switch i.ApplicationCommandData().Options[0].Name {
  256. case "single":
  257. response = &discordgo.InteractionResponse{
  258. Type: discordgo.InteractionResponseChannelMessageWithSource,
  259. Data: &discordgo.InteractionResponseData{
  260. Content: "Now let's take a look on selects. This is single item select menu.",
  261. Flags: 1 << 6,
  262. Components: []discordgo.MessageComponent{
  263. discordgo.ActionsRow{
  264. Components: []discordgo.MessageComponent{
  265. discordgo.SelectMenu{
  266. // Select menu, as other components, must have a customID, so we set it to this value.
  267. CustomID: "select",
  268. Placeholder: "Choose your favorite programming language 👇",
  269. Options: []discordgo.SelectMenuOption{
  270. {
  271. Label: "Go",
  272. // As with components, this things must have their own unique "id" to identify which is which.
  273. // In this case such id is Value field.
  274. Value: "go",
  275. Emoji: discordgo.ComponentEmoji{
  276. Name: "🦦",
  277. },
  278. // You can also make it a default option, but in this case we won't.
  279. Default: false,
  280. Description: "Go programming language",
  281. },
  282. {
  283. Label: "JS",
  284. Value: "js",
  285. Emoji: discordgo.ComponentEmoji{
  286. Name: "🟨",
  287. },
  288. Description: "JavaScript programming language",
  289. },
  290. {
  291. Label: "Python",
  292. Value: "py",
  293. Emoji: discordgo.ComponentEmoji{
  294. Name: "🐍",
  295. },
  296. Description: "Python programming language",
  297. },
  298. },
  299. },
  300. },
  301. },
  302. },
  303. },
  304. }
  305. case "multi":
  306. minValues := 1
  307. response = &discordgo.InteractionResponse{
  308. Type: discordgo.InteractionResponseChannelMessageWithSource,
  309. Data: &discordgo.InteractionResponseData{
  310. Content: "The tastiest things are left for the end. Let's see how the multi-item select menu works: " +
  311. "try generating your own stackoverflow search link",
  312. Flags: 1 << 6,
  313. Components: []discordgo.MessageComponent{
  314. discordgo.ActionsRow{
  315. Components: []discordgo.MessageComponent{
  316. discordgo.SelectMenu{
  317. CustomID: "stackoverflow_tags",
  318. Placeholder: "Select tags to search on StackOverflow",
  319. // This is where confusion comes from. If you don't specify these things you will get single item select.
  320. // These fields control the minimum and maximum amount of selected items.
  321. MinValues: &minValues,
  322. MaxValues: 3,
  323. Options: []discordgo.SelectMenuOption{
  324. {
  325. Label: "Go",
  326. Description: "Simple yet powerful programming language",
  327. Value: "go",
  328. // Default works the same for multi-select menus.
  329. Default: false,
  330. Emoji: discordgo.ComponentEmoji{
  331. Name: "🦦",
  332. },
  333. },
  334. {
  335. Label: "JS",
  336. Description: "Multiparadigm OOP language",
  337. Value: "javascript",
  338. Emoji: discordgo.ComponentEmoji{
  339. Name: "🟨",
  340. },
  341. },
  342. {
  343. Label: "Python",
  344. Description: "OOP prototyping programming language",
  345. Value: "python",
  346. Emoji: discordgo.ComponentEmoji{
  347. Name: "🐍",
  348. },
  349. },
  350. {
  351. Label: "Web",
  352. Description: "Web related technologies",
  353. Value: "web",
  354. Emoji: discordgo.ComponentEmoji{
  355. Name: "🌐",
  356. },
  357. },
  358. {
  359. Label: "Desktop",
  360. Description: "Desktop applications",
  361. Value: "desktop",
  362. Emoji: discordgo.ComponentEmoji{
  363. Name: "💻",
  364. },
  365. },
  366. },
  367. },
  368. },
  369. },
  370. },
  371. },
  372. }
  373. }
  374. err := s.InteractionRespond(i.Interaction, response)
  375. if err != nil {
  376. panic(err)
  377. }
  378. },
  379. }
  380. )
  381. func main() {
  382. s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
  383. log.Println("Bot is up!")
  384. })
  385. // Components are part of interactions, so we register InteractionCreate handler
  386. s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
  387. switch i.Type {
  388. case discordgo.InteractionApplicationCommand:
  389. if h, ok := commandsHandlers[i.ApplicationCommandData().Name]; ok {
  390. h(s, i)
  391. }
  392. case discordgo.InteractionMessageComponent:
  393. if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok {
  394. h(s, i)
  395. }
  396. }
  397. })
  398. _, err := s.ApplicationCommandCreate(*AppID, *GuildID, &discordgo.ApplicationCommand{
  399. Name: "buttons",
  400. Description: "Test the buttons if you got courage",
  401. })
  402. if err != nil {
  403. log.Fatalf("Cannot create slash command: %v", err)
  404. }
  405. _, err = s.ApplicationCommandCreate(*AppID, *GuildID, &discordgo.ApplicationCommand{
  406. Name: "selects",
  407. Options: []*discordgo.ApplicationCommandOption{
  408. {
  409. Type: discordgo.ApplicationCommandOptionSubCommand,
  410. Name: "multi",
  411. Description: "Multi-item select menu",
  412. },
  413. {
  414. Type: discordgo.ApplicationCommandOptionSubCommand,
  415. Name: "single",
  416. Description: "Single-item select menu",
  417. },
  418. },
  419. Description: "Lo and behold: dropdowns are coming",
  420. })
  421. if err != nil {
  422. log.Fatalf("Cannot create slash command: %v", err)
  423. }
  424. err = s.Open()
  425. if err != nil {
  426. log.Fatalf("Cannot open the session: %v", err)
  427. }
  428. defer s.Close()
  429. stop := make(chan os.Signal, 1)
  430. signal.Notify(stop, os.Interrupt)
  431. <-stop
  432. log.Println("Graceful shutdown")
  433. }