Skip to content

Commit 330fd03

Browse files
committed
feat: new components
1 parent ee63c3d commit 330fd03

File tree

17 files changed

+1214
-172
lines changed

17 files changed

+1214
-172
lines changed

examples/components.rb

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# frozen_string_literal: true
2+
3+
require 'discordrb'
4+
5+
bot = Discordrb::Bot.new(token: ENV.fetch('BOT_TOKEN', nil), intents: %i[servers server_emojis server_messages])
6+
7+
bot.register_application_command(:container, 'An example of a container component.', server_id: ENV.fetch('SERVER_ID', nil)) do |option|
8+
option.boolean(:color, 'Whether the container should include an accent color.', required: false)
9+
end
10+
11+
bot.application_command(:container) do |event|
12+
# Transform the first 15 emojis from the server our command is
13+
# called from into the formart of: "mention - name **{Integer}**".
14+
emojis = event.server.emojis.values.shuffle.take(15).map do |emoji|
15+
"#{emoji.mention}#{emoji.name} **(#{rand(2001..5001)})**\n"
16+
end
17+
18+
# The `has_components` flag must be manually set to true to enable uikit components.
19+
event.respond(has_components: true) do |_, view|
20+
# A new container is added to contain other components. We don't have to do this,
21+
# since any non-interactive component can be used as top level component.
22+
view.container do |container|
23+
# A section must have either a thumbnail or a button. This is currently
24+
# the only case where a button can be used without being in an action row.
25+
container.section do |section|
26+
section.thumbnail(url: event.server.icon_url)
27+
section.text_display(text: "### Emoji Statistics for #{event.server.name}")
28+
section.text_display(text: 'These are the fake emoji statistics for your server.')
29+
end
30+
31+
# Unlike embeds, if the accent color isn't set, the container simply won't have an accent color.
32+
container.color = rand(0..0xFFFFFF) if event.options['color']
33+
34+
# A seperator can appear as a thin, and translucent line when setting `divider` to true. Otherwise,
35+
# the seperator can function as an invisible barrier to proivde padding between components.
36+
container.seperator(divider: true, spacing: :small)
37+
38+
# A text display is a container for text. Similar to the `content` field, you can use mentions, MDX, etc.
39+
container.text_display(text: emojis.empty? ? 'No Emojis!' : emojis.join)
40+
41+
# Try setting the spacing to `:large` to have a bigger gap between the other components.
42+
container.seperator(divider: true, spacing: :small)
43+
44+
# We clear the existing emojis array and add a random emoji to the emojis array.
45+
emojis.clear && 3.times { emojis << event.server.emojis.values.sample }
46+
47+
# We can add a select menu inside of our containter as shown here. Since this is an action row, we could've
48+
# chosen to add buttons here instead, but for the sake of the example, we'll stick to a select menu.
49+
container.row do |row|
50+
row.select_menu(custom_id: 'emojis', placeholder: 'Pick a statistic type...', min_values: 1) do |menu|
51+
menu.option(label: 'Reaction', value: 'Reaction', description: 'View reaction statistics.', emoji: emojis.pop)
52+
menu.option(label: 'Message', value: 'Message', description: 'View message statistics.', emoji: emojis.pop)
53+
menu.option(label: 'Lowest', value: 'Lowest', description: 'View the boring emojis.', emoji: emojis.pop)
54+
end
55+
end
56+
end
57+
end
58+
end
59+
60+
# This doesn't actually display any stats, but returns a placeholder message instead.
61+
bot.select_menu(custom_id: 'emojis') do |event|
62+
case event.values.first
63+
when 'Reaction', 'Message'
64+
event.respond(content: "You're viewing stats for #{event.values.first}s!", ephemeral: true)
65+
when 'Lowest'
66+
event.respond(content: "You're viewing very boring stats!", ephemeral: true)
67+
else
68+
event.respond(content: 'What kind of stats...', ephemeral: true)
69+
end
70+
end
71+
72+
# The second example is a little more generic and uses a standard message sent to a channel.
73+
bot.message(content: '!sample') do |event|
74+
# Any of the components used below can be used within a container as well.
75+
event.send_message!(has_components: true) do |_, view|
76+
view.text_display(text: <<~CONTENT)
77+
This is a text display component! Any markdown that can be used in the `content` field can also be used here.\n
78+
~~strikethrough~~ **bold text** *italics* ||spoiler|| `code` __underline__ [masked link](https://youtu.be/dQw4w9WgXcQ)\n
79+
```ruby
80+
puts("Hello World!")
81+
```
82+
CONTENT
83+
84+
# Just like in the example above, we can set `:divider` to true in order to generate a barrier.
85+
view.seperator(divider: true, spacing: :large)
86+
87+
# A media gallery is a container for multiple pieces of media, such as videos, GIFs or staic images.
88+
# You can add optionally add alt text via the `description:` argument and spoiler each media item.
89+
view.media_gallery do |gallery|
90+
gallery.item(url: 'https://static.wikitide.net/sillycatsbookwiki/f/f7/Apple_Cat.jpg', spoiler: true)
91+
gallery.item(url: 'https://media.tenor.com/JNrPF3XuHXIAAAAd/java-duke.gif', description: 'Factory')
92+
end
93+
94+
# A Section allows you to group together text display components and pair them with an accessory.
95+
# In the emoji stats example above, we had a section with a thumbnail component. At the time of writing,
96+
# a section must contain either a thumbnail or a button.
97+
view.section do |section|
98+
section.text_display(text: 'This is text from a section. This section has a button instead of a thumbnail.')
99+
100+
section.button(label: 'Delete', style: :danger, custom_id: 'delete_message', emoji: 577658883468689409)
101+
end
102+
end
103+
end
104+
105+
# Delete the message that was sent in response to "!sample".
106+
bot.button(custom_id: 'delete_message') do |event|
107+
event.respond(content: 'Successfully deleted the message.', ephemeral: true)
108+
109+
event.message.delete
110+
end
111+
112+
# This last example shows how a file component looks.
113+
bot.message(content: '!file') do |event|
114+
# Any attachments that are provided must be manually exposed via the component system.
115+
event.send_message!(attachments: [File.open('data/music.mp3', 'rb')], has_components: true) do |_, view|
116+
view.container do |container|
117+
# All components accept an `id:` KWARG. This ID can be any 32-bit integer. This is
118+
# not to be confused with the `custom_id:` parameter.
119+
container.section(id: rand(500..600)) do |section|
120+
section.thumbnail(url: 'https://cdn.discordapp.com/icons/81384788765712384/a363a84e969bcbe1353eb2fdfb2e50e6.webp')
121+
122+
section.text_display(text: '### Musical File')
123+
124+
# All of the information below can be found if you inspect the audio file's metadata.
125+
section.text_display(text: <<~CONTENT)
126+
> **Title:** Discordrb Theme
127+
> **Composed:** <t:1472839597:R>
128+
> **Album:** Discord API Music
129+
CONTENT
130+
end
131+
132+
# Try setting `spoiler` to true in order to spoiler the file.
133+
container.file(url: 'attachment://music.mp3', spoiler: false)
134+
end
135+
end
136+
end
137+
138+
bot.run

examples/modals.rb

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,46 @@
99

1010
bot.application_command :modal_test do |event|
1111
event.show_modal(title: 'Test modal', custom_id: 'test1234') do |modal|
12-
modal.row do |row|
13-
row.text_input(
12+
modal.label(label: 'Test input') do |label|
13+
label.text_input(
1414
style: :paragraph,
1515
custom_id: 'input',
16-
label: 'Test input',
1716
required: true,
1817
placeholder: 'Type something to submit.'
1918
)
2019
end
20+
21+
# We can add a select menu inside of a modal as well.
22+
modal.label(label: 'Fruit Picker') do |label|
23+
label.string_select(custom_id: 'fruits', placeholder: 'Pick a fruit...', max_values: 3, required: false) do |menu|
24+
menu.option(label: 'Banana', value: 'banana', description: 'A yellow pulpy curved fruit.', emoji: '🍌')
25+
menu.option(label: 'Peach', value: 'peach', description: 'A soft orange-ish fuzzy fruit.', emoji: '🍑')
26+
menu.option(label: 'Pear', value: 'pear', description: 'A green fruit with gritty pulp.', emoji: '🍐')
27+
end
28+
end
29+
30+
# Text displays are allowed to be used as a top level component.
31+
modal.text_display(text: <<~CONTENT)
32+
This is a text display component in a modal! This is the same as a normal text display component.\n
33+
~~strikethrough~~ **bold text** *italics* ||spoiler|| `code` __underline__ [masked link](https://youtu.be/dQw4w9WgXcQ)\n
34+
```ruby
35+
puts("Hello Modal!")
36+
```
37+
CONTENT
38+
39+
modal.label(label: 'Channel Picker', description: 'This is an optional description for a label component.') do |label|
40+
label.channel_select(custom_id: 'channels', placeholder: 'Pick a channel...', required: true, max_values: 7, types: %i[text news])
41+
end
2142
end
2243
end
2344

2445
bot.application_command :modal_await_test do |event|
2546
id = SecureRandom.uuid
2647
event.show_modal(title: "I'm waiting for you", custom_id: id) do |modal|
27-
modal.row do |row|
28-
row.text_input(
48+
modal.label(label: 'Test input') do |label|
49+
label.text_input(
2950
style: :paragraph,
3051
custom_id: 'input',
31-
label: 'Test input',
3252
required: true,
3353
placeholder: 'Type something to submit.'
3454
)
@@ -46,7 +66,10 @@
4666
end
4767

4868
bot.modal_submit custom_id: 'test1234' do |event|
49-
event.respond(content: "Thanks for submitting your modal. You sent #{event.value('input').chars.count} characters.")
69+
# The selected values for the string select can be accessed via {#values}.
70+
select_response = "and picked #{event.values('fruits')&.size} fruits."
71+
72+
event.respond(content: "Thanks for submitting your modal. You sent #{event.value('input').chars.count} characters, #{select_response}")
5073
end
5174

5275
bot.run

lib/discordrb/api/interaction.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def get_original_interaction_response(interaction_token, application_id)
5151

5252
# Edit the original response to an interaction.
5353
# https://discord.com/developers/docs/interactions/slash-commands#edit-original-interaction-response
54-
def edit_original_interaction_response(interaction_token, application_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil, attachments = nil)
55-
Discordrb::API::Webhook.token_edit_message(interaction_token, application_id, '@original', content, embeds, allowed_mentions, components, attachments)
54+
def edit_original_interaction_response(interaction_token, application_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil, attachments = nil, flags = nil)
55+
Discordrb::API::Webhook.token_edit_message(interaction_token, application_id, '@original', content, embeds, allowed_mentions, components, attachments, flags)
5656
end
5757

5858
# Delete the original response to an interaction.

lib/discordrb/api/webhook.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ def token_get_message(webhook_token, webhook_id, message_id)
120120

121121
# Edit a webhook message via webhook token
122122
# https://discord.com/developers/docs/resources/webhook#edit-webhook-message
123-
def token_edit_message(webhook_token, webhook_id, message_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil, attachments = nil)
124-
body = { content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components }
123+
def token_edit_message(webhook_token, webhook_id, message_id, content = nil, embeds = nil, allowed_mentions = nil, components = nil, attachments = nil, flags = nil)
124+
body = { content: content, embeds: embeds, allowed_mentions: allowed_mentions, components: components, flags: flags }
125125

126126
body = if attachments
127127
files = [*0...attachments.size].zip(attachments).to_h

lib/discordrb/data/channel.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ def slowmode?
428428
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
429429
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
430430
# @param components [View, Array<Hash>] Interaction components to associate with this message.
431-
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2) and SUPPRESS_NOTIFICATIONS (1 << 12) can be set.
431+
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2), SUPPRESS_NOTIFICATIONS (1 << 12), and IS_COMPONENTS_V2 (1 << 15) can be set.
432432
# @return [Message] the message that was sent.
433433
def send_message(content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0)
434434
@bot.send_message(@id, content, tts, embed, attachments, allowed_mentions, message_reference, components, flags)
@@ -445,7 +445,7 @@ def send_message(content, tts = false, embed = nil, attachments = nil, allowed_m
445445
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
446446
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
447447
# @param components [View, Array<Hash>] Interaction components to associate with this message.
448-
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2) and SUPPRESS_NOTIFICATIONS (1 << 12) can be set.
448+
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2), SUPPRESS_NOTIFICATIONS (1 << 12), and IS_COMPONENTS_V2 (1 << 15) can be set.
449449
def send_temporary_message(content, timeout, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil, flags = 0)
450450
@bot.send_temporary_message(@id, content, timeout, tts, embed, attachments, allowed_mentions, message_reference, components, flags)
451451
end
@@ -463,7 +463,7 @@ def send_temporary_message(content, timeout, tts = false, embed = nil, attachmen
463463
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
464464
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
465465
# @param components [View, Array<Hash>] Interaction components to associate with this message.
466-
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2) and SUPPRESS_NOTIFICATIONS (1 << 12) can be set.
466+
# @param flags [Integer] Flags for this message. Currently only SUPPRESS_EMBEDS (1 << 2), SUPPRESS_NOTIFICATIONS (1 << 12), and IS_COMPONENTS_V2 (1 << 15) can be set.
467467
# @yield [embed] Yields the embed to allow for easy building inside a block.
468468
# @yieldparam embed [Discordrb::Webhooks::Embed] The embed from the parameters, or a new one.
469469
# @return [Message] The resulting message.

0 commit comments

Comments
 (0)