Skip to content

Commit c34a3bc

Browse files
authored
fix(discord): align tools with live API docs, add missing endpoints (#5472)
* fix(discord): align tools with live API docs, add missing endpoints - fix ban_member deprecated delete_message_days -> delete_message_seconds - fix get_server phantom member_count/channels outputs, add with_counts support - fix create_thread missing type field causing silent private-thread default - fix delete_channel to return the deleted channel body - fix get_pinned_messages to use the current (non-deprecated) pins endpoint and drop an unused required serverId param - add .trim() on all URL-interpolated IDs and bot tokens across every tool - add discord_list_channels, discord_list_roles, discord_get_pinned_messages, discord_bulk_delete_messages - fix block subBlock required flags (userId, content, messageId) to match each operation's actual tool requirements - remove orphaned Discord OAuth scope descriptions (Discord uses bot tokens, not OAuth) * fix(discord): guard against whitespace-only messageId in create_thread Cursor Bugbot found that a whitespace-only messageId was treated as present (truthy), routing to the message-thread URL and trimming to an empty path segment instead of creating a standalone thread. * fix(discord): guard whitespace userId, add pins pagination - remove_reaction: whitespace-only userId no longer breaks the /@me fallback (Cursor Bugbot) - get_pinned_messages: add limit/before query params so pins beyond the first page (max 50) can be retrieved (Cursor Bugbot) - export DiscordMessage type and properly type pinned-message mapping * fix(discord): clamp shared limit subBlock to 1-50 for pinned messages The limit subBlock is shared between discord_get_messages (max 100) and discord_get_pinned_messages (max 50 per Discord's API), so a value carried over from the messages operation could exceed the pins endpoint's max and trigger a 400. Clamp at both the block dispatcher and the tool's request builder. * fix(discord): validate 2-100 message count before bulk delete Discord's bulk-delete endpoint requires 2-100 message IDs; forwarding an out-of-range count produced an opaque Discord API error instead of a clear preflight message.
1 parent f3a65e1 commit c34a3bc

44 files changed

Lines changed: 625 additions & 97 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/blocks/blocks/discord.ts

