opencode-skills-collection 1.0.185 → 1.0.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +5 -1
- package/bundled-skills/3d-web-experience/SKILL.md +152 -37
- package/bundled-skills/agent-evaluation/SKILL.md +1088 -26
- package/bundled-skills/agent-memory-systems/SKILL.md +1037 -25
- package/bundled-skills/agent-tool-builder/SKILL.md +668 -16
- package/bundled-skills/ai-agents-architect/SKILL.md +271 -31
- package/bundled-skills/ai-product/SKILL.md +716 -26
- package/bundled-skills/ai-wrapper-product/SKILL.md +450 -44
- package/bundled-skills/algolia-search/SKILL.md +867 -15
- package/bundled-skills/autonomous-agents/SKILL.md +1033 -26
- package/bundled-skills/aws-serverless/SKILL.md +1046 -35
- package/bundled-skills/azure-functions/SKILL.md +1318 -19
- package/bundled-skills/browser-automation/SKILL.md +1065 -28
- package/bundled-skills/browser-extension-builder/SKILL.md +159 -32
- package/bundled-skills/bullmq-specialist/SKILL.md +347 -16
- package/bundled-skills/clerk-auth/SKILL.md +796 -15
- package/bundled-skills/computer-use-agents/SKILL.md +1870 -28
- package/bundled-skills/context-window-management/SKILL.md +271 -18
- package/bundled-skills/conversation-memory/SKILL.md +453 -24
- package/bundled-skills/crewai/SKILL.md +252 -46
- package/bundled-skills/discord-bot-architect/SKILL.md +1207 -34
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/email-systems/SKILL.md +646 -26
- package/bundled-skills/faf-expert/SKILL.md +221 -0
- package/bundled-skills/faf-wizard/SKILL.md +252 -0
- package/bundled-skills/file-uploads/SKILL.md +212 -11
- package/bundled-skills/firebase/SKILL.md +646 -16
- package/bundled-skills/gcp-cloud-run/SKILL.md +1117 -32
- package/bundled-skills/graphql/SKILL.md +1026 -27
- package/bundled-skills/hubspot-integration/SKILL.md +804 -19
- package/bundled-skills/idea-darwin/SKILL.md +120 -0
- package/bundled-skills/inngest/SKILL.md +431 -16
- package/bundled-skills/interactive-portfolio/SKILL.md +342 -44
- package/bundled-skills/langfuse/SKILL.md +296 -41
- package/bundled-skills/langgraph/SKILL.md +259 -50
- package/bundled-skills/micro-saas-launcher/SKILL.md +343 -44
- package/bundled-skills/neon-postgres/SKILL.md +572 -15
- package/bundled-skills/nextjs-supabase-auth/SKILL.md +269 -21
- package/bundled-skills/notion-template-business/SKILL.md +371 -44
- package/bundled-skills/personal-tool-builder/SKILL.md +537 -44
- package/bundled-skills/plaid-fintech/SKILL.md +825 -19
- package/bundled-skills/prompt-caching/SKILL.md +438 -25
- package/bundled-skills/rag-engineer/SKILL.md +271 -29
- package/bundled-skills/salesforce-development/SKILL.md +912 -19
- package/bundled-skills/satori/SKILL.md +54 -0
- package/bundled-skills/scroll-experience/SKILL.md +381 -44
- package/bundled-skills/segment-cdp/SKILL.md +817 -19
- package/bundled-skills/shopify-apps/SKILL.md +1475 -19
- package/bundled-skills/slack-bot-builder/SKILL.md +1162 -28
- package/bundled-skills/telegram-bot-builder/SKILL.md +152 -37
- package/bundled-skills/telegram-mini-app/SKILL.md +445 -44
- package/bundled-skills/trigger-dev/SKILL.md +916 -27
- package/bundled-skills/twilio-communications/SKILL.md +1310 -28
- package/bundled-skills/upstash-qstash/SKILL.md +898 -27
- package/bundled-skills/vercel-deployment/SKILL.md +637 -39
- package/bundled-skills/viral-generator-builder/SKILL.md +132 -37
- package/bundled-skills/voice-agents/SKILL.md +937 -27
- package/bundled-skills/voice-ai-development/SKILL.md +375 -46
- package/bundled-skills/workflow-automation/SKILL.md +982 -29
- package/bundled-skills/zapier-make-patterns/SKILL.md +772 -27
- package/package.json +1 -1
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: discord-bot-architect
|
|
3
|
-
description:
|
|
3
|
+
description: Specialized skill for building production-ready Discord bots.
|
|
4
|
+
Covers Discord.js (JavaScript) and Pycord (Python), gateway intents, slash
|
|
5
|
+
commands, interactive components, rate limiting, and sharding.
|
|
4
6
|
risk: unknown
|
|
5
|
-
source:
|
|
6
|
-
date_added:
|
|
7
|
+
source: vibeship-spawner-skills (Apache 2.0)
|
|
8
|
+
date_added: 2026-02-27
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
# Discord Bot Architect
|
|
10
12
|
|
|
13
|
+
Specialized skill for building production-ready Discord bots.
|
|
14
|
+
Covers Discord.js (JavaScript) and Pycord (Python), gateway intents,
|
|
15
|
+
slash commands, interactive components, rate limiting, and sharding.
|
|
16
|
+
|
|
17
|
+
## Principles
|
|
18
|
+
|
|
19
|
+
- Slash commands over message parsing (Message Content Intent deprecated)
|
|
20
|
+
- Acknowledge interactions within 3 seconds, always
|
|
21
|
+
- Request only required intents (minimize privileged intents)
|
|
22
|
+
- Handle rate limits gracefully with exponential backoff
|
|
23
|
+
- Plan for sharding from the start (required at 2500+ guilds)
|
|
24
|
+
- Use components (buttons, selects, modals) for rich UX
|
|
25
|
+
- Test with guild commands first, deploy global when ready
|
|
26
|
+
|
|
11
27
|
## Patterns
|
|
12
28
|
|
|
13
29
|
### Discord.js v14 Foundation
|
|
14
30
|
|
|
15
31
|
Modern Discord bot setup with Discord.js v14 and slash commands
|
|
16
32
|
|
|
17
|
-
**When to use**:
|
|
33
|
+
**When to use**: Building Discord bots with JavaScript/TypeScript,Need full gateway connection with events,Building bots with complex interactions
|
|
18
34
|
|
|
19
|
-
```javascript
|
|
20
35
|
```javascript
|
|
21
36
|
// src/index.js
|
|
22
37
|
const { Client, Collection, GatewayIntentBits, Events } = require('discord.js');
|
|
@@ -90,16 +105,96 @@ module.exports = {
|
|
|
90
105
|
const { Events } = require('discord.js');
|
|
91
106
|
|
|
92
107
|
module.exports = {
|
|
93
|
-
name:
|
|
108
|
+
name: Events.InteractionCreate,
|
|
109
|
+
async execute(interaction) {
|
|
110
|
+
if (!interaction.isChatInputCommand()) return;
|
|
111
|
+
|
|
112
|
+
const command = interaction.client.commands.get(interaction.commandName);
|
|
113
|
+
if (!command) {
|
|
114
|
+
console.error(`No command matching ${interaction.commandName}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await command.execute(interaction);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(error);
|
|
122
|
+
const reply = {
|
|
123
|
+
content: 'There was an error executing this command!',
|
|
124
|
+
ephemeral: true
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (interaction.replied || interaction.deferred) {
|
|
128
|
+
await interaction.followUp(reply);
|
|
129
|
+
} else {
|
|
130
|
+
await interaction.reply(reply);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
94
135
|
```
|
|
95
136
|
|
|
137
|
+
```javascript
|
|
138
|
+
// src/deploy-commands.js
|
|
139
|
+
const { REST, Routes } = require('discord.js');
|
|
140
|
+
const fs = require('node:fs');
|
|
141
|
+
const path = require('node:path');
|
|
142
|
+
require('dotenv').config();
|
|
143
|
+
|
|
144
|
+
const commands = [];
|
|
145
|
+
const commandsPath = path.join(__dirname, 'commands');
|
|
146
|
+
const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));
|
|
147
|
+
|
|
148
|
+
for (const file of commandFiles) {
|
|
149
|
+
const command = require(path.join(commandsPath, file));
|
|
150
|
+
commands.push(command.data.toJSON());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
|
|
154
|
+
|
|
155
|
+
(async () => {
|
|
156
|
+
try {
|
|
157
|
+
console.log(`Refreshing ${commands.length} commands...`);
|
|
158
|
+
|
|
159
|
+
// Guild commands (instant, for testing)
|
|
160
|
+
// const data = await rest.put(
|
|
161
|
+
// Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID),
|
|
162
|
+
// { body: commands }
|
|
163
|
+
// );
|
|
164
|
+
|
|
165
|
+
// Global commands (can take up to 1 hour to propagate)
|
|
166
|
+
const data = await rest.put(
|
|
167
|
+
Routes.applicationCommands(process.env.CLIENT_ID),
|
|
168
|
+
{ body: commands }
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
console.log(`Successfully registered ${data.length} commands`);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(error);
|
|
174
|
+
}
|
|
175
|
+
})();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Structure
|
|
179
|
+
|
|
180
|
+
discord-bot/
|
|
181
|
+
├── src/
|
|
182
|
+
│ ├── index.js # Main entry point
|
|
183
|
+
│ ├── deploy-commands.js # Command registration script
|
|
184
|
+
│ ├── commands/ # Slash command handlers
|
|
185
|
+
│ │ └── ping.js
|
|
186
|
+
│ └── events/ # Event handlers
|
|
187
|
+
│ ├── ready.js
|
|
188
|
+
│ └── interactionCreate.js
|
|
189
|
+
├── .env
|
|
190
|
+
└── package.json
|
|
191
|
+
|
|
96
192
|
### Pycord Bot Foundation
|
|
97
193
|
|
|
98
194
|
Discord bot with Pycord (Python) and application commands
|
|
99
195
|
|
|
100
|
-
**When to use**:
|
|
196
|
+
**When to use**: Building Discord bots with Python,Prefer async/await patterns,Need good slash command support
|
|
101
197
|
|
|
102
|
-
```python
|
|
103
198
|
```python
|
|
104
199
|
# main.py
|
|
105
200
|
import os
|
|
@@ -169,16 +264,32 @@ class General(commands.Cog):
|
|
|
169
264
|
embed.add_field(name="Latency", value=f"{round(self.bot.latency * 1000)}ms")
|
|
170
265
|
await ctx.respond(embed=embed)
|
|
171
266
|
|
|
172
|
-
@commands.Cog.
|
|
267
|
+
@commands.Cog.listener()
|
|
268
|
+
async def on_member_join(self, member: discord.Member):
|
|
269
|
+
# Requires Members intent (PRIVILEGED)
|
|
270
|
+
channel = member.guild.system_channel
|
|
271
|
+
if channel:
|
|
272
|
+
await channel.send(f"Welcome {member.mention}!")
|
|
273
|
+
|
|
274
|
+
def setup(bot):
|
|
275
|
+
bot.add_cog(General(bot))
|
|
173
276
|
```
|
|
174
277
|
|
|
278
|
+
### Structure
|
|
279
|
+
|
|
280
|
+
discord-bot/
|
|
281
|
+
├── main.py # Main bot file
|
|
282
|
+
├── cogs/ # Command groups
|
|
283
|
+
│ └── general.py
|
|
284
|
+
├── .env
|
|
285
|
+
└── requirements.txt
|
|
286
|
+
|
|
175
287
|
### Interactive Components Pattern
|
|
176
288
|
|
|
177
289
|
Using buttons, select menus, and modals for rich UX
|
|
178
290
|
|
|
179
|
-
**When to use**:
|
|
291
|
+
**When to use**: Need interactive user interfaces,Collecting user input beyond slash command options,Building menus, confirmations, or forms
|
|
180
292
|
|
|
181
|
-
```python
|
|
182
293
|
```javascript
|
|
183
294
|
// Discord.js - Buttons and Select Menus
|
|
184
295
|
const {
|
|
@@ -245,38 +356,1100 @@ module.exports = {
|
|
|
245
356
|
if (i.customId === 'confirm') {
|
|
246
357
|
await i.update({ content: 'Confirmed!', components: [] });
|
|
247
358
|
collector.stop();
|
|
248
|
-
} else if (i.
|
|
359
|
+
} else if (i.customId === 'cancel') {
|
|
360
|
+
await i.update({ content: 'Cancelled', components: [] });
|
|
361
|
+
collector.stop();
|
|
362
|
+
} else if (i.customId === 'select-role') {
|
|
363
|
+
await i.update({ content: `You selected: ${i.values.join(', ')}` });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// Modals (forms)
|
|
372
|
+
module.exports = {
|
|
373
|
+
data: new SlashCommandBuilder()
|
|
374
|
+
.setName('feedback')
|
|
375
|
+
.setDescription('Submit feedback'),
|
|
376
|
+
|
|
377
|
+
async execute(interaction) {
|
|
378
|
+
const modal = new ModalBuilder()
|
|
379
|
+
.setCustomId('feedback-modal')
|
|
380
|
+
.setTitle('Submit Feedback');
|
|
381
|
+
|
|
382
|
+
const titleInput = new TextInputBuilder()
|
|
383
|
+
.setCustomId('feedback-title')
|
|
384
|
+
.setLabel('Title')
|
|
385
|
+
.setStyle(TextInputStyle.Short)
|
|
386
|
+
.setRequired(true)
|
|
387
|
+
.setMaxLength(100);
|
|
388
|
+
|
|
389
|
+
const bodyInput = new TextInputBuilder()
|
|
390
|
+
.setCustomId('feedback-body')
|
|
391
|
+
.setLabel('Your feedback')
|
|
392
|
+
.setStyle(TextInputStyle.Paragraph)
|
|
393
|
+
.setRequired(true)
|
|
394
|
+
.setMaxLength(1000)
|
|
395
|
+
.setPlaceholder('Describe your feedback...');
|
|
396
|
+
|
|
397
|
+
modal.addComponents(
|
|
398
|
+
new ActionRowBuilder().addComponents(titleInput),
|
|
399
|
+
new ActionRowBuilder().addComponents(bodyInput)
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Show modal - MUST be first response
|
|
403
|
+
await interaction.showModal(modal);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Handle modal submission in interactionCreate
|
|
408
|
+
if (interaction.isModalSubmit()) {
|
|
409
|
+
if (interaction.customId === 'feedback-modal') {
|
|
410
|
+
const title = interaction.fields.getTextInputValue('feedback-title');
|
|
411
|
+
const body = interaction.fields.getTextInputValue('feedback-body');
|
|
412
|
+
|
|
413
|
+
await interaction.reply({
|
|
414
|
+
content: `Thanks for your feedback!\n**${title}**\n${body}`,
|
|
415
|
+
ephemeral: true
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
# Pycord - Buttons and Views
|
|
423
|
+
import discord
|
|
424
|
+
|
|
425
|
+
class ConfirmView(discord.ui.View):
|
|
426
|
+
def __init__(self):
|
|
427
|
+
super().__init__(timeout=60)
|
|
428
|
+
self.value = None
|
|
429
|
+
|
|
430
|
+
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.green)
|
|
431
|
+
async def confirm(self, button, interaction):
|
|
432
|
+
self.value = True
|
|
433
|
+
await interaction.response.edit_message(content="Confirmed!", view=None)
|
|
434
|
+
self.stop()
|
|
435
|
+
|
|
436
|
+
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.red)
|
|
437
|
+
async def cancel(self, button, interaction):
|
|
438
|
+
self.value = False
|
|
439
|
+
await interaction.response.edit_message(content="Cancelled", view=None)
|
|
440
|
+
self.stop()
|
|
441
|
+
|
|
442
|
+
@bot.slash_command(name="confirm")
|
|
443
|
+
async def confirm_cmd(ctx: discord.ApplicationContext):
|
|
444
|
+
view = ConfirmView()
|
|
445
|
+
await ctx.respond("Are you sure?", view=view)
|
|
446
|
+
|
|
447
|
+
await view.wait() # Wait for user interaction
|
|
448
|
+
if view.value is None:
|
|
449
|
+
await ctx.followup.send("Timed out")
|
|
450
|
+
|
|
451
|
+
# Select Menu
|
|
452
|
+
class RoleSelect(discord.ui.Select):
|
|
453
|
+
def __init__(self):
|
|
454
|
+
options = [
|
|
455
|
+
discord.SelectOption(label="Developer", value="dev", emoji="💻"),
|
|
456
|
+
discord.SelectOption(label="Designer", value="design", emoji="🎨"),
|
|
457
|
+
]
|
|
458
|
+
super().__init__(
|
|
459
|
+
placeholder="Select roles...",
|
|
460
|
+
min_values=1,
|
|
461
|
+
max_values=2,
|
|
462
|
+
options=options
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
async def callback(self, interaction):
|
|
466
|
+
await interaction.response.send_message(
|
|
467
|
+
f"You selected: {', '.join(self.values)}",
|
|
468
|
+
ephemeral=True
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
class RoleView(discord.ui.View):
|
|
472
|
+
def __init__(self):
|
|
473
|
+
super().__init__()
|
|
474
|
+
self.add_item(RoleSelect())
|
|
475
|
+
|
|
476
|
+
# Modal
|
|
477
|
+
class FeedbackModal(discord.ui.Modal):
|
|
478
|
+
def __init__(self):
|
|
479
|
+
super().__init__(title="Submit Feedback")
|
|
480
|
+
|
|
481
|
+
self.add_item(discord.ui.InputText(
|
|
482
|
+
label="Title",
|
|
483
|
+
style=discord.InputTextStyle.short,
|
|
484
|
+
required=True,
|
|
485
|
+
max_length=100
|
|
486
|
+
))
|
|
487
|
+
self.add_item(discord.ui.InputText(
|
|
488
|
+
label="Feedback",
|
|
489
|
+
style=discord.InputTextStyle.long,
|
|
490
|
+
required=True,
|
|
491
|
+
max_length=1000
|
|
492
|
+
))
|
|
493
|
+
|
|
494
|
+
async def callback(self, interaction):
|
|
495
|
+
title = self.children[0].value
|
|
496
|
+
body = self.children[1].value
|
|
497
|
+
await interaction.response.send_message(
|
|
498
|
+
f"Thanks!\n**{title}**\n{body}",
|
|
499
|
+
ephemeral=True
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
@bot.slash_command(name="feedback")
|
|
503
|
+
async def feedback(ctx: discord.ApplicationContext):
|
|
504
|
+
await ctx.send_modal(FeedbackModal())
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Limits
|
|
508
|
+
|
|
509
|
+
- 5 ActionRows per message/modal
|
|
510
|
+
- 5 buttons per ActionRow
|
|
511
|
+
- 1 select menu per ActionRow (takes all 5 slots)
|
|
512
|
+
- 5 select menus max per message
|
|
513
|
+
- 25 options per select menu
|
|
514
|
+
- Modal must be first response (cannot defer first)
|
|
515
|
+
|
|
516
|
+
### Deferred Response Pattern
|
|
517
|
+
|
|
518
|
+
Handle slow operations without timing out
|
|
519
|
+
|
|
520
|
+
**When to use**: Operation takes more than 3 seconds,Database queries, API calls, LLM responses,File processing or generation
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
// Discord.js - Deferred response
|
|
524
|
+
module.exports = {
|
|
525
|
+
data: new SlashCommandBuilder()
|
|
526
|
+
.setName('slow-task')
|
|
527
|
+
.setDescription('Performs a slow operation'),
|
|
528
|
+
|
|
529
|
+
async execute(interaction) {
|
|
530
|
+
// Defer immediately - you have 3 seconds!
|
|
531
|
+
await interaction.deferReply();
|
|
532
|
+
// For ephemeral: await interaction.deferReply({ ephemeral: true });
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
// Now you have 15 minutes to complete
|
|
536
|
+
const result = await slowDatabaseQuery();
|
|
537
|
+
const aiResponse = await callOpenAI(result);
|
|
538
|
+
|
|
539
|
+
// Edit the deferred reply
|
|
540
|
+
await interaction.editReply({
|
|
541
|
+
content: `Result: ${aiResponse}`,
|
|
542
|
+
embeds: [resultEmbed]
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
await interaction.editReply({
|
|
546
|
+
content: 'An error occurred while processing your request.'
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// For components (buttons, select menus)
|
|
553
|
+
collector.on('collect', async i => {
|
|
554
|
+
await i.deferUpdate(); // Acknowledge without visual change
|
|
555
|
+
// Or: await i.deferReply({ ephemeral: true });
|
|
556
|
+
|
|
557
|
+
const result = await slowOperation();
|
|
558
|
+
await i.editReply({ content: result });
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
# Pycord - Deferred response
|
|
564
|
+
@bot.slash_command(name="slow-task")
|
|
565
|
+
async def slow_task(ctx: discord.ApplicationContext):
|
|
566
|
+
# Defer immediately
|
|
567
|
+
await ctx.defer()
|
|
568
|
+
# For ephemeral: await ctx.defer(ephemeral=True)
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
result = await slow_database_query()
|
|
572
|
+
ai_response = await call_openai(result)
|
|
573
|
+
|
|
574
|
+
await ctx.followup.send(f"Result: {ai_response}")
|
|
575
|
+
except Exception as e:
|
|
576
|
+
await ctx.followup.send("An error occurred")
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Timing
|
|
580
|
+
|
|
581
|
+
- Initial_response: 3 seconds
|
|
582
|
+
- Deferred_followup: 15 minutes
|
|
583
|
+
- Ephemeral_note: Can only be set on initial response, not changed later
|
|
584
|
+
|
|
585
|
+
### Embed Builder Pattern
|
|
586
|
+
|
|
587
|
+
Rich embedded messages for professional-looking content
|
|
588
|
+
|
|
589
|
+
**When to use**: Displaying formatted information,Status updates, help menus, logs,Data with structure (fields, images)
|
|
590
|
+
|
|
591
|
+
```javascript
|
|
592
|
+
const { EmbedBuilder, Colors } = require('discord.js');
|
|
593
|
+
|
|
594
|
+
// Basic embed
|
|
595
|
+
const embed = new EmbedBuilder()
|
|
596
|
+
.setColor(Colors.Blue)
|
|
597
|
+
.setTitle('Bot Status')
|
|
598
|
+
.setURL('https://example.com')
|
|
599
|
+
.setAuthor({
|
|
600
|
+
name: 'Bot Name',
|
|
601
|
+
iconURL: client.user.displayAvatarURL()
|
|
602
|
+
})
|
|
603
|
+
.setDescription('Current status and statistics')
|
|
604
|
+
.addFields(
|
|
605
|
+
{ name: 'Servers', value: `${client.guilds.cache.size}`, inline: true },
|
|
606
|
+
{ name: 'Users', value: `${client.users.cache.size}`, inline: true },
|
|
607
|
+
{ name: 'Uptime', value: formatUptime(), inline: true }
|
|
608
|
+
)
|
|
609
|
+
.setThumbnail(client.user.displayAvatarURL())
|
|
610
|
+
.setImage('https://example.com/banner.png')
|
|
611
|
+
.setTimestamp()
|
|
612
|
+
.setFooter({
|
|
613
|
+
text: 'Requested by User',
|
|
614
|
+
iconURL: interaction.user.displayAvatarURL()
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
await interaction.reply({ embeds: [embed] });
|
|
618
|
+
|
|
619
|
+
// Multiple embeds (max 10)
|
|
620
|
+
await interaction.reply({ embeds: [embed1, embed2, embed3] });
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
```python
|
|
624
|
+
# Pycord
|
|
625
|
+
embed = discord.Embed(
|
|
626
|
+
title="Bot Status",
|
|
627
|
+
description="Current status and statistics",
|
|
628
|
+
color=discord.Color.blue(),
|
|
629
|
+
url="https://example.com"
|
|
630
|
+
)
|
|
631
|
+
embed.set_author(
|
|
632
|
+
name="Bot Name",
|
|
633
|
+
icon_url=bot.user.display_avatar.url
|
|
634
|
+
)
|
|
635
|
+
embed.add_field(name="Servers", value=len(bot.guilds), inline=True)
|
|
636
|
+
embed.add_field(name="Users", value=len(bot.users), inline=True)
|
|
637
|
+
embed.set_thumbnail(url=bot.user.display_avatar.url)
|
|
638
|
+
embed.set_image(url="https://example.com/banner.png")
|
|
639
|
+
embed.set_footer(text="Requested by User", icon_url=ctx.author.display_avatar.url)
|
|
640
|
+
embed.timestamp = discord.utils.utcnow()
|
|
641
|
+
|
|
642
|
+
await ctx.respond(embed=embed)
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Limits
|
|
646
|
+
|
|
647
|
+
- 10 embeds per message
|
|
648
|
+
- 6000 characters total across all embeds
|
|
649
|
+
- 256 characters for title
|
|
650
|
+
- 4096 characters for description
|
|
651
|
+
- 25 fields per embed
|
|
652
|
+
- 256 characters per field name
|
|
653
|
+
- 1024 characters per field value
|
|
654
|
+
|
|
655
|
+
### Rate Limit Handling Pattern
|
|
656
|
+
|
|
657
|
+
Gracefully handle Discord API rate limits
|
|
658
|
+
|
|
659
|
+
**When to use**: High-volume operations,Bulk messaging or role assignments,Any repeated API calls
|
|
660
|
+
|
|
661
|
+
```javascript
|
|
662
|
+
// Discord.js handles rate limits automatically, but for custom handling:
|
|
663
|
+
const { REST } = require('discord.js');
|
|
664
|
+
|
|
665
|
+
const rest = new REST({ version: '10' })
|
|
666
|
+
.setToken(process.env.DISCORD_TOKEN);
|
|
667
|
+
|
|
668
|
+
rest.on('rateLimited', (info) => {
|
|
669
|
+
console.log(`Rate limited! Retry after ${info.retryAfter}ms`);
|
|
670
|
+
console.log(`Route: ${info.route}`);
|
|
671
|
+
console.log(`Global: ${info.global}`);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Queue pattern for bulk operations
|
|
675
|
+
class RateLimitQueue {
|
|
676
|
+
constructor() {
|
|
677
|
+
this.queue = [];
|
|
678
|
+
this.processing = false;
|
|
679
|
+
this.requestsPerSecond = 40; // Safe margin below 50
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async add(operation) {
|
|
683
|
+
return new Promise((resolve, reject) => {
|
|
684
|
+
this.queue.push({ operation, resolve, reject });
|
|
685
|
+
this.process();
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async process() {
|
|
690
|
+
if (this.processing || this.queue.length === 0) return;
|
|
691
|
+
this.processing = true;
|
|
692
|
+
|
|
693
|
+
while (this.queue.length > 0) {
|
|
694
|
+
const { operation, resolve, reject } = this.queue.shift();
|
|
695
|
+
|
|
696
|
+
try {
|
|
697
|
+
const result = await operation();
|
|
698
|
+
resolve(result);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
reject(error);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Throttle: ~40 requests per second
|
|
704
|
+
await new Promise(r => setTimeout(r, 1000 / this.requestsPerSecond));
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
this.processing = false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const queue = new RateLimitQueue();
|
|
712
|
+
|
|
713
|
+
// Usage: Send 200 messages without hitting rate limits
|
|
714
|
+
for (const user of users) {
|
|
715
|
+
await queue.add(() => user.send('Welcome!'));
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
```python
|
|
720
|
+
# Pycord/discord.py handles rate limits automatically
|
|
721
|
+
# For custom handling:
|
|
722
|
+
import asyncio
|
|
723
|
+
from collections import deque
|
|
724
|
+
|
|
725
|
+
class RateLimitQueue:
|
|
726
|
+
def __init__(self, requests_per_second=40):
|
|
727
|
+
self.queue = deque()
|
|
728
|
+
self.processing = False
|
|
729
|
+
self.delay = 1 / requests_per_second
|
|
730
|
+
|
|
731
|
+
async def add(self, coro):
|
|
732
|
+
future = asyncio.Future()
|
|
733
|
+
self.queue.append((coro, future))
|
|
734
|
+
if not self.processing:
|
|
735
|
+
asyncio.create_task(self._process())
|
|
736
|
+
return await future
|
|
737
|
+
|
|
738
|
+
async def _process(self):
|
|
739
|
+
self.processing = True
|
|
740
|
+
while self.queue:
|
|
741
|
+
coro, future = self.queue.popleft()
|
|
742
|
+
try:
|
|
743
|
+
result = await coro
|
|
744
|
+
future.set_result(result)
|
|
745
|
+
except Exception as e:
|
|
746
|
+
future.set_exception(e)
|
|
747
|
+
await asyncio.sleep(self.delay)
|
|
748
|
+
self.processing = False
|
|
749
|
+
|
|
750
|
+
queue = RateLimitQueue()
|
|
751
|
+
|
|
752
|
+
# Usage
|
|
753
|
+
for member in guild.members:
|
|
754
|
+
await queue.add(member.send("Welcome!"))
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### Rate_limits
|
|
758
|
+
|
|
759
|
+
- Global: 50 requests per second
|
|
760
|
+
- Gateway: 120 requests per 60 seconds
|
|
761
|
+
- Specific: Messages to same channel: 5/5s, Bulk delete: 1/1s, Guild member requests: varies by guild size
|
|
762
|
+
|
|
763
|
+
### Sharding Pattern
|
|
764
|
+
|
|
765
|
+
Scale bots to 2500+ servers with sharding
|
|
766
|
+
|
|
767
|
+
**When to use**: Bot approaching 2500 guilds (required),Want horizontal scaling,Memory optimization for large bots
|
|
768
|
+
|
|
769
|
+
```javascript
|
|
770
|
+
// Discord.js Sharding Manager
|
|
771
|
+
// shard.js (main entry)
|
|
772
|
+
const { ShardingManager } = require('discord.js');
|
|
773
|
+
|
|
774
|
+
const manager = new ShardingManager('./bot.js', {
|
|
775
|
+
token: process.env.DISCORD_TOKEN,
|
|
776
|
+
totalShards: 'auto', // Discord determines optimal count
|
|
777
|
+
// Or specify: totalShards: 4
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
manager.on('shardCreate', shard => {
|
|
781
|
+
console.log(`Launched shard ${shard.id}`);
|
|
782
|
+
|
|
783
|
+
shard.on('ready', () => {
|
|
784
|
+
console.log(`Shard ${shard.id} ready`);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
shard.on('disconnect', () => {
|
|
788
|
+
console.log(`Shard ${shard.id} disconnected`);
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
manager.spawn();
|
|
793
|
+
|
|
794
|
+
// bot.js - Modified for sharding
|
|
795
|
+
const { Client } = require('discord.js');
|
|
796
|
+
|
|
797
|
+
const client = new Client({ intents: [...] });
|
|
798
|
+
|
|
799
|
+
// Get shard info
|
|
800
|
+
client.on('ready', () => {
|
|
801
|
+
console.log(`Shard ${client.shard.ids[0]} ready with ${client.guilds.cache.size} guilds`);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// Cross-shard data
|
|
805
|
+
async function getTotalGuilds() {
|
|
806
|
+
const results = await client.shard.fetchClientValues('guilds.cache.size');
|
|
807
|
+
return results.reduce((acc, count) => acc + count, 0);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Broadcast to all shards
|
|
811
|
+
async function broadcastMessage(channelId, message) {
|
|
812
|
+
await client.shard.broadcastEval(
|
|
813
|
+
(c, { channelId, message }) => {
|
|
814
|
+
const channel = c.channels.cache.get(channelId);
|
|
815
|
+
if (channel) channel.send(message);
|
|
816
|
+
},
|
|
817
|
+
{ context: { channelId, message } }
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
```python
|
|
823
|
+
# Pycord - AutoShardedBot
|
|
824
|
+
import discord
|
|
825
|
+
from discord.ext import commands
|
|
826
|
+
|
|
827
|
+
# Automatically handles sharding
|
|
828
|
+
bot = commands.AutoShardedBot(
|
|
829
|
+
command_prefix="!",
|
|
830
|
+
intents=discord.Intents.default(),
|
|
831
|
+
shard_count=None # Auto-determine
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
@bot.event
|
|
835
|
+
async def on_ready():
|
|
836
|
+
print(f"Logged in on {len(bot.shards)} shards")
|
|
837
|
+
for shard_id, shard in bot.shards.items():
|
|
838
|
+
print(f"Shard {shard_id}: {shard.latency * 1000:.2f}ms")
|
|
839
|
+
|
|
840
|
+
@bot.event
|
|
841
|
+
async def on_shard_ready(shard_id):
|
|
842
|
+
print(f"Shard {shard_id} is ready")
|
|
843
|
+
|
|
844
|
+
# Get guilds per shard
|
|
845
|
+
for shard_id, guilds in bot.guilds_by_shard().items():
|
|
846
|
+
print(f"Shard {shard_id}: {len(guilds)} guilds")
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### Scaling_guide
|
|
850
|
+
|
|
851
|
+
- 1-2500 guilds: No sharding required
|
|
852
|
+
- 2500+ guilds: Sharding required by Discord
|
|
853
|
+
- Recommended: ~1000 guilds per shard
|
|
854
|
+
- Memory: Each shard runs in separate process
|
|
855
|
+
|
|
856
|
+
## Sharp Edges
|
|
857
|
+
|
|
858
|
+
### Interaction Timeout (3 Second Rule)
|
|
859
|
+
|
|
860
|
+
Severity: CRITICAL
|
|
861
|
+
|
|
862
|
+
Situation: Handling slash commands, buttons, select menus, or modals
|
|
863
|
+
|
|
864
|
+
Symptoms:
|
|
865
|
+
User sees "This interaction failed" or "The application did not respond."
|
|
866
|
+
Command works locally but fails in production.
|
|
867
|
+
Slow operations never complete.
|
|
868
|
+
|
|
869
|
+
Why this breaks:
|
|
870
|
+
Discord requires ALL interactions to be acknowledged within 3 seconds:
|
|
871
|
+
- Slash commands
|
|
872
|
+
- Button clicks
|
|
873
|
+
- Select menu selections
|
|
874
|
+
- Context menu commands
|
|
875
|
+
|
|
876
|
+
If you do ANY slow operation (database, API, file I/O) before responding,
|
|
877
|
+
you'll miss the window. Discord shows an error even if your bot processes
|
|
878
|
+
the request correctly afterward.
|
|
879
|
+
|
|
880
|
+
After acknowledgment, you have 15 minutes for follow-up responses.
|
|
881
|
+
|
|
882
|
+
Recommended fix:
|
|
883
|
+
|
|
884
|
+
## Acknowledge immediately, process later
|
|
885
|
+
|
|
886
|
+
```javascript
|
|
887
|
+
// Discord.js - Defer for slow operations
|
|
888
|
+
module.exports = {
|
|
889
|
+
async execute(interaction) {
|
|
890
|
+
// DEFER IMMEDIATELY - before any slow operation
|
|
891
|
+
await interaction.deferReply();
|
|
892
|
+
// For ephemeral: await interaction.deferReply({ ephemeral: true });
|
|
893
|
+
|
|
894
|
+
// Now you have 15 minutes
|
|
895
|
+
const result = await slowDatabaseQuery();
|
|
896
|
+
const aiResponse = await callLLM(result);
|
|
897
|
+
|
|
898
|
+
// Edit the deferred reply
|
|
899
|
+
await interaction.editReply(`Result: ${aiResponse}`);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
```python
|
|
905
|
+
# Pycord
|
|
906
|
+
@bot.slash_command()
|
|
907
|
+
async def slow_command(ctx):
|
|
908
|
+
await ctx.defer() # Acknowledge immediately
|
|
909
|
+
# await ctx.defer(ephemeral=True) # For private response
|
|
910
|
+
|
|
911
|
+
result = await slow_operation()
|
|
912
|
+
await ctx.followup.send(f"Result: {result}")
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
## For components (buttons, menus)
|
|
916
|
+
|
|
917
|
+
```javascript
|
|
918
|
+
// If you're updating the message
|
|
919
|
+
await interaction.deferUpdate();
|
|
920
|
+
|
|
921
|
+
// If you're sending a new response
|
|
922
|
+
await interaction.deferReply({ ephemeral: true });
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### Missing Privileged Intent Configuration
|
|
926
|
+
|
|
927
|
+
Severity: CRITICAL
|
|
928
|
+
|
|
929
|
+
Situation: Bot needs member data, presences, or message content
|
|
930
|
+
|
|
931
|
+
Symptoms:
|
|
932
|
+
Members intent: member lists empty, on_member_join doesn't fire
|
|
933
|
+
Presences intent: statuses always unknown/offline
|
|
934
|
+
Message content intent: message.content is empty string
|
|
935
|
+
|
|
936
|
+
Why this breaks:
|
|
937
|
+
Discord has 3 privileged intents that require manual enablement:
|
|
938
|
+
1. **GUILD_MEMBERS** - Member join/leave, member lists
|
|
939
|
+
2. **GUILD_PRESENCES** - Online status, activities
|
|
940
|
+
3. **MESSAGE_CONTENT** - Read message text (deprecated for commands)
|
|
941
|
+
|
|
942
|
+
These must be:
|
|
943
|
+
1. Enabled in Discord Developer Portal > Bot > Privileged Gateway Intents
|
|
944
|
+
2. Requested in your bot code
|
|
945
|
+
|
|
946
|
+
At 100+ servers, you need Discord verification to keep using them.
|
|
947
|
+
|
|
948
|
+
Recommended fix:
|
|
949
|
+
|
|
950
|
+
## Step 1: Enable in Developer Portal
|
|
951
|
+
|
|
952
|
+
```
|
|
953
|
+
1. Go to https://discord.com/developers/applications
|
|
954
|
+
2. Select your application
|
|
955
|
+
3. Go to Bot section
|
|
956
|
+
4. Scroll to Privileged Gateway Intents
|
|
957
|
+
5. Toggle ON the intents you need
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
## Step 2: Request in code
|
|
961
|
+
|
|
962
|
+
```javascript
|
|
963
|
+
// Discord.js
|
|
964
|
+
const { Client, GatewayIntentBits } = require('discord.js');
|
|
965
|
+
|
|
966
|
+
const client = new Client({
|
|
967
|
+
intents: [
|
|
968
|
+
GatewayIntentBits.Guilds,
|
|
969
|
+
GatewayIntentBits.GuildMembers, // PRIVILEGED
|
|
970
|
+
// GatewayIntentBits.GuildPresences, // PRIVILEGED
|
|
971
|
+
// GatewayIntentBits.MessageContent, // PRIVILEGED - avoid!
|
|
972
|
+
]
|
|
973
|
+
});
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
```python
|
|
977
|
+
# Pycord
|
|
978
|
+
intents = discord.Intents.default()
|
|
979
|
+
intents.members = True # PRIVILEGED
|
|
980
|
+
# intents.presences = True # PRIVILEGED
|
|
981
|
+
# intents.message_content = True # PRIVILEGED - avoid!
|
|
982
|
+
|
|
983
|
+
bot = commands.Bot(intents=intents)
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
## Avoid Message Content Intent if possible
|
|
987
|
+
|
|
988
|
+
Use slash commands, buttons, and modals instead of message parsing.
|
|
989
|
+
These don't require the Message Content intent.
|
|
990
|
+
|
|
991
|
+
### Command Registration Rate Limited
|
|
992
|
+
|
|
993
|
+
Severity: HIGH
|
|
994
|
+
|
|
995
|
+
Situation: Registering slash commands
|
|
996
|
+
|
|
997
|
+
Symptoms:
|
|
998
|
+
Commands not appearing. 429 errors when deploying.
|
|
999
|
+
"You are being rate limited" messages.
|
|
1000
|
+
Commands appear for some guilds but not others.
|
|
1001
|
+
|
|
1002
|
+
Why this breaks:
|
|
1003
|
+
Command registration is rate limited:
|
|
1004
|
+
- Global commands: 200 creates/day, updates take up to 1 hour to propagate
|
|
1005
|
+
- Guild commands: 200 creates/day per guild, instant update
|
|
1006
|
+
|
|
1007
|
+
Common mistakes:
|
|
1008
|
+
- Registering commands on every bot startup
|
|
1009
|
+
- Registering in every guild separately
|
|
1010
|
+
- Making changes in a loop without delays
|
|
1011
|
+
|
|
1012
|
+
Recommended fix:
|
|
1013
|
+
|
|
1014
|
+
## Use a separate deploy script (not on startup)
|
|
1015
|
+
|
|
1016
|
+
```javascript
|
|
1017
|
+
// deploy-commands.js - Run manually, not on bot start
|
|
1018
|
+
const { REST, Routes } = require('discord.js');
|
|
1019
|
+
|
|
1020
|
+
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
|
|
1021
|
+
|
|
1022
|
+
async function deploy() {
|
|
1023
|
+
// For development: Guild commands (instant)
|
|
1024
|
+
if (process.env.GUILD_ID) {
|
|
1025
|
+
await rest.put(
|
|
1026
|
+
Routes.applicationGuildCommands(
|
|
1027
|
+
process.env.CLIENT_ID,
|
|
1028
|
+
process.env.GUILD_ID
|
|
1029
|
+
),
|
|
1030
|
+
{ body: commands }
|
|
1031
|
+
);
|
|
1032
|
+
console.log('Guild commands deployed instantly');
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// For production: Global commands (up to 1 hour)
|
|
1036
|
+
else {
|
|
1037
|
+
await rest.put(
|
|
1038
|
+
Routes.applicationCommands(process.env.CLIENT_ID),
|
|
1039
|
+
{ body: commands }
|
|
1040
|
+
);
|
|
1041
|
+
console.log('Global commands deployed (may take up to 1 hour)');
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
deploy();
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
```python
|
|
1049
|
+
# Pycord - Don't sync on every startup
|
|
1050
|
+
@bot.event
|
|
1051
|
+
async def on_ready():
|
|
1052
|
+
# DON'T DO THIS:
|
|
1053
|
+
# await bot.sync_commands()
|
|
1054
|
+
|
|
1055
|
+
print(f"Ready! Commands should already be registered.")
|
|
1056
|
+
|
|
1057
|
+
# Instead, sync manually or use a flag
|
|
1058
|
+
if __name__ == "__main__":
|
|
1059
|
+
if "--sync" in sys.argv:
|
|
1060
|
+
# Only sync when explicitly requested
|
|
1061
|
+
bot.sync_commands_on_start = True
|
|
1062
|
+
bot.run(token)
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
## Testing workflow
|
|
1066
|
+
|
|
1067
|
+
1. Use guild commands during development (instant updates)
|
|
1068
|
+
2. Only deploy global commands when ready for production
|
|
1069
|
+
3. Run deploy script manually, not on every restart
|
|
1070
|
+
|
|
1071
|
+
### Bot Token Exposed
|
|
1072
|
+
|
|
1073
|
+
Severity: CRITICAL
|
|
1074
|
+
|
|
1075
|
+
Situation: Storing or sharing bot token
|
|
1076
|
+
|
|
1077
|
+
Symptoms:
|
|
1078
|
+
Unauthorized actions from your bot.
|
|
1079
|
+
Bot joins random servers.
|
|
1080
|
+
Bot sends spam or malicious content.
|
|
1081
|
+
"Invalid token" after Discord invalidates it.
|
|
1082
|
+
|
|
1083
|
+
Why this breaks:
|
|
1084
|
+
Your bot token provides FULL control over your bot. Attackers can:
|
|
1085
|
+
- Send messages as your bot
|
|
1086
|
+
- Join servers, create invites
|
|
1087
|
+
- Access all data your bot can access
|
|
1088
|
+
- Potentially take over servers where bot has admin
|
|
1089
|
+
|
|
1090
|
+
Discord actively scans GitHub for exposed tokens and invalidates them.
|
|
1091
|
+
Common exposure points:
|
|
1092
|
+
- Committed to Git
|
|
1093
|
+
- Shared in Discord itself
|
|
1094
|
+
- In client-side code
|
|
1095
|
+
- In public screenshots
|
|
1096
|
+
|
|
1097
|
+
Recommended fix:
|
|
1098
|
+
|
|
1099
|
+
## Never hardcode tokens
|
|
1100
|
+
|
|
1101
|
+
```javascript
|
|
1102
|
+
// BAD - never do this
|
|
1103
|
+
const token = 'MTIzNDU2Nzg5MDEyMzQ1Njc4.ABCDEF.xyz...';
|
|
1104
|
+
|
|
1105
|
+
// GOOD - environment variables
|
|
1106
|
+
require('dotenv').config();
|
|
1107
|
+
client.login(process.env.DISCORD_TOKEN);
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
## Use .gitignore
|
|
1111
|
+
|
|
1112
|
+
```
|
|
1113
|
+
# .gitignore
|
|
1114
|
+
.env
|
|
1115
|
+
.env.local
|
|
1116
|
+
config.json
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
## If token is exposed
|
|
1120
|
+
|
|
1121
|
+
1. Go to Developer Portal immediately
|
|
1122
|
+
2. Regenerate the token
|
|
1123
|
+
3. Update all deployments
|
|
1124
|
+
4. Review bot activity for unauthorized actions
|
|
1125
|
+
5. Check git history and force push to remove if needed
|
|
1126
|
+
|
|
1127
|
+
## Use environment variables properly
|
|
1128
|
+
|
|
1129
|
+
```bash
|
|
1130
|
+
# .env (never commit)
|
|
1131
|
+
DISCORD_TOKEN=your_token_here
|
|
1132
|
+
CLIENT_ID=your_client_id
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
```javascript
|
|
1136
|
+
// Load with dotenv
|
|
1137
|
+
require('dotenv').config();
|
|
1138
|
+
const token = process.env.DISCORD_TOKEN;
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### Bot Missing applications.commands Scope
|
|
1142
|
+
|
|
1143
|
+
Severity: HIGH
|
|
1144
|
+
|
|
1145
|
+
Situation: Slash commands not appearing for users
|
|
1146
|
+
|
|
1147
|
+
Symptoms:
|
|
1148
|
+
Bot is in server but slash commands don't show up.
|
|
1149
|
+
Typing / shows no commands from your bot.
|
|
1150
|
+
Commands worked in development server but not others.
|
|
1151
|
+
|
|
1152
|
+
Why this breaks:
|
|
1153
|
+
Discord has two important OAuth scopes:
|
|
1154
|
+
- `bot` - Traditional bot permissions (messages, reactions, etc.)
|
|
1155
|
+
- `applications.commands` - Slash command permissions
|
|
1156
|
+
|
|
1157
|
+
Many bots were invited with only the `bot` scope before slash commands
|
|
1158
|
+
existed. They need to be re-invited with both scopes.
|
|
1159
|
+
|
|
1160
|
+
Recommended fix:
|
|
1161
|
+
|
|
1162
|
+
## Generate correct invite URL
|
|
1163
|
+
|
|
1164
|
+
```
|
|
1165
|
+
https://discord.com/api/oauth2/authorize
|
|
1166
|
+
?client_id=YOUR_CLIENT_ID
|
|
1167
|
+
&permissions=0
|
|
1168
|
+
&scope=bot%20applications.commands
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
## In Discord Developer Portal
|
|
1172
|
+
|
|
1173
|
+
1. Go to OAuth2 > URL Generator
|
|
1174
|
+
2. Select BOTH:
|
|
1175
|
+
- `bot`
|
|
1176
|
+
- `applications.commands`
|
|
1177
|
+
3. Select required bot permissions
|
|
1178
|
+
4. Use generated URL
|
|
1179
|
+
|
|
1180
|
+
## Re-invite without kicking
|
|
1181
|
+
|
|
1182
|
+
Users can use the new invite URL even if bot is already in server.
|
|
1183
|
+
This adds the new scope without removing the bot.
|
|
1184
|
+
|
|
1185
|
+
```javascript
|
|
1186
|
+
// Generate invite URL in code
|
|
1187
|
+
const inviteUrl = client.generateInvite({
|
|
1188
|
+
scopes: ['bot', 'applications.commands'],
|
|
1189
|
+
permissions: [
|
|
1190
|
+
'SendMessages',
|
|
1191
|
+
'EmbedLinks',
|
|
1192
|
+
// Add other needed permissions
|
|
1193
|
+
]
|
|
1194
|
+
});
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
### Global Commands Not Appearing Immediately
|
|
1198
|
+
|
|
1199
|
+
Severity: MEDIUM
|
|
1200
|
+
|
|
1201
|
+
Situation: Deploying global slash commands
|
|
1202
|
+
|
|
1203
|
+
Symptoms:
|
|
1204
|
+
Commands don't appear after deployment.
|
|
1205
|
+
Guild commands work but global commands don't.
|
|
1206
|
+
Commands appear after an hour.
|
|
1207
|
+
|
|
1208
|
+
Why this breaks:
|
|
1209
|
+
Global commands can take up to 1 hour to propagate to all Discord servers.
|
|
1210
|
+
This is by design for Discord's caching and CDN.
|
|
1211
|
+
|
|
1212
|
+
Guild commands are instant but only work in that specific guild.
|
|
1213
|
+
|
|
1214
|
+
Recommended fix:
|
|
1215
|
+
|
|
1216
|
+
## Development: Use guild commands
|
|
1217
|
+
|
|
1218
|
+
```javascript
|
|
1219
|
+
// Instant updates for testing
|
|
1220
|
+
await rest.put(
|
|
1221
|
+
Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID),
|
|
1222
|
+
{ body: commands }
|
|
1223
|
+
);
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
## Production: Deploy global commands during off-peak
|
|
1227
|
+
|
|
1228
|
+
```javascript
|
|
1229
|
+
// Takes up to 1 hour to propagate
|
|
1230
|
+
await rest.put(
|
|
1231
|
+
Routes.applicationCommands(CLIENT_ID),
|
|
1232
|
+
{ body: commands }
|
|
1233
|
+
);
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
## Workflow
|
|
1237
|
+
|
|
1238
|
+
1. Develop and test with guild commands (instant)
|
|
1239
|
+
2. When ready, deploy global commands
|
|
1240
|
+
3. Wait up to 1 hour for propagation
|
|
1241
|
+
4. Don't deploy global commands frequently
|
|
1242
|
+
|
|
1243
|
+
### Frequent Gateway Disconnections
|
|
1244
|
+
|
|
1245
|
+
Severity: MEDIUM
|
|
1246
|
+
|
|
1247
|
+
Situation: Bot randomly goes offline or misses events
|
|
1248
|
+
|
|
1249
|
+
Symptoms:
|
|
1250
|
+
Bot shows as offline intermittently.
|
|
1251
|
+
Events are missed (member joins, messages).
|
|
1252
|
+
Reconnection messages in logs.
|
|
1253
|
+
|
|
1254
|
+
Why this breaks:
|
|
1255
|
+
Discord gateway requires regular heartbeats. Issues:
|
|
1256
|
+
- Blocking operations prevent heartbeat
|
|
1257
|
+
- Network instability
|
|
1258
|
+
- Memory pressure causing GC pauses
|
|
1259
|
+
- Too many guilds without sharding (2500+ requires sharding)
|
|
1260
|
+
|
|
1261
|
+
Recommended fix:
|
|
1262
|
+
|
|
1263
|
+
## Never block the event loop
|
|
1264
|
+
|
|
1265
|
+
```javascript
|
|
1266
|
+
// BAD - blocks event loop
|
|
1267
|
+
const data = fs.readFileSync('file.json');
|
|
1268
|
+
|
|
1269
|
+
// GOOD - async
|
|
1270
|
+
const data = await fs.promises.readFile('file.json');
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
## Handle reconnections gracefully
|
|
1274
|
+
|
|
1275
|
+
```javascript
|
|
1276
|
+
client.on('shardResume', (id, replayedEvents) => {
|
|
1277
|
+
console.log(`Shard ${id} resumed, replayed ${replayedEvents} events`);
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
client.on('shardDisconnect', (event, id) => {
|
|
1281
|
+
console.log(`Shard ${id} disconnected`);
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
client.on('shardReconnecting', (id) => {
|
|
1285
|
+
console.log(`Shard ${id} reconnecting...`);
|
|
1286
|
+
});
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
## Implement sharding at scale
|
|
1290
|
+
|
|
1291
|
+
```javascript
|
|
1292
|
+
// Required at 2500+ guilds
|
|
1293
|
+
const manager = new ShardingManager('./bot.js', {
|
|
1294
|
+
token: process.env.DISCORD_TOKEN,
|
|
1295
|
+
totalShards: 'auto'
|
|
1296
|
+
});
|
|
1297
|
+
manager.spawn();
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
### Modal Must Be First Response
|
|
1301
|
+
|
|
1302
|
+
Severity: MEDIUM
|
|
1303
|
+
|
|
1304
|
+
Situation: Showing a modal from a slash command or button
|
|
1305
|
+
|
|
1306
|
+
Symptoms:
|
|
1307
|
+
"Interaction has already been acknowledged" error.
|
|
1308
|
+
Modal doesn't appear.
|
|
1309
|
+
Works sometimes but not others.
|
|
1310
|
+
|
|
1311
|
+
Why this breaks:
|
|
1312
|
+
Modals have a special requirement: showing a modal MUST be the first
|
|
1313
|
+
response to an interaction. You cannot:
|
|
1314
|
+
- defer() then showModal()
|
|
1315
|
+
- reply() then showModal()
|
|
1316
|
+
- Think for more than 3 seconds then showModal()
|
|
1317
|
+
|
|
1318
|
+
Recommended fix:
|
|
1319
|
+
|
|
1320
|
+
## Show modal immediately
|
|
1321
|
+
|
|
1322
|
+
```javascript
|
|
1323
|
+
// CORRECT - modal is first response
|
|
1324
|
+
async execute(interaction) {
|
|
1325
|
+
const modal = new ModalBuilder()
|
|
1326
|
+
.setCustomId('my-modal')
|
|
1327
|
+
.setTitle('Input Form');
|
|
1328
|
+
|
|
1329
|
+
// Show immediately - no defer, no reply first
|
|
1330
|
+
await interaction.showModal(modal);
|
|
1331
|
+
}
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
```javascript
|
|
1335
|
+
// WRONG - deferred first
|
|
1336
|
+
async execute(interaction) {
|
|
1337
|
+
await interaction.deferReply(); // CAN'T DO THIS
|
|
1338
|
+
await interaction.showModal(modal); // Will fail
|
|
1339
|
+
}
|
|
249
1340
|
```
|
|
250
1341
|
|
|
251
|
-
##
|
|
1342
|
+
## If you need to check something first
|
|
1343
|
+
|
|
1344
|
+
```javascript
|
|
1345
|
+
async execute(interaction) {
|
|
1346
|
+
// Quick sync check is OK (under 3 seconds)
|
|
1347
|
+
if (!hasPermission(interaction.user.id)) {
|
|
1348
|
+
return interaction.reply({
|
|
1349
|
+
content: 'No permission',
|
|
1350
|
+
ephemeral: true
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Show modal (still first interaction response for this path)
|
|
1355
|
+
await interaction.showModal(modal);
|
|
1356
|
+
}
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
## Validation Checks
|
|
1360
|
+
|
|
1361
|
+
### Hardcoded Discord Token
|
|
1362
|
+
|
|
1363
|
+
Severity: ERROR
|
|
1364
|
+
|
|
1365
|
+
Discord tokens must never be hardcoded
|
|
1366
|
+
|
|
1367
|
+
Message: Hardcoded Discord token detected. Use environment variables.
|
|
1368
|
+
|
|
1369
|
+
### Token Variable Assignment
|
|
1370
|
+
|
|
1371
|
+
Severity: ERROR
|
|
1372
|
+
|
|
1373
|
+
Tokens should come from environment, not strings
|
|
1374
|
+
|
|
1375
|
+
Message: Token assigned from string literal. Use environment variable.
|
|
1376
|
+
|
|
1377
|
+
### Token in Client-Side Code
|
|
1378
|
+
|
|
1379
|
+
Severity: ERROR
|
|
252
1380
|
|
|
253
|
-
|
|
1381
|
+
Never expose Discord tokens to browsers
|
|
254
1382
|
|
|
255
|
-
|
|
256
|
-
Slash commands are the intended approach.
|
|
1383
|
+
Message: Discord credentials exposed client-side. Only use server-side.
|
|
257
1384
|
|
|
258
|
-
###
|
|
1385
|
+
### Slow Operation Without Defer
|
|
259
1386
|
|
|
260
|
-
|
|
261
|
-
to propagate. Syncing on every start wastes API calls and can hit limits.
|
|
1387
|
+
Severity: WARNING
|
|
262
1388
|
|
|
263
|
-
|
|
1389
|
+
Slow operations should be deferred to avoid timeout
|
|
264
1390
|
|
|
265
|
-
|
|
266
|
-
cause missed heartbeats and disconnections.
|
|
1391
|
+
Message: Slow operation without defer. Interaction may timeout.
|
|
267
1392
|
|
|
268
|
-
|
|
1393
|
+
### Interaction Without Error Handling
|
|
269
1394
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1395
|
+
Severity: WARNING
|
|
1396
|
+
|
|
1397
|
+
Interactions should have try/catch for graceful errors
|
|
1398
|
+
|
|
1399
|
+
Message: Interaction without error handling. Add try/catch.
|
|
1400
|
+
|
|
1401
|
+
### Using Message Content Intent
|
|
1402
|
+
|
|
1403
|
+
Severity: WARNING
|
|
1404
|
+
|
|
1405
|
+
Message Content is privileged, prefer slash commands
|
|
1406
|
+
|
|
1407
|
+
Message: Using Message Content intent. Consider slash commands instead.
|
|
1408
|
+
|
|
1409
|
+
### Requesting All Intents
|
|
1410
|
+
|
|
1411
|
+
Severity: WARNING
|
|
1412
|
+
|
|
1413
|
+
Only request intents you actually need
|
|
1414
|
+
|
|
1415
|
+
Message: Requesting all intents. Only enable what you need.
|
|
1416
|
+
|
|
1417
|
+
### Syncing Commands on Ready Event
|
|
1418
|
+
|
|
1419
|
+
Severity: WARNING
|
|
1420
|
+
|
|
1421
|
+
Don't sync commands on every bot startup
|
|
1422
|
+
|
|
1423
|
+
Message: Syncing commands on startup. Use separate deploy script.
|
|
1424
|
+
|
|
1425
|
+
### Registering Commands in Loop
|
|
1426
|
+
|
|
1427
|
+
Severity: WARNING
|
|
1428
|
+
|
|
1429
|
+
Use bulk registration, not individual calls
|
|
1430
|
+
|
|
1431
|
+
Message: Registering commands in loop. Use bulk registration.
|
|
1432
|
+
|
|
1433
|
+
### No Rate Limit Handling
|
|
1434
|
+
|
|
1435
|
+
Severity: INFO
|
|
1436
|
+
|
|
1437
|
+
Consider handling rate limits for bulk operations
|
|
1438
|
+
|
|
1439
|
+
Message: Bulk operation without rate limit handling.
|
|
1440
|
+
|
|
1441
|
+
## Collaboration
|
|
1442
|
+
|
|
1443
|
+
### Delegation Triggers
|
|
1444
|
+
|
|
1445
|
+
- user needs AI-powered Discord bot -> llm-architect (Integrate LLM for conversational Discord bot)
|
|
1446
|
+
- user needs Slack integration too -> slack-bot-builder (Cross-platform bot architecture)
|
|
1447
|
+
- user needs voice features -> voice-agents (Discord voice channel integration)
|
|
1448
|
+
- user needs database for bot data -> postgres-wizard (Store user data, server configs, moderation logs)
|
|
1449
|
+
- user needs workflow automation -> workflow-automation (Discord events trigger workflows)
|
|
1450
|
+
- user needs high availability -> devops (Sharding, scaling, monitoring for large bots)
|
|
1451
|
+
- user needs payment integration -> stripe-specialist (Premium bot features, subscription management)
|
|
280
1452
|
|
|
281
1453
|
## When to Use
|
|
282
|
-
|
|
1454
|
+
|
|
1455
|
+
Use this skill when the request clearly matches the capabilities and patterns described above.
|