@onebots/adapter-discord 1.0.0

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.
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Discord Gateway WebSocket 客户端
3
+ * 用于 Node.js 环境
4
+ */
5
+ import { EventEmitter } from 'events';
6
+ import { DiscordREST } from './rest.js';
7
+ // Gateway Opcodes
8
+ export var GatewayOpcodes;
9
+ (function (GatewayOpcodes) {
10
+ GatewayOpcodes[GatewayOpcodes["Dispatch"] = 0] = "Dispatch";
11
+ GatewayOpcodes[GatewayOpcodes["Heartbeat"] = 1] = "Heartbeat";
12
+ GatewayOpcodes[GatewayOpcodes["Identify"] = 2] = "Identify";
13
+ GatewayOpcodes[GatewayOpcodes["PresenceUpdate"] = 3] = "PresenceUpdate";
14
+ GatewayOpcodes[GatewayOpcodes["VoiceStateUpdate"] = 4] = "VoiceStateUpdate";
15
+ GatewayOpcodes[GatewayOpcodes["Resume"] = 6] = "Resume";
16
+ GatewayOpcodes[GatewayOpcodes["Reconnect"] = 7] = "Reconnect";
17
+ GatewayOpcodes[GatewayOpcodes["RequestGuildMembers"] = 8] = "RequestGuildMembers";
18
+ GatewayOpcodes[GatewayOpcodes["InvalidSession"] = 9] = "InvalidSession";
19
+ GatewayOpcodes[GatewayOpcodes["Hello"] = 10] = "Hello";
20
+ GatewayOpcodes[GatewayOpcodes["HeartbeatAck"] = 11] = "HeartbeatAck";
21
+ })(GatewayOpcodes || (GatewayOpcodes = {}));
22
+ // Gateway Intents
23
+ export var GatewayIntents;
24
+ (function (GatewayIntents) {
25
+ GatewayIntents[GatewayIntents["Guilds"] = 1] = "Guilds";
26
+ GatewayIntents[GatewayIntents["GuildMembers"] = 2] = "GuildMembers";
27
+ GatewayIntents[GatewayIntents["GuildModeration"] = 4] = "GuildModeration";
28
+ GatewayIntents[GatewayIntents["GuildEmojisAndStickers"] = 8] = "GuildEmojisAndStickers";
29
+ GatewayIntents[GatewayIntents["GuildIntegrations"] = 16] = "GuildIntegrations";
30
+ GatewayIntents[GatewayIntents["GuildWebhooks"] = 32] = "GuildWebhooks";
31
+ GatewayIntents[GatewayIntents["GuildInvites"] = 64] = "GuildInvites";
32
+ GatewayIntents[GatewayIntents["GuildVoiceStates"] = 128] = "GuildVoiceStates";
33
+ GatewayIntents[GatewayIntents["GuildPresences"] = 256] = "GuildPresences";
34
+ GatewayIntents[GatewayIntents["GuildMessages"] = 512] = "GuildMessages";
35
+ GatewayIntents[GatewayIntents["GuildMessageReactions"] = 1024] = "GuildMessageReactions";
36
+ GatewayIntents[GatewayIntents["GuildMessageTyping"] = 2048] = "GuildMessageTyping";
37
+ GatewayIntents[GatewayIntents["DirectMessages"] = 4096] = "DirectMessages";
38
+ GatewayIntents[GatewayIntents["DirectMessageReactions"] = 8192] = "DirectMessageReactions";
39
+ GatewayIntents[GatewayIntents["DirectMessageTyping"] = 16384] = "DirectMessageTyping";
40
+ GatewayIntents[GatewayIntents["MessageContent"] = 32768] = "MessageContent";
41
+ GatewayIntents[GatewayIntents["GuildScheduledEvents"] = 65536] = "GuildScheduledEvents";
42
+ GatewayIntents[GatewayIntents["AutoModerationConfiguration"] = 1048576] = "AutoModerationConfiguration";
43
+ GatewayIntents[GatewayIntents["AutoModerationExecution"] = 2097152] = "AutoModerationExecution";
44
+ })(GatewayIntents || (GatewayIntents = {}));
45
+ export class DiscordGateway extends EventEmitter {
46
+ ws = null;
47
+ token;
48
+ intents;
49
+ proxyUrl;
50
+ heartbeatInterval = null;
51
+ sequence = null;
52
+ sessionId = null;
53
+ resumeGatewayUrl = null;
54
+ rest;
55
+ isReady = false;
56
+ constructor(options) {
57
+ super();
58
+ this.token = options.token;
59
+ this.intents = options.intents;
60
+ if (options.proxy?.url) {
61
+ const proxyUrl = new URL(options.proxy.url);
62
+ if (options.proxy.username)
63
+ proxyUrl.username = options.proxy.username;
64
+ if (options.proxy.password)
65
+ proxyUrl.password = options.proxy.password;
66
+ this.proxyUrl = proxyUrl.toString();
67
+ }
68
+ this.rest = new DiscordREST({ token: options.token, proxy: options.proxy });
69
+ }
70
+ /**
71
+ * 连接 Gateway
72
+ */
73
+ async connect() {
74
+ // 获取 Gateway URL
75
+ const { url } = await this.rest.getGatewayBot();
76
+ const gatewayUrl = `${url}?v=10&encoding=json`;
77
+ return this.connectToGateway(gatewayUrl);
78
+ }
79
+ /**
80
+ * 连接到指定 Gateway URL
81
+ */
82
+ async connectToGateway(url) {
83
+ // 动态导入 ws
84
+ const { WebSocket } = await import('ws');
85
+ // 如果有代理,使用 socks-proxy-agent (WebSocket 更稳定)
86
+ let wsOptions = {};
87
+ if (this.proxyUrl) {
88
+ try {
89
+ // 优先使用 SOCKS5 代理 (对 WebSocket 支持更好)
90
+ // 将 http:// 代理转换为 socks5:// (Clash 混合端口同时支持)
91
+ const socksUrl = this.proxyUrl.replace(/^https?:\/\//, 'socks5://');
92
+ // @ts-ignore - socks-proxy-agent 是可选依赖
93
+ const { SocksProxyAgent } = await import('socks-proxy-agent');
94
+ wsOptions.agent = new SocksProxyAgent(socksUrl);
95
+ console.log(`[Gateway] 使用 SOCKS5 代理: ${socksUrl}`);
96
+ }
97
+ catch {
98
+ // 回退到 https-proxy-agent
99
+ try {
100
+ // @ts-ignore - https-proxy-agent 是可选依赖
101
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
102
+ wsOptions.agent = new HttpsProxyAgent(this.proxyUrl);
103
+ console.log(`[Gateway] 使用 HTTP 代理: ${this.proxyUrl}`);
104
+ }
105
+ catch {
106
+ console.warn('[Gateway] 代理 agent 未安装,将直接连接');
107
+ }
108
+ }
109
+ }
110
+ return new Promise((resolve, reject) => {
111
+ this.ws = new WebSocket(url, wsOptions);
112
+ this.ws.on('open', () => {
113
+ console.log('[Gateway] WebSocket 已连接');
114
+ });
115
+ this.ws.on('message', (data) => {
116
+ this.handleMessage(JSON.parse(data.toString()));
117
+ });
118
+ this.ws.on('close', (code, reason) => {
119
+ console.log(`[Gateway] WebSocket 关闭: ${code} - ${reason.toString()}`);
120
+ this.cleanup();
121
+ this.emit('close', code, reason.toString());
122
+ // 自动重连
123
+ if (code !== 1000 && code !== 4004) {
124
+ setTimeout(() => this.reconnect(), 5000);
125
+ }
126
+ });
127
+ this.ws.on('error', (error) => {
128
+ console.error('[Gateway] WebSocket 错误:', error);
129
+ this.emit('error', error);
130
+ reject(error);
131
+ });
132
+ // 设置超时
133
+ const timeout = setTimeout(() => {
134
+ if (!this.isReady) {
135
+ reject(new Error('Gateway 连接超时'));
136
+ }
137
+ }, 30000);
138
+ this.once('ready', () => {
139
+ clearTimeout(timeout);
140
+ resolve();
141
+ });
142
+ });
143
+ }
144
+ /**
145
+ * 处理 Gateway 消息
146
+ */
147
+ handleMessage(payload) {
148
+ const { op, d, s, t } = payload;
149
+ // 更新序列号
150
+ if (s !== null) {
151
+ this.sequence = s;
152
+ }
153
+ switch (op) {
154
+ case GatewayOpcodes.Hello:
155
+ this.startHeartbeat(d.heartbeat_interval);
156
+ this.identify();
157
+ break;
158
+ case GatewayOpcodes.HeartbeatAck:
159
+ // 心跳确认
160
+ break;
161
+ case GatewayOpcodes.Heartbeat:
162
+ this.sendHeartbeat();
163
+ break;
164
+ case GatewayOpcodes.Dispatch:
165
+ this.handleDispatch(t, d);
166
+ break;
167
+ case GatewayOpcodes.Reconnect:
168
+ console.log('[Gateway] 收到重连请求');
169
+ this.reconnect();
170
+ break;
171
+ case GatewayOpcodes.InvalidSession:
172
+ console.log('[Gateway] 会话无效,重新识别');
173
+ if (d) {
174
+ // 可恢复,尝试 resume
175
+ setTimeout(() => this.resume(), 1000);
176
+ }
177
+ else {
178
+ // 不可恢复,重新 identify
179
+ this.sessionId = null;
180
+ setTimeout(() => this.identify(), 1000);
181
+ }
182
+ break;
183
+ }
184
+ }
185
+ /**
186
+ * 处理 Dispatch 事件
187
+ */
188
+ handleDispatch(eventName, data) {
189
+ switch (eventName) {
190
+ case 'READY':
191
+ this.sessionId = data.session_id;
192
+ this.resumeGatewayUrl = data.resume_gateway_url;
193
+ this.isReady = true;
194
+ this.emit('ready', data.user);
195
+ break;
196
+ case 'RESUMED':
197
+ console.log('[Gateway] 会话已恢复');
198
+ this.emit('resumed');
199
+ break;
200
+ case 'MESSAGE_CREATE':
201
+ this.emit('messageCreate', data);
202
+ break;
203
+ case 'MESSAGE_UPDATE':
204
+ this.emit('messageUpdate', data);
205
+ break;
206
+ case 'MESSAGE_DELETE':
207
+ this.emit('messageDelete', data);
208
+ break;
209
+ case 'GUILD_CREATE':
210
+ this.emit('guildCreate', data);
211
+ break;
212
+ case 'GUILD_DELETE':
213
+ this.emit('guildDelete', data);
214
+ break;
215
+ case 'GUILD_MEMBER_ADD':
216
+ this.emit('guildMemberAdd', data);
217
+ break;
218
+ case 'GUILD_MEMBER_REMOVE':
219
+ this.emit('guildMemberRemove', data);
220
+ break;
221
+ case 'INTERACTION_CREATE':
222
+ this.emit('interactionCreate', data);
223
+ break;
224
+ default:
225
+ // 发送通用事件
226
+ this.emit('dispatch', eventName, data);
227
+ }
228
+ }
229
+ /**
230
+ * 发送 Identify
231
+ */
232
+ identify() {
233
+ this.send({
234
+ op: GatewayOpcodes.Identify,
235
+ d: {
236
+ token: this.token,
237
+ intents: this.intents,
238
+ properties: {
239
+ os: typeof process !== 'undefined' ? process.platform : 'unknown',
240
+ browser: 'onebots-lite',
241
+ device: 'onebots-lite',
242
+ },
243
+ },
244
+ });
245
+ }
246
+ /**
247
+ * 发送 Resume
248
+ */
249
+ resume() {
250
+ if (!this.sessionId || !this.sequence) {
251
+ this.identify();
252
+ return;
253
+ }
254
+ this.send({
255
+ op: GatewayOpcodes.Resume,
256
+ d: {
257
+ token: this.token,
258
+ session_id: this.sessionId,
259
+ seq: this.sequence,
260
+ },
261
+ });
262
+ }
263
+ /**
264
+ * 开始心跳
265
+ */
266
+ startHeartbeat(interval) {
267
+ this.stopHeartbeat();
268
+ // 首次心跳添加随机抖动
269
+ const jitter = Math.random() * interval;
270
+ setTimeout(() => {
271
+ this.sendHeartbeat();
272
+ this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), interval);
273
+ }, jitter);
274
+ }
275
+ /**
276
+ * 停止心跳
277
+ */
278
+ stopHeartbeat() {
279
+ if (this.heartbeatInterval) {
280
+ clearInterval(this.heartbeatInterval);
281
+ this.heartbeatInterval = null;
282
+ }
283
+ }
284
+ /**
285
+ * 发送心跳
286
+ */
287
+ sendHeartbeat() {
288
+ this.send({
289
+ op: GatewayOpcodes.Heartbeat,
290
+ d: this.sequence,
291
+ });
292
+ }
293
+ /**
294
+ * 发送数据
295
+ */
296
+ send(data) {
297
+ if (this.ws && this.ws.readyState === 1) {
298
+ this.ws.send(JSON.stringify(data));
299
+ }
300
+ }
301
+ /**
302
+ * 重连
303
+ */
304
+ async reconnect() {
305
+ this.cleanup();
306
+ if (this.resumeGatewayUrl && this.sessionId) {
307
+ // 尝试恢复
308
+ try {
309
+ await this.connectToGateway(`${this.resumeGatewayUrl}?v=10&encoding=json`);
310
+ this.resume();
311
+ return;
312
+ }
313
+ catch {
314
+ // 恢复失败,重新连接
315
+ }
316
+ }
317
+ // 重新连接
318
+ await this.connect();
319
+ }
320
+ /**
321
+ * 清理资源
322
+ */
323
+ cleanup() {
324
+ this.stopHeartbeat();
325
+ this.isReady = false;
326
+ if (this.ws) {
327
+ this.ws.removeAllListeners();
328
+ if (this.ws.readyState === 1) {
329
+ this.ws.close();
330
+ }
331
+ this.ws = null;
332
+ }
333
+ }
334
+ /**
335
+ * 断开连接
336
+ */
337
+ disconnect() {
338
+ this.sessionId = null;
339
+ this.resumeGatewayUrl = null;
340
+ this.cleanup();
341
+ }
342
+ /**
343
+ * 获取 REST 客户端
344
+ */
345
+ getREST() {
346
+ return this.rest;
347
+ }
348
+ }
349
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Discord Lite - 轻量版 Discord 客户端
3
+ *
4
+ * 特性:
5
+ * - 使用原生 fetch,无外部依赖
6
+ * - 自动检测运行时环境
7
+ * - Node.js: 支持 Gateway WebSocket
8
+ * - Cloudflare Workers / Vercel: 支持 Interactions Webhook
9
+ *
10
+ * @example Node.js Gateway 模式
11
+ * ```ts
12
+ * import { DiscordLite, GatewayIntents } from './lite';
13
+ *
14
+ * const client = new DiscordLite({
15
+ * token: 'your-bot-token',
16
+ * intents: GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.MessageContent,
17
+ * mode: 'gateway',
18
+ * });
19
+ *
20
+ * client.on('messageCreate', (message) => {
21
+ * console.log(message.content);
22
+ * });
23
+ *
24
+ * await client.start();
25
+ * ```
26
+ *
27
+ * @example Cloudflare Workers Webhook 模式
28
+ * ```ts
29
+ * import { DiscordLite, InteractionsHandler } from './lite';
30
+ *
31
+ * const handler = new InteractionsHandler({
32
+ * publicKey: 'your-public-key',
33
+ * token: 'your-bot-token',
34
+ * applicationId: 'your-app-id',
35
+ * });
36
+ *
37
+ * handler.onCommand('hello', async (interaction) => {
38
+ * return InteractionsHandler.messageResponse('Hello, World!');
39
+ * });
40
+ *
41
+ * export default {
42
+ * fetch: (request) => handler.handleRequest(request),
43
+ * };
44
+ * ```
45
+ */
46
+ import { EventEmitter } from 'events';
47
+ import { DiscordREST } from './rest.js';
48
+ import { InteractionsHandler } from './interactions.js';
49
+ export { DiscordREST, type RESTOptions } from './rest.js';
50
+ export { DiscordGateway, GatewayIntents, GatewayOpcodes, type GatewayOptions } from './gateway.js';
51
+ export { InteractionsHandler, InteractionType, InteractionCallbackType, verifyInteractionSignature, type InteractionWebhookOptions } from './interactions.js';
52
+ export { DiscordLiteBot, type DiscordLiteBotConfig } from './bot.js';
53
+ export type { DiscordUser, DiscordMessage, DiscordGuild, DiscordChannel, DiscordMember, DiscordAttachment } from './bot.js';
54
+ /**
55
+ * 运行时类型
56
+ */
57
+ export type RuntimeType = 'node' | 'cloudflare' | 'vercel' | 'deno' | 'bun' | 'browser' | 'unknown';
58
+ /**
59
+ * 检测当前运行时环境
60
+ */
61
+ export declare function detectRuntime(): RuntimeType;
62
+ /**
63
+ * 检测是否支持 WebSocket Gateway
64
+ */
65
+ export declare function supportsGateway(): boolean;
66
+ /**
67
+ * Discord Lite 配置
68
+ */
69
+ export interface DiscordLiteOptions {
70
+ token: string;
71
+ intents?: number;
72
+ proxy?: {
73
+ url: string;
74
+ username?: string;
75
+ password?: string;
76
+ };
77
+ mode?: 'gateway' | 'interactions' | 'auto';
78
+ publicKey?: string;
79
+ applicationId?: string;
80
+ }
81
+ /**
82
+ * Discord Lite 统一客户端
83
+ */
84
+ export declare class DiscordLite extends EventEmitter {
85
+ private options;
86
+ private gateway;
87
+ private interactions;
88
+ private rest;
89
+ private runtime;
90
+ private mode;
91
+ private user;
92
+ constructor(options: DiscordLiteOptions);
93
+ /**
94
+ * 启动客户端(Gateway 模式)
95
+ */
96
+ start(): Promise<void>;
97
+ /**
98
+ * 停止客户端
99
+ */
100
+ stop(): void;
101
+ /**
102
+ * 初始化 Interactions 处理器
103
+ */
104
+ initInteractions(): InteractionsHandler;
105
+ /**
106
+ * 处理 HTTP 请求(Interactions 模式)
107
+ */
108
+ handleRequest(request: Request): Promise<Response>;
109
+ /**
110
+ * 获取 REST 客户端
111
+ */
112
+ getREST(): DiscordREST;
113
+ /**
114
+ * 获取当前用户
115
+ */
116
+ getUser(): any;
117
+ /**
118
+ * 获取当前运行时
119
+ */
120
+ getRuntime(): RuntimeType;
121
+ /**
122
+ * 获取当前模式
123
+ */
124
+ getMode(): 'gateway' | 'interactions';
125
+ /** 发送消息 */
126
+ sendMessage(channelId: string, content: string | {
127
+ content?: string;
128
+ embeds?: any[];
129
+ }): Promise<any>;
130
+ /** 编辑消息 */
131
+ editMessage(channelId: string, messageId: string, content: string | {
132
+ content?: string;
133
+ embeds?: any[];
134
+ }): Promise<any>;
135
+ /** 删除消息 */
136
+ deleteMessage(channelId: string, messageId: string): Promise<any>;
137
+ /** 获取消息 */
138
+ getMessage(channelId: string, messageId: string): Promise<any>;
139
+ /** 获取服务器 */
140
+ getGuild(guildId: string): Promise<any>;
141
+ /** 获取服务器成员 */
142
+ getGuildMember(guildId: string, userId: string): Promise<any>;
143
+ }
144
+ /**
145
+ * 创建 Discord Lite 客户端的便捷方法
146
+ */
147
+ export declare function createClient(options: DiscordLiteOptions): DiscordLite;
148
+ //# sourceMappingURL=index.d.ts.map