Lines changed: 133 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
2929
{ label: 'Get User Information', id: 'discord_get_user' },
3030
{ label: 'Edit Message', id: 'discord_edit_message' },
3131
{ label: 'Delete Message', id: 'discord_delete_message' },
32+
{ label: 'Bulk Delete Messages', id: 'discord_bulk_delete_messages' },
3233
{ label: 'Add Reaction', id: 'discord_add_reaction' },
3334
{ label: 'Remove Reaction', id: 'discord_remove_reaction' },
3435
{ label: 'Pin Message', id: 'discord_pin_message' },
3536
{ label: 'Unpin Message', id: 'discord_unpin_message' },
37+
{ label: 'Get Pinned Messages', id: 'discord_get_pinned_messages' },
3638
{ label: 'Create Thread', id: 'discord_create_thread' },
3739
{ label: 'Join Thread', id: 'discord_join_thread' },
3840
{ label: 'Leave Thread', id: 'discord_leave_thread' },
@@ -41,11 +43,13 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
4143
{ label: 'Update Channel', id: 'discord_update_channel' },
4244
{ label: 'Delete Channel', id: 'discord_delete_channel' },
4345
{ label: 'Get Channel', id: 'discord_get_channel' },
46+
{ label: 'List Channels', id: 'discord_list_channels' },
4447
{ label: 'Create Role', id: 'discord_create_role' },
4548
{ label: 'Update Role', id: 'discord_update_role' },
4649
{ label: 'Delete Role', id: 'discord_delete_role' },
4750
{ label: 'Assign Role', id: 'discord_assign_role' },
4851
{ label: 'Remove Role', id: 'discord_remove_role' },
52+
{ label: 'List Roles', id: 'discord_list_roles' },
4953
{ label: 'Kick Member', id: 'discord_kick_member' },
5054
{ label: 'Ban Member', id: 'discord_ban_member' },
5155
{ label: 'Unban Member', id: 'discord_unban_member' },
@@ -92,10 +96,12 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
9296
'discord_get_messages',
9397
'discord_edit_message',
9498
'discord_delete_message',
99+
'discord_bulk_delete_messages',
95100
'discord_add_reaction',
96101
'discord_remove_reaction',
97102
'discord_pin_message',
98103
'discord_unpin_message',
104+
'discord_get_pinned_messages',
99105
'discord_create_thread',
100106
'discord_update_channel',
101107
'discord_delete_channel',
@@ -121,10 +127,35 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
121127
'discord_remove_reaction',
122128
'discord_pin_message',
123129
'discord_unpin_message',
124-
'discord_create_thread',
125130
],
126131
},
127132
},
133+
// Message ID (optional) - for creating a thread from an existing message
134+
{
135+
id: 'messageId',
136+
title: 'Message ID',
137+
type: 'short-input',
138+
placeholder: 'Enter message ID (leave empty for a standalone thread)',
139+
condition: {
140+
field: 'operation',
141+
value: ['discord_create_thread'],
142+
},
143+
},
144+
// Message IDs - for bulk delete
145+
{
146+
id: 'messageIds',
147+
title: 'Message IDs',
148+
type: 'long-input',
149+
placeholder: 'Comma-separated message IDs to delete (2-100)',
150+
required: true,
151+
condition: { field: 'operation', value: 'discord_bulk_delete_messages' },
152+
wandConfig: {
153+
enabled: true,
154+
prompt:
155+
'Generate a comma-separated list of Discord message IDs (2-100 numeric snowflake IDs) based on the context: {context}. Return ONLY the comma-separated list - no explanations, no extra text.',
156+
placeholder: 'Describe which messages to delete',
157+
},
158+
},
128159
// Content - for send/edit message
129160
{
130161
id: 'content',
@@ -133,32 +164,44 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
133164
placeholder: 'Enter message content...',
134165
condition: {
135166
field: 'operation',
136-
value: ['discord_send_message', 'discord_edit_message', 'discord_execute_webhook'],
167+
value: ['discord_send_message', 'discord_edit_message'],
168+
},
169+
},
170+
// Content (required) - for executing a webhook
171+
{
172+
id: 'content',
173+
title: 'Message Content',
174+
type: 'long-input',
175+
placeholder: 'Enter message content...',
176+
required: true,
177+
condition: {
178+
field: 'operation',
179+
value: ['discord_execute_webhook'],
137180
},
138181
},
139182
// Emoji - for reaction operations
140183
{
141184
id: 'emoji',
142185
title: 'Emoji',
143186
type: 'short-input',
144-
placeholder: 'Enter emoji (e.g., 👍 or custom:123456789)',
187+
placeholder: 'Enter emoji (e.g., 👍 or emoji_name:123456789012345678 for custom emoji)',
145188
required: true,
146189
condition: {
147190
field: 'operation',
148191
value: ['discord_add_reaction', 'discord_remove_reaction'],
149192
},
150193
},
151-
// User ID - for user/member operations
194+
// User ID (required) - for user/member operations
152195
{
153196
id: 'userId',
154197
title: 'User ID',
155198
type: 'short-input',
156199
placeholder: 'Enter Discord user ID',
200+
required: true,
157201
condition: {
158202
field: 'operation',
159203
value: [
160204
'discord_get_user',
161-
'discord_remove_reaction',
162205
'discord_assign_role',
163206
'discord_remove_role',
164207
'discord_kick_member',
@@ -169,6 +212,17 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
169212
],
170213
},
171214
},
215+
// User ID (optional) - to remove a specific user's reaction
216+
{
217+
id: 'userId',
218+
title: 'User ID',
219+
type: 'short-input',
220+
placeholder: 'Enter Discord user ID (leave empty to remove your own reaction)',
221+
condition: {
222+
field: 'operation',
223+
value: ['discord_remove_reaction'],
224+
},
225+
},
172226
// Thread ID - for thread operations
173227
{
174228
id: 'threadId',
@@ -329,6 +383,24 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
329383
placeholder: 'Number of messages (default: 10, max: 100)',
330384
condition: { field: 'operation', value: 'discord_get_messages' },
331385
},
386+
// Limit (for get pinned messages)
387+
{
388+
id: 'limit',
389+
title: 'Pin Limit',
390+
type: 'short-input',
391+
placeholder: 'Number of pins per page (default: 50, max: 50)',
392+
mode: 'advanced',
393+
condition: { field: 'operation', value: 'discord_get_pinned_messages' },
394+
},
395+
// Before (pagination cursor for get pinned messages)
396+
{
397+
id: 'before',
398+
title: 'Before',
399+
type: 'short-input',
400+
placeholder: 'Return pins before this ISO8601 timestamp (for paging)',
401+
mode: 'advanced',
402+
condition: { field: 'operation', value: 'discord_get_pinned_messages' },
403+
},
332404
// Auto Archive Duration (for threads)
333405
{
334406
id: 'autoArchiveDuration',
@@ -346,6 +418,22 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
346418
value: ['discord_create_thread'],
347419
},
348420
},
421+
// Thread Visibility (for create_thread, standalone threads only)
422+
{
423+
id: 'threadVisibility',
424+
title: 'Thread Visibility',
425+
type: 'dropdown',
426+
options: [
427+
{ label: 'Public - visible to everyone in the channel', id: 'public' },
428+
{ label: 'Private - invite-only', id: 'private' },
429+
],
430+
value: () => 'public',
431+
mode: 'advanced',
432+
condition: {
433+
field: 'operation',
434+
value: ['discord_create_thread'],
435+
},
436+
},
349437
// Channel Type (for create_channel)
350438
{
351439
id: 'channelType',
@@ -354,6 +442,7 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
354442
options: [
355443
{ label: 'Text Channel', id: '0' },
356444
{ label: 'Voice Channel', id: '2' },
445+
{ label: 'Category', id: '4' },
357446
{ label: 'Announcement Channel', id: '5' },
358447
{ label: 'Stage Channel', id: '13' },
359448
{ label: 'Forum Channel', id: '15' },
@@ -537,10 +626,12 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
537626
'discord_get_user',
538627
'discord_edit_message',
539628
'discord_delete_message',
629+
'discord_bulk_delete_messages',
540630
'discord_add_reaction',
541631
'discord_remove_reaction',
542632
'discord_pin_message',
543633
'discord_unpin_message',
634+
'discord_get_pinned_messages',
544635
'discord_create_thread',
545636
'discord_join_thread',
546637
'discord_leave_thread',
@@ -549,11 +640,13 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
549640
'discord_update_channel',
550641
'discord_delete_channel',
551642
'discord_get_channel',
643+
'discord_list_channels',
552644
'discord_create_role',
553645
'discord_update_role',
554646
'discord_delete_role',
555647
'discord_assign_role',
556648
'discord_remove_role',
649+
'discord_list_roles',
557650
'discord_kick_member',
558651
'discord_ban_member',
559652
'discord_unban_member',
@@ -612,14 +705,30 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
612705
channelId: params.channelId,
613706
messageId: params.messageId,
614707
}
708+
case 'discord_bulk_delete_messages':
709+
return {
710+
...commonParams,
711+
channelId: params.channelId,
712+
messageIds: String(params.messageIds || '')
713+
.split(',')
714+
.map((id: string) => id.trim())
715+
.filter(Boolean),
716+
}
717+
case 'discord_get_pinned_messages':
718+
return {
719+
...commonParams,
720+
channelId: params.channelId,
721+
...(params.limit && { limit: Math.min(Math.max(1, Number(params.limit)), 50) }),
722+
...(params.before?.trim() && { before: params.before.trim() }),
723+
}
615724
case 'discord_add_reaction':
616725
case 'discord_remove_reaction':
617726
return {
618727
...commonParams,
619728
channelId: params.channelId,
620729
messageId: params.messageId,
621730
emoji: params.emoji,
622-
...(params.userId && { userId: params.userId }),
731+
...(params.userId?.trim() && { userId: params.userId.trim() }),
623732
}
624733
case 'discord_pin_message':
625734
case 'discord_unpin_message':
@@ -633,10 +742,13 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
633742
...commonParams,
634743
channelId: params.channelId,
635744
name: params.name,
636-
...(params.messageId && { messageId: params.messageId }),
745+
...(params.messageId?.trim() && { messageId: params.messageId.trim() }),
637746
...(params.autoArchiveDuration && {
638747
autoArchiveDuration: Number(params.autoArchiveDuration),
639748
}),
749+
...(params.threadVisibility !== undefined && {
750+
isPublic: params.threadVisibility !== 'private',
751+
}),
640752
}
641753
case 'discord_join_thread':
642754
case 'discord_leave_thread':
@@ -665,6 +777,8 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
665777
case 'discord_delete_channel':
666778
case 'discord_get_channel':
667779
return { ...commonParams, channelId: params.channelId }
780+
case 'discord_list_channels':
781+
return commonParams
668782
case 'discord_create_role':
669783
return {
670784
...commonParams,
@@ -695,6 +809,8 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
695809
userId: params.userId,
696810
roleId: params.roleId,
697811
}
812+
case 'discord_list_roles':
813+
return commonParams
698814
case 'discord_kick_member':
699815
case 'discord_unban_member':
700816
return {
@@ -708,7 +824,7 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
708824
userId: params.userId,
709825
...(params.reason && { reason: params.reason }),
710826
...(params.deleteMessageDays && {
711-
deleteMessageDays: Number(params.deleteMessageDays),
827+
deleteMessageSeconds: Number(params.deleteMessageDays) * 86400,
712828
}),
713829
}
714830
case 'discord_get_member':
@@ -761,6 +877,10 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
761877
serverId: { type: 'string', description: 'Discord server identifier' },
762878
channelId: { type: 'string', description: 'Discord channel identifier' },
763879
messageId: { type: 'string', description: 'Discord message identifier' },
880+
messageIds: {
881+
type: 'string',
882+
description: 'Comma-separated message IDs to bulk delete (2-100)',
883+
},
764884
threadId: { type: 'string', description: 'Discord thread identifier' },
765885
userId: { type: 'string', description: 'Discord user identifier' },
766886
roleId: { type: 'string', description: 'Discord role identifier' },
@@ -777,7 +897,12 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
777897
archived: { type: 'string', description: 'Archive status (true/false)' },
778898
files: { type: 'array', description: 'Files to attach (canonical param)' },
779899
limit: { type: 'number', description: 'Message limit' },
900+
before: { type: 'string', description: 'Return pins before this ISO8601 timestamp' },
780901
autoArchiveDuration: { type: 'number', description: 'Thread auto-archive duration in minutes' },
902+
threadVisibility: {
903+
type: 'string',
904+
description: 'Visibility for a new standalone thread (public/private)',
905+
},
781906
channelType: { type: 'number', description: 'Discord channel type (0=text, 2=voice, etc.)' },
782907
parentId: { type: 'string', description: 'Parent category ID for channel' },
783908
hoist: { type: 'boolean', description: 'Whether to display role members separately' },

