@zhin.js/adapter-discord 1.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # @zhin.js/adapter-discord
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [efdd58a]
8
+ - @zhin.js/types@1.0.1
9
+ - zhin.js@1.0.1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 凉菜
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,600 @@
1
+ # @zhin.js/adapter-discord
2
+
3
+ Zhin.js Discord 适配器,基于 `discord.js` v14 实现,支持 **Gateway** 和 **Interactions** 两种模式。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @zhin.js/adapter-discord
9
+ ```
10
+
11
+ ## 配置
12
+
13
+ ### Gateway 模式配置(推荐常规使用)
14
+
15
+ ```typescript
16
+ import { DiscordBotConfig } from '@zhin.js/adapter-discord';
17
+
18
+ const config: DiscordBotConfig = {
19
+ context: 'discord',
20
+ name: 'my-discord-bot',
21
+ token: 'YOUR_BOT_TOKEN', // 从 Discord Developer Portal 获取的 Bot Token
22
+ }
23
+ ```
24
+
25
+ ### Interactions 端点模式配置(推荐高性能场景)
26
+
27
+ ```typescript
28
+ import { DiscordInteractionsConfig } from '@zhin.js/adapter-discord';
29
+
30
+ const config: DiscordInteractionsConfig = {
31
+ context: 'discord-interactions',
32
+ name: 'my-discord-bot',
33
+ token: 'YOUR_BOT_TOKEN',
34
+ applicationId: 'YOUR_APPLICATION_ID', // Discord 应用 ID
35
+ publicKey: 'YOUR_PUBLIC_KEY', // Discord 应用的 Public Key
36
+ interactionsPath: '/discord/interactions', // 交互端点路径
37
+ useGateway: false // 是否同时使用 Gateway(可选)
38
+ }
39
+ ```
40
+
41
+ ### 通用配置参数
42
+
43
+ - `token` (必需): Discord Bot Token,从 [Discord Developer Portal](https://discord.com/developers/applications) 获取
44
+ - `name`: 机器人名称
45
+ - `intents`: Gateway Intents 配置(可选,有默认值)
46
+ - `enableSlashCommands`: 是否启用斜杠命令支持(默认: false)
47
+ - `globalCommands`: 是否注册全局命令(默认: false,全局命令更新较慢)
48
+ - `slashCommands`: Slash Commands 定义数组(使用 SlashCommandBuilder 创建)
49
+ - `defaultActivity`: 默认活动状态配置(可选)
50
+ - `name`: 活动名称
51
+ - `type`: 活动类型('PLAYING' | 'STREAMING' | 'LISTENING' | 'WATCHING' | 'COMPETING')
52
+ - `url`: 活动URL(流媒体类型需要)
53
+
54
+ ### 完整配置示例
55
+
56
+ #### Gateway 模式完整配置
57
+
58
+ ```typescript
59
+ import { GatewayIntentBits, SlashCommandBuilder } from 'discord.js';
60
+
61
+ const config: DiscordBotConfig = {
62
+ context: 'discord',
63
+ name: 'my-discord-bot',
64
+ token: 'YOUR_BOT_TOKEN',
65
+ intents: [
66
+ GatewayIntentBits.Guilds,
67
+ GatewayIntentBits.GuildMessages,
68
+ GatewayIntentBits.MessageContent,
69
+ GatewayIntentBits.DirectMessages,
70
+ GatewayIntentBits.GuildMembers,
71
+ GatewayIntentBits.GuildMessageReactions
72
+ ],
73
+ enableSlashCommands: true,
74
+ globalCommands: false,
75
+ defaultActivity: {
76
+ name: '正在为用户服务',
77
+ type: 'PLAYING'
78
+ },
79
+ slashCommands: [
80
+ new SlashCommandBuilder()
81
+ .setName('help')
82
+ .setDescription('显示帮助信息')
83
+ .toJSON()
84
+ ]
85
+ }
86
+ ```
87
+
88
+ #### Interactions 模式完整配置
89
+
90
+ ```typescript
91
+ import { SlashCommandBuilder } from 'discord.js';
92
+
93
+ const config: DiscordInteractionsConfig = {
94
+ context: 'discord-interactions',
95
+ name: 'interactions-bot',
96
+ token: 'YOUR_BOT_TOKEN',
97
+ applicationId: 'YOUR_APPLICATION_ID',
98
+ publicKey: 'YOUR_PUBLIC_KEY',
99
+ interactionsPath: '/discord/interactions',
100
+ useGateway: false, // 纯 Interactions 模式
101
+ slashCommands: [
102
+ new SlashCommandBuilder()
103
+ .setName('ping')
104
+ .setDescription('测试命令')
105
+ .toJSON(),
106
+ new SlashCommandBuilder()
107
+ .setName('weather')
108
+ .setDescription('获取天气信息')
109
+ .addStringOption(option =>
110
+ option.setName('city')
111
+ .setDescription('城市名称')
112
+ .setRequired(true)
113
+ )
114
+ .toJSON()
115
+ ],
116
+ globalCommands: true // Interactions 模式推荐使用全局命令
117
+ }
118
+ ```
119
+
120
+ ## 获取配置信息
121
+
122
+ ### 获取 Bot Token
123
+
124
+ 1. 访问 [Discord Developer Portal](https://discord.com/developers/applications)
125
+ 2. 创建新的应用程序(New Application)
126
+ 3. 在左侧菜单选择 "Bot"
127
+ 4. 点击 "Reset Token" 获取新的 Token
128
+ 5. 复制 Token(注意保密)
129
+ 6. 在 "Privileged Gateway Intents" 中启用需要的权限
130
+
131
+ ### 获取 Application ID 和 Public Key(Interactions 模式需要)
132
+
133
+ 1. 在 Discord Developer Portal 的应用详情页
134
+ 2. **Application ID**: 在 "General Information" 页面可以找到
135
+ 3. **Public Key**: 在 "General Information" 页面的 "Public Key" 字段
136
+
137
+ ## 使用示例
138
+
139
+ ### Gateway 模式使用
140
+
141
+ ```typescript
142
+ import { createApp } from 'zhin.js';
143
+ import '@zhin.js/adapter-discord';
144
+
145
+ const app = createApp();
146
+
147
+ app.adapter('discord', {
148
+ context: 'discord',
149
+ name: 'my-bot',
150
+ token: 'YOUR_BOT_TOKEN'
151
+ });
152
+
153
+ app.middleware((session, next) => {
154
+ console.log(`收到消息: ${session.content}`);
155
+ return next();
156
+ });
157
+
158
+ app.command('ping').action((session) => {
159
+ session.send('Pong! 🏓');
160
+ });
161
+
162
+ app.start();
163
+ ```
164
+
165
+ ### Interactions 端点模式使用
166
+
167
+ ```typescript
168
+ import { createApp } from 'zhin.js';
169
+ import '@zhin.js/adapter-discord';
170
+ import '@zhin.js/http'; // 需要 HTTP 插件支持
171
+
172
+ const app = createApp();
173
+
174
+ // 先加载 HTTP 插件
175
+ app.plugin(require('@zhin.js/http'));
176
+
177
+ // 配置 Discord Interactions
178
+ app.adapter('discord-interactions', {
179
+ context: 'discord-interactions',
180
+ name: 'interactions-bot',
181
+ token: 'YOUR_BOT_TOKEN',
182
+ applicationId: 'YOUR_APPLICATION_ID',
183
+ publicKey: 'YOUR_PUBLIC_KEY',
184
+ interactionsPath: '/discord/interactions'
185
+ });
186
+
187
+ // 处理 Slash Commands
188
+ app.command('ping').action((session) => {
189
+ session.send('Pong from Interactions! ⚡');
190
+ });
191
+
192
+ app.start();
193
+ ```
194
+
195
+ ### 高级功能使用
196
+
197
+ ```typescript
198
+ import { createApp } from 'zhin.js';
199
+ import '@zhin.js/adapter-discord';
200
+
201
+ const app = createApp();
202
+
203
+ app.adapter('discord', {
204
+ context: 'discord',
205
+ name: 'advanced-bot',
206
+ token: 'YOUR_BOT_TOKEN',
207
+ defaultActivity: {
208
+ name: '正在处理消息',
209
+ type: 'LISTENING'
210
+ }
211
+ });
212
+
213
+ // 处理提及消息
214
+ app.middleware((session, next) => {
215
+ const mentions = session.content.filter(seg => seg.type === 'at');
216
+ if (mentions.length > 0) {
217
+ console.log('收到提及:', mentions.map(seg => seg.data.name));
218
+ }
219
+ return next();
220
+ });
221
+
222
+ // 发送富媒体消息
223
+ app.command('embed').action(async (session) => {
224
+ await session.send([
225
+ {
226
+ type: 'embed',
227
+ data: {
228
+ title: '这是一个 Embed 消息',
229
+ description: '支持丰富的格式化内容',
230
+ color: 0x00ff00,
231
+ fields: [
232
+ { name: '字段1', value: '值1', inline: true },
233
+ { name: '字段2', value: '值2', inline: true }
234
+ ],
235
+ thumbnail: { url: 'https://example.com/thumbnail.png' },
236
+ footer: { text: '底部文字' },
237
+ timestamp: new Date().toISOString()
238
+ }
239
+ }
240
+ ]);
241
+ });
242
+
243
+ // 处理图片消息
244
+ app.middleware((session, next) => {
245
+ const imageSegments = session.content.filter(seg => seg.type === 'image');
246
+ if (imageSegments.length > 0) {
247
+ console.log('收到图片:', imageSegments.map(seg => seg.data.url));
248
+ }
249
+ return next();
250
+ });
251
+
252
+ app.start();
253
+ ```
254
+
255
+ ## 两种模式对比
256
+
257
+ | 特性 | Gateway 模式 | Interactions 端点模式 |
258
+ |------|-------------|---------------------|
259
+ | **连接方式** | WebSocket 长连接 | HTTP 端点接收 |
260
+ | **实时性** | 高(实时推送) | 高(实时推送) |
261
+ | **消息处理** | 全部消息类型 | 主要是 Slash Commands |
262
+ | **资源消耗** | 中等(保持连接) | 低(按需处理) |
263
+ | **网络要求** | 稳定网络连接 | 需要公网 HTTPS |
264
+ | **适用场景** | 全功能机器人 | 命令型机器人 |
265
+ | **响应速度** | 一般 | 极快 |
266
+ | **配置复杂度** | 简单 | 中等 |
267
+
268
+ ### 选择建议
269
+
270
+ - **全功能机器人**: 使用 `discord` (Gateway 模式)
271
+ - **命令型机器人**: 使用 `discord-interactions` (Interactions 模式)
272
+ - **高性能场景**: 优先考虑 Interactions 模式
273
+ - **开发阶段**: Gateway 模式更方便调试
274
+
275
+ ## 支持的消息类型
276
+
277
+ ### 接收消息 (Gateway 模式)
278
+ - **文本消息**: 支持 Discord 格式化语法(**粗体**、*斜体*、`代码`等)
279
+ - **用户提及**: 解析 @用户 提及,包含用户 ID 和显示名
280
+ - **频道提及**: 解析 #频道 提及,包含频道信息
281
+ - **角色提及**: 解析 @角色 提及,包含角色信息
282
+ - **自定义表情**: 解析服务器自定义表情
283
+ - **图片消息**: 支持图片附件和 URL
284
+ - **音频消息**: 支持音频文件附件
285
+ - **视频消息**: 支持视频文件附件
286
+ - **文件消息**: 支持任意格式的文件附件
287
+ - **Embed 消息**: 支持富文本嵌入消息
288
+ - **回复消息**: 支持消息回复和引用
289
+
290
+ ### 接收消息 (Interactions 模式)
291
+ - **Slash Commands**: 接收并解析 Discord 斜杠命令
292
+ - **命令参数**: 自动解析命令的各种参数类型
293
+ - **上下文信息**: 包含用户、频道、服务器等上下文信息
294
+
295
+ ### 发送消息 (两种模式)
296
+ - **文本消息**: 支持 Discord 格式化语法
297
+ - **用户提及**: 发送 @用户 提及
298
+ - **频道提及**: 发送 #频道 提及
299
+ - **角色提及**: 发送 @角色 提及
300
+ - **自定义表情**: 发送服务器自定义表情
301
+ - **图片消息**: 发送图片文件、URL或Buffer
302
+ - **音频消息**: 发送音频文件
303
+ - **视频消息**: 发送视频文件
304
+ - **文件消息**: 发送任意格式的文件
305
+ - **Embed 消息**: 发送富文本嵌入消息
306
+ - **回复消息**: 回复到指定消息
307
+
308
+ ## 聊天类型支持
309
+
310
+ - `private`: 私信(DM)
311
+ - `group`: 群组私信(Group DM)
312
+ - `channel`: 服务器频道(Guild Text Channel)
313
+
314
+ ## Discord 特色功能
315
+
316
+ ### Slash Commands
317
+
318
+ #### Gateway 模式中的 Slash Commands
319
+
320
+ ```typescript
321
+ import { SlashCommandBuilder } from 'discord.js';
322
+
323
+ // 定义 Slash Commands
324
+ const slashCommands = [
325
+ new SlashCommandBuilder()
326
+ .setName('weather')
327
+ .setDescription('获取天气信息')
328
+ .addStringOption(option =>
329
+ option.setName('city')
330
+ .setDescription('城市名称')
331
+ .setRequired(true)
332
+ )
333
+ .addBooleanOption(option =>
334
+ option.setName('detailed')
335
+ .setDescription('是否显示详细信息')
336
+ .setRequired(false)
337
+ )
338
+ .toJSON()
339
+ ];
340
+
341
+ // 配置 Bot
342
+ const bot = app.adapter('discord', {
343
+ // ... 其他配置
344
+ enableSlashCommands: true,
345
+ slashCommands
346
+ });
347
+
348
+ // 添加处理器
349
+ bot.addSlashCommandHandler('weather', async (interaction) => {
350
+ const city = interaction.options.getString('city');
351
+ const detailed = interaction.options.getBoolean('detailed') || false;
352
+
353
+ // 处理命令逻辑
354
+ await interaction.reply(`${city} 的天气信息...`);
355
+ });
356
+ ```
357
+
358
+ #### Interactions 模式中的 Slash Commands
359
+
360
+ ```typescript
361
+ // Interactions 模式自动处理 Slash Commands
362
+ app.command('weather <city:string> [detailed:boolean]').action((session) => {
363
+ const city = session.argv.city;
364
+ const detailed = session.argv.detailed || false;
365
+
366
+ session.send(`${city} 的天气信息...`);
367
+ });
368
+ ```
369
+
370
+ ### Embed 消息
371
+
372
+ Discord 的嵌入消息支持丰富的格式化内容:
373
+
374
+ ```typescript
375
+ app.command('rich').action(async (session) => {
376
+ await session.send([
377
+ {
378
+ type: 'embed',
379
+ data: {
380
+ title: '丰富的嵌入消息',
381
+ description: '这是一个包含多种元素的 Embed',
382
+ url: 'https://example.com',
383
+ color: 0x00ff00, // 绿色
384
+
385
+ // 作者信息
386
+ author: {
387
+ name: '作者名称',
388
+ icon_url: 'https://example.com/author.png',
389
+ url: 'https://example.com/author'
390
+ },
391
+
392
+ // 缩略图
393
+ thumbnail: {
394
+ url: 'https://example.com/thumb.png'
395
+ },
396
+
397
+ // 字段
398
+ fields: [
399
+ {
400
+ name: '字段1',
401
+ value: '这是第一个字段的内容',
402
+ inline: true
403
+ },
404
+ {
405
+ name: '字段2',
406
+ value: '这是第二个字段的内容',
407
+ inline: true
408
+ },
409
+ {
410
+ name: '完整宽度字段',
411
+ value: '这个字段占据完整宽度',
412
+ inline: false
413
+ }
414
+ ],
415
+
416
+ // 图片
417
+ image: {
418
+ url: 'https://example.com/image.png'
419
+ },
420
+
421
+ // 底部信息
422
+ footer: {
423
+ text: '底部文字',
424
+ icon_url: 'https://example.com/footer.png'
425
+ },
426
+
427
+ // 时间戳
428
+ timestamp: new Date().toISOString()
429
+ }
430
+ }
431
+ ]);
432
+ });
433
+ ```
434
+
435
+ ### 权限管理
436
+
437
+ Discord 机器人需要适当的权限才能正常工作:
438
+
439
+ ```typescript
440
+ // 常用权限示例
441
+ const requiredPermissions = [
442
+ 'VIEW_CHANNEL', // 查看频道
443
+ 'SEND_MESSAGES', // 发送消息
444
+ 'READ_MESSAGE_HISTORY', // 读取消息历史
445
+ 'USE_SLASH_COMMANDS', // 使用斜杠命令
446
+ 'EMBED_LINKS', // 嵌入链接
447
+ 'ATTACH_FILES', // 附加文件
448
+ 'ADD_REACTIONS', // 添加反应
449
+ 'MENTION_EVERYONE' // 提及所有人(谨慎使用)
450
+ ];
451
+ ```
452
+
453
+ ## 最佳实践
454
+
455
+ ### 1. Intent 选择
456
+
457
+ 只启用需要的 Intent 以提高性能和安全性:
458
+
459
+ ```typescript
460
+ import { GatewayIntentBits } from 'discord.js';
461
+
462
+ // 最小权限集合
463
+ const minimalIntents = [
464
+ GatewayIntentBits.Guilds,
465
+ GatewayIntentBits.GuildMessages
466
+ ];
467
+
468
+ // 需要读取消息内容时
469
+ const messageContentIntents = [
470
+ ...minimalIntents,
471
+ GatewayIntentBits.MessageContent
472
+ ];
473
+
474
+ // 需要成员信息时
475
+ const memberIntents = [
476
+ ...messageContentIntents,
477
+ GatewayIntentBits.GuildMembers
478
+ ];
479
+ ```
480
+
481
+ ### 2. 错误处理
482
+
483
+ ```typescript
484
+ app.adapter('discord', {
485
+ // ... 配置
486
+ }).on('error', (error) => {
487
+ console.error('Discord adapter error:', error);
488
+ });
489
+
490
+ // 优雅处理 API 限制
491
+ app.middleware(async (session, next) => {
492
+ try {
493
+ await next();
494
+ } catch (error) {
495
+ if (error.code === 50013) { // Missing Permissions
496
+ await session.send('抱歉,我没有执行此操作的权限。');
497
+ } else {
498
+ console.error('Command error:', error);
499
+ }
500
+ }
501
+ });
502
+ ```
503
+
504
+ ### 3. 性能优化
505
+
506
+ ```typescript
507
+ // 使用 Interactions 模式处理命令
508
+ app.adapter('discord-interactions', {
509
+ // 只注册需要的 Slash Commands
510
+ slashCommands: [
511
+ // 只包含实际使用的命令
512
+ ],
513
+ // 全局命令响应更快
514
+ globalCommands: true
515
+ });
516
+
517
+ // Gateway 模式优化
518
+ app.adapter('discord', {
519
+ // 只启用必要的 Intent
520
+ intents: [
521
+ GatewayIntentBits.Guilds,
522
+ GatewayIntentBits.GuildMessages
523
+ ]
524
+ });
525
+ ```
526
+
527
+ ## 故障排除
528
+
529
+ ### Gateway 模式问题
530
+
531
+ 1. **连接失败**
532
+ ```
533
+ Error: Cannot connect to gateway
534
+ ```
535
+ - 检查网络连接和防火墙设置
536
+ - 确认 Token 是否正确且有效
537
+ - 检查 Intent 权限是否足够
538
+
539
+ 2. **权限不足**
540
+ ```
541
+ DiscordAPIError: Missing Permissions
542
+ ```
543
+ - 在 Discord 服务器中检查机器人权限
544
+ - 确认机器人已被正确邀请到服务器
545
+ - 检查频道特定权限
546
+
547
+ ### Interactions 模式问题
548
+
549
+ 1. **端点验证失败**
550
+ ```
551
+ Invalid Discord signature
552
+ ```
553
+ - 确认 `publicKey` 配置正确
554
+ - 检查 Discord 应用设置中的 Public Key
555
+ - 确保使用 HTTPS 且证书有效
556
+
557
+ 2. **命令注册失败**
558
+ ```
559
+ Error registering slash commands
560
+ ```
561
+ - 检查 `applicationId` 是否正确
562
+ - 确认 Bot Token 权限足够
563
+ - 验证命令定义格式是否正确
564
+
565
+ 3. **端点无响应**
566
+ ```
567
+ Interaction endpoint not responding
568
+ ```
569
+ - 确保 `interactionsPath` 路径正确
570
+ - 检查 HTTP 插件是否已加载
571
+ - 确认防火墙和反向代理配置
572
+
573
+ ### 通用问题
574
+
575
+ 1. **Token 无效**
576
+ - 重新生成 Bot Token
577
+ - 确认复制时没有额外空格
578
+ - 检查 Token 格式是否完整
579
+
580
+ 2. **消息发送失败**
581
+ - 检查机器人是否在目标频道中
582
+ - 确认消息内容符合 Discord 限制
583
+ - 验证权限设置
584
+
585
+ ## 更新日志
586
+
587
+ ### v1.1.0
588
+ - ✨ 新增 Interactions 端点模式支持
589
+ - ✨ 使用 `useContext('router')` 集成 HTTP 服务
590
+ - 🔒 添加签名验证安全机制
591
+ - ⚡ 优化 Slash Commands 处理性能
592
+ - 📚 完善两种模式的文档说明
593
+
594
+ ### v1.0.0
595
+ - 🎉 初始版本
596
+ - 🌐 完整的 Gateway 模式支持
597
+ - 🎯 丰富的消息类型支持
598
+ - ⚡ Slash Commands 集成
599
+ - 📱 Embed 消息和多媒体支持
600
+ - 🔧 灵活的权限和活动配置