apps/sim/lib/oauth/utils.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,6 @@ export const SCOPE_DESCRIPTIONS: Record<string, string> = {
242242
'GroupMember.ReadWrite.All': 'Read and write all group memberships',
243243
'Directory.Read.All': 'Read directory data',
244244

245-
// Discord scopes
246-
identify: 'Read Discord user',
247-
bot: 'Read Discord bot',
248-
'messages.read': 'Read Discord messages',
249-
guilds: 'Read Discord guilds',
250-
'guilds.members.read': 'Read Discord guild members',
251-
252245
// Reddit scopes
253246
identity: 'Access Reddit identity',
254247
submit: 'Submit posts and comments',

apps/sim/tools/discord/add_reaction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ export const discordAddReactionTool: ToolConfig<
4646
request: {
4747
url: (params: DiscordAddReactionParams) => {
4848
const encodedEmoji = encodeURIComponent(params.emoji)
49-
return `https://discord.com/api/v10/channels/${params.channelId}/messages/${params.messageId}/reactions/${encodedEmoji}/@me`
49+
return `https://discord.com/api/v10/channels/${params.channelId.trim()}/messages/${params.messageId.trim()}/reactions/${encodedEmoji}/@me`
5050
},
5151
method: 'PUT',
5252
headers: (params) => ({
53-
Authorization: `Bot ${params.botToken}`,
53+
Authorization: `Bot ${params.botToken.trim()}`,
5454
}),
5555
},
5656

apps/sim/tools/discord/archive_thread.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ export const discordArchiveThreadTool: ToolConfig<
4242

4343
request: {
4444
url: (params: DiscordArchiveThreadParams) => {
45-
return `https://discord.com/api/v10/channels/${params.threadId}`
45+
return `https://discord.com/api/v10/channels/${params.threadId.trim()}`
4646
},
4747
method: 'PATCH',
4848
headers: (params) => ({
4949
'Content-Type': 'application/json',
50-
Authorization: `Bot ${params.botToken}`,
50+
Authorization: `Bot ${params.botToken.trim()}`,
5151
}),
5252
body: (params: DiscordArchiveThreadParams) => {
5353
return {

apps/sim/tools/discord/assign_role.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export const discordAssignRoleTool: ToolConfig<DiscordAssignRoleParams, DiscordA
3737

3838
request: {
3939
url: (params: DiscordAssignRoleParams) => {
40-
return `https://discord.com/api/v10/guilds/${params.serverId}/members/${params.userId}/roles/${params.roleId}`
40+
return `https://discord.com/api/v10/guilds/${params.serverId.trim()}/members/${params.userId.trim()}/roles/${params.roleId.trim()}`
4141
},
4242
method: 'PUT',
4343
headers: (params) => ({
44-
Authorization: `Bot ${params.botToken}`,
44+
Authorization: `Bot ${params.botToken.trim()}`,
4545
}),
4646
},
4747

0 commit comments

Comments
 (0)