@zhin.js/adapter-qq 1.0.84 → 1.1.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.
- package/CHANGELOG.md +844 -17
- package/README.md +136 -233
- package/adapters/qq.js +50 -0
- package/adapters/qq.ts +71 -0
- package/agent/PERMITS.md +25 -0
- package/{skills/qq/SKILL.md → agent/skills/qq.md} +1 -1
- package/agent/tools/add_role.ts +21 -0
- package/agent/tools/channel_info.ts +17 -0
- package/agent/tools/create_role.ts +20 -0
- package/agent/tools/list_channels.ts +18 -0
- package/agent/tools/list_guilds.ts +14 -0
- package/agent/tools/list_roles.ts +18 -0
- package/agent/tools/member_detail.ts +18 -0
- package/agent/tools/remove_role.ts +21 -0
- package/commands/endpoint/add/[[id]].js +3 -0
- package/commands/endpoint/add/[[id]].ts +3 -0
- package/commands/endpoint/cancel.js +12 -0
- package/commands/endpoint/cancel.ts +15 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +12 -0
- package/lib/client.js +2 -0
- package/lib/endpoint.d.ts +56 -0
- package/lib/endpoint.js +456 -0
- package/lib/index.d.ts +8 -18
- package/lib/index.js +8 -338
- package/lib/outbound.d.ts +17 -0
- package/lib/outbound.js +234 -0
- package/lib/platform-permit.d.ts +15 -0
- package/lib/platform-permit.js +41 -0
- package/lib/protocol.d.ts +111 -0
- package/lib/protocol.js +166 -0
- package/lib/qq-bind-api.d.ts +36 -0
- package/lib/qq-bind-api.js +89 -0
- package/lib/qq-bind-flow.d.ts +19 -0
- package/lib/qq-bind-flow.js +85 -0
- package/lib/qq-bind-persist.d.ts +12 -0
- package/lib/qq-bind-persist.js +22 -0
- package/lib/qq-bot-kind-prompt.d.ts +9 -0
- package/lib/qq-bot-kind-prompt.js +37 -0
- package/lib/qq-endpoint-commands.d.ts +45 -0
- package/lib/qq-endpoint-commands.js +180 -0
- package/lib/qq-endpoint-config.d.ts +17 -0
- package/lib/qq-endpoint-config.js +22 -0
- package/lib/qq-intents.d.ts +27 -0
- package/lib/qq-intents.js +51 -0
- package/lib/qq-runtime-state.d.ts +30 -0
- package/lib/qq-runtime-state.js +14 -0
- package/lib/side-event-dispatch.d.ts +10 -0
- package/lib/side-event-dispatch.js +114 -0
- package/lib/webhook.d.ts +18 -0
- package/lib/webhook.js +103 -0
- package/lib/ws.d.ts +55 -0
- package/lib/ws.js +234 -0
- package/middlewares/bot-kind-reply.js +46 -0
- package/middlewares/bot-kind-reply.ts +60 -0
- package/package.json +69 -28
- package/plugin.js +18 -0
- package/schema.json +142 -0
- package/src/client.ts +16 -0
- package/src/endpoint.ts +534 -0
- package/src/index.ts +66 -326
- package/src/outbound.ts +255 -0
- package/src/platform-permit.ts +56 -0
- package/src/protocol.ts +273 -0
- package/src/qq-bind-api.ts +145 -0
- package/src/qq-bind-flow.ts +127 -0
- package/src/qq-bind-persist.ts +44 -0
- package/src/qq-bot-kind-prompt.ts +47 -0
- package/src/qq-endpoint-commands.ts +251 -0
- package/src/qq-endpoint-config.ts +44 -0
- package/src/qq-intents.ts +77 -0
- package/src/qq-runtime-state.ts +51 -0
- package/src/side-event-dispatch.ts +140 -0
- package/src/webhook.ts +132 -0
- package/src/ws.ts +331 -0
- package/client/Dashboard.tsx +0 -230
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -17
- package/dist/index.js +0 -30
- package/lib/adapter.d.ts +0 -17
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -49
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -102
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -337
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts +0 -16
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -58
- package/src/bot.ts +0 -347
- package/src/types.ts +0 -24
package/src/ws.ts
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ WebSocket transport: bot factory and inbound message normalization.
|
|
3
|
+
*/
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
Bot,
|
|
7
|
+
ReceiverMode,
|
|
8
|
+
type ApproveJoinRequestOptions,
|
|
9
|
+
type Sendable,
|
|
10
|
+
} from 'qq-official-bot';
|
|
11
|
+
import type { MediaRef, Segment } from '@zhin.js/core';
|
|
12
|
+
import type { QqOutboundMessage } from './outbound.js';
|
|
13
|
+
import type { QqChannelKind, QqInboundMessage, ResolvedQqWebsocketConfig } from './protocol.js';
|
|
14
|
+
|
|
15
|
+
/** SDK business surface exposed to plugins without bypassing Endpoint lifecycle ownership. */
|
|
16
|
+
export type QqOfficialApi = Pick<Bot,
|
|
17
|
+
| 'messageService'
|
|
18
|
+
| 'fileProcessor'
|
|
19
|
+
| 'guildService'
|
|
20
|
+
| 'channelService'
|
|
21
|
+
| 'memberService'
|
|
22
|
+
| 'permissionService'
|
|
23
|
+
| 'reactionService'
|
|
24
|
+
| 'scheduleService'
|
|
25
|
+
| 'threadService'
|
|
26
|
+
| 'audioService'
|
|
27
|
+
| 'botService'
|
|
28
|
+
| 'groupService'
|
|
29
|
+
| 'menuPanelService'
|
|
30
|
+
| 'group'
|
|
31
|
+
| 'user'
|
|
32
|
+
| 'channel'
|
|
33
|
+
| 'direct'
|
|
34
|
+
| 'guild'
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
/** Minimal bot surface used by the endpoint (real qq-official-bot or test mock). */
|
|
38
|
+
export interface QqBotTransport {
|
|
39
|
+
/** Full qq-official-bot business API; lifecycle remains owned by the Endpoint. */
|
|
40
|
+
readonly api: QqOfficialApi;
|
|
41
|
+
start(): Promise<void>;
|
|
42
|
+
stop(): Promise<void>;
|
|
43
|
+
on(event: string, listener: (...args: unknown[]) => void): void;
|
|
44
|
+
removeAllListeners(): void;
|
|
45
|
+
sendPrivateMessage(userId: string, message: QqOutboundMessage): Promise<unknown>;
|
|
46
|
+
sendGroupMessage(groupId: string, message: QqOutboundMessage): Promise<unknown>;
|
|
47
|
+
sendGuildMessage(channelId: string, message: QqOutboundMessage): Promise<unknown>;
|
|
48
|
+
sendDirectMessage?(guildId: string, message: QqOutboundMessage): Promise<unknown>;
|
|
49
|
+
recallPrivateMessage?(userId: string, messageId: string): Promise<unknown>;
|
|
50
|
+
recallGroupMessage?(groupId: string, messageId: string): Promise<unknown>;
|
|
51
|
+
recallGuildMessage?(channelId: string, messageId: string): Promise<unknown>;
|
|
52
|
+
recallDirectMessage?(guildId: string, messageId: string): Promise<unknown>;
|
|
53
|
+
getGuilds(): Promise<unknown[]>;
|
|
54
|
+
getChannels(guildId: string): Promise<unknown[]>;
|
|
55
|
+
getChannelInfo(channelId: string): Promise<unknown>;
|
|
56
|
+
getGuildMember(guildId: string, userId: string): Promise<unknown>;
|
|
57
|
+
getGuildRoles(guildId: string): Promise<unknown[]>;
|
|
58
|
+
createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
|
|
59
|
+
addMemberRole(
|
|
60
|
+
guildId: string,
|
|
61
|
+
channelId: string,
|
|
62
|
+
userId: string,
|
|
63
|
+
roleId: string,
|
|
64
|
+
): Promise<boolean>;
|
|
65
|
+
removeMemberRole(
|
|
66
|
+
guildId: string,
|
|
67
|
+
channelId: string,
|
|
68
|
+
userId: string,
|
|
69
|
+
roleId: string,
|
|
70
|
+
): Promise<boolean>;
|
|
71
|
+
approveGroupJoinRequest?(
|
|
72
|
+
groupId: string,
|
|
73
|
+
userId: string,
|
|
74
|
+
options: ApproveJoinRequestOptions,
|
|
75
|
+
): Promise<unknown>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type CreateQqBot = (config: ResolvedQqWebsocketConfig) => QqBotTransport;
|
|
79
|
+
|
|
80
|
+
interface QqOfficialBotLifecycle {
|
|
81
|
+
stop(): Promise<unknown>;
|
|
82
|
+
readonly sessionManager: {
|
|
83
|
+
userClose: boolean;
|
|
84
|
+
readonly connectionManager?: { destroy(): void };
|
|
85
|
+
readonly authManager?: { destroy(): void };
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* qq-official-bot 1.2 used a separate connection manager; 1.3 moved WebSocket
|
|
91
|
+
* ownership into the receiver, but Session.stop() still leaves the auth refresh
|
|
92
|
+
* manager alive. Mark the session closed, clean up legacy connections when
|
|
93
|
+
* present, stop the receiver, then destroy auth refresh at the SDK boundary.
|
|
94
|
+
*/
|
|
95
|
+
export async function stopQqOfficialBot(bot: QqOfficialBotLifecycle): Promise<void> {
|
|
96
|
+
const session = bot.sessionManager;
|
|
97
|
+
session.userClose = true;
|
|
98
|
+
session.connectionManager?.destroy();
|
|
99
|
+
try {
|
|
100
|
+
await bot.stop();
|
|
101
|
+
} finally {
|
|
102
|
+
session.authManager?.destroy();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function bindQqBotInboundEvents(
|
|
107
|
+
bot: QqBotTransport,
|
|
108
|
+
admitRaw: (raw: unknown) => void,
|
|
109
|
+
): void {
|
|
110
|
+
bot.on('message.group', admitRaw);
|
|
111
|
+
bot.on('message.guild', admitRaw);
|
|
112
|
+
bot.on('message.private', admitRaw);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function normalizeQqMessage(raw: unknown): QqInboundMessage | null {
|
|
116
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
117
|
+
const msg = raw as {
|
|
118
|
+
message_id?: string | number;
|
|
119
|
+
message_type?: string;
|
|
120
|
+
sub_type?: string;
|
|
121
|
+
user_id?: string | number;
|
|
122
|
+
group_id?: string | number;
|
|
123
|
+
group_openid?: string;
|
|
124
|
+
channel_id?: string | number;
|
|
125
|
+
guild_id?: string | number;
|
|
126
|
+
raw_message?: string;
|
|
127
|
+
message?: unknown;
|
|
128
|
+
sender?: { user_id?: string | number; user_name?: string; roles?: (string | number)[] };
|
|
129
|
+
member?: { roles?: (string | number)[] };
|
|
130
|
+
author?: { member_openid?: string; user_openid?: string; id?: string; username?: string };
|
|
131
|
+
/** AT 事件载荷:被 @ 的用户列表;is_you 标识当前机器人,bot 标识机器人账号 */
|
|
132
|
+
mentions?: Array<{ id?: string | number; bot?: boolean; is_you?: boolean; member_openid?: string; member_role?: string }>;
|
|
133
|
+
};
|
|
134
|
+
if (msg.message_id == null) return null;
|
|
135
|
+
|
|
136
|
+
let channelKind: QqChannelKind = 'private';
|
|
137
|
+
let channelId = '';
|
|
138
|
+
|
|
139
|
+
const isGroup = msg.message_type === 'group'
|
|
140
|
+
|| (msg.message_type == null && (msg.group_id != null || msg.group_openid != null));
|
|
141
|
+
const isGuild = msg.message_type === 'guild'
|
|
142
|
+
|| (msg.message_type == null && !isGroup && msg.guild_id != null && msg.channel_id != null);
|
|
143
|
+
|
|
144
|
+
if (isGuild) {
|
|
145
|
+
channelKind = msg.sub_type === 'direct' ? 'direct' : 'channel';
|
|
146
|
+
channelId = channelKind === 'direct'
|
|
147
|
+
? String(msg.guild_id ?? '')
|
|
148
|
+
: String(msg.channel_id ?? '');
|
|
149
|
+
} else if (isGroup) {
|
|
150
|
+
channelKind = 'group';
|
|
151
|
+
channelId = String(msg.group_id ?? msg.group_openid ?? '');
|
|
152
|
+
} else {
|
|
153
|
+
channelKind = 'private';
|
|
154
|
+
channelId = String(msg.user_id ?? msg.author?.user_openid ?? msg.author?.id ?? '');
|
|
155
|
+
}
|
|
156
|
+
if (!channelId) return null;
|
|
157
|
+
|
|
158
|
+
const authorId = String(
|
|
159
|
+
msg.sender?.user_id
|
|
160
|
+
?? msg.user_id
|
|
161
|
+
?? msg.author?.member_openid
|
|
162
|
+
?? msg.author?.user_openid
|
|
163
|
+
?? msg.author?.id
|
|
164
|
+
?? '',
|
|
165
|
+
);
|
|
166
|
+
const content = extractTextContent(msg.message) || msg.raw_message || '';
|
|
167
|
+
const segments = extractCanonicalSegments(msg.message);
|
|
168
|
+
const rawRoles = msg.member?.roles ?? msg.sender?.roles;
|
|
169
|
+
const authorRoles = Array.isArray(rawRoles) ? rawRoles.map(String) : undefined;
|
|
170
|
+
// 统一按 mentions 判定 @:is_you 精确标识当前机器人(群载荷实测字段)。
|
|
171
|
+
// 群里 @ 另一个机器人时 mentions 带 bot:true 但无 is_you,不能误判为 @ 当前机器人,
|
|
172
|
+
// 故群场景只认 is_you;bot 回退仅限频道 AT_MESSAGE_CREATE(该事件仅 @ 当前
|
|
173
|
+
// 机器人时下发,其 mentions 无 is_you)。
|
|
174
|
+
const mentioned = Array.isArray(msg.mentions)
|
|
175
|
+
&& msg.mentions.some((m) => m?.is_you === true || (m?.bot === true && channelKind !== 'group'));
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
id: String(msg.message_id),
|
|
179
|
+
content,
|
|
180
|
+
channelKind,
|
|
181
|
+
channelId,
|
|
182
|
+
authorId,
|
|
183
|
+
authorName: msg.sender?.user_name || msg.author?.username || authorId,
|
|
184
|
+
authorRoles,
|
|
185
|
+
timestamp: Date.now(),
|
|
186
|
+
guildId: msg.guild_id != null ? String(msg.guild_id) : undefined,
|
|
187
|
+
rawMessage: msg.raw_message,
|
|
188
|
+
...(segments.length > 0 ? { segments } : {}),
|
|
189
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* SDK 解析后的消息段(text/at/face/image/video/audio/file/reply)→ canonical Segment[]。
|
|
195
|
+
* 附件段的 url 已由 SDK 归一(补 https:// 前缀);mime 无法从载荷得知时按类型给默认。
|
|
196
|
+
*/
|
|
197
|
+
function extractCanonicalSegments(message: unknown): Segment[] {
|
|
198
|
+
if (!Array.isArray(message)) return [];
|
|
199
|
+
const out: Segment[] = [];
|
|
200
|
+
for (const seg of message) {
|
|
201
|
+
if (!seg || typeof seg !== 'object') continue;
|
|
202
|
+
const item = seg as { type?: string; data?: Record<string, unknown> };
|
|
203
|
+
const type = item.type ?? '';
|
|
204
|
+
const data = item.data ?? {};
|
|
205
|
+
switch (type) {
|
|
206
|
+
case 'text': {
|
|
207
|
+
const text = String(data.text ?? '');
|
|
208
|
+
if (text) out.push({ type: 'text', data: { text } });
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'at': {
|
|
212
|
+
const target = String(data.user_id ?? data.id ?? '');
|
|
213
|
+
if (!target) break;
|
|
214
|
+
const name = typeof data.username === 'string' && data.username
|
|
215
|
+
? data.username
|
|
216
|
+
: undefined;
|
|
217
|
+
out.push({ type: 'mention', data: name ? { target, name } : { target } });
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case 'face': {
|
|
221
|
+
const id = data.id;
|
|
222
|
+
if (id == null) break;
|
|
223
|
+
out.push({ type: 'face', data: { id: id as string | number } });
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case 'image':
|
|
227
|
+
case 'video':
|
|
228
|
+
case 'audio':
|
|
229
|
+
case 'file': {
|
|
230
|
+
const url = typeof data.url === 'string' ? data.url : '';
|
|
231
|
+
if (!url) break;
|
|
232
|
+
const media: MediaRef = {
|
|
233
|
+
kind: 'url',
|
|
234
|
+
value: url,
|
|
235
|
+
mime_type: defaultMimeForSegment(type),
|
|
236
|
+
...(typeof data.name === 'string' && data.name ? { file_name: data.name } : {}),
|
|
237
|
+
...(typeof data.size === 'number' ? { size: data.size } : {}),
|
|
238
|
+
};
|
|
239
|
+
if (type === 'image') out.push({ type: 'image', data: { media } });
|
|
240
|
+
else if (type === 'video') out.push({ type: 'video', data: { media } });
|
|
241
|
+
else if (type === 'audio') out.push({ type: 'audio', data: { media } });
|
|
242
|
+
else out.push({ type: 'file', data: { media, ...(media.file_name ? { name: media.file_name } : {}) } });
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case 'reply': {
|
|
246
|
+
const messageId = String(data.id ?? data.message_id ?? '');
|
|
247
|
+
if (messageId) out.push({ type: 'reply', data: { message_id: messageId } });
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
default:
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function defaultMimeForSegment(type: string): string | undefined {
|
|
258
|
+
switch (type) {
|
|
259
|
+
case 'image': return 'image/jpeg';
|
|
260
|
+
case 'video': return 'video/mp4';
|
|
261
|
+
case 'audio': return 'audio/mpeg';
|
|
262
|
+
default: return undefined;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function extractTextContent(message: unknown): string {
|
|
267
|
+
if (typeof message === 'string') return message;
|
|
268
|
+
if (!Array.isArray(message)) return '';
|
|
269
|
+
return message
|
|
270
|
+
.map((seg) => {
|
|
271
|
+
if (!seg || typeof seg !== 'object') return '';
|
|
272
|
+
const item = seg as { type?: string; data?: { text?: string }; text?: string };
|
|
273
|
+
if (item.type === 'text') return String(item.data?.text ?? item.text ?? '');
|
|
274
|
+
return '';
|
|
275
|
+
})
|
|
276
|
+
.join('');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function defaultCreateBot(config: ResolvedQqWebsocketConfig): QqBotTransport {
|
|
280
|
+
const bot = new Bot({
|
|
281
|
+
appid: config.appid,
|
|
282
|
+
secret: config.secret,
|
|
283
|
+
mode: ReceiverMode.WEBSOCKET,
|
|
284
|
+
sandbox: config.sandbox,
|
|
285
|
+
dataDir: path.join(process.cwd(), 'data', 'qq'),
|
|
286
|
+
// 关闭 qq-official-bot 内置 log4js,入/出站由 getAdapterLogger('qq', …) 统一格式
|
|
287
|
+
logLevel: 'off',
|
|
288
|
+
...(config.intents ? { intents: config.intents as never } : {}),
|
|
289
|
+
...(config.accessTokenUrl ? { accessTokenUrl: config.accessTokenUrl } : {}),
|
|
290
|
+
...(config.gatewayUrl ? { gatewayUrl: config.gatewayUrl } : {}),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
api: bot,
|
|
295
|
+
start: () => bot.start().then(() => undefined),
|
|
296
|
+
stop: () => stopQqOfficialBot(bot as unknown as QqOfficialBotLifecycle),
|
|
297
|
+
on: (event, listener) => {
|
|
298
|
+
bot.on(event as never, listener as never);
|
|
299
|
+
},
|
|
300
|
+
removeAllListeners: () => {
|
|
301
|
+
bot.removeAllListeners(undefined as never);
|
|
302
|
+
},
|
|
303
|
+
sendPrivateMessage: (userId, message) => bot.sendPrivateMessage(userId, message as Sendable),
|
|
304
|
+
sendGroupMessage: (groupId, message) => bot.sendGroupMessage(groupId, message as Sendable),
|
|
305
|
+
sendGuildMessage: (channelId, message) => bot.sendGuildMessage(channelId, message as Sendable),
|
|
306
|
+
sendDirectMessage: (guildId, message) => bot.sendDirectMessage(guildId, message as Sendable),
|
|
307
|
+
recallPrivateMessage: (userId, messageId) => bot.recallPrivateMessage(userId, messageId),
|
|
308
|
+
recallGroupMessage: (groupId, messageId) => bot.recallGroupMessage(groupId, messageId),
|
|
309
|
+
recallGuildMessage: (channelId, messageId) => bot.recallGuildMessage(channelId, messageId),
|
|
310
|
+
recallDirectMessage: (guildId, messageId) => bot.recallDirectMessage(guildId, messageId),
|
|
311
|
+
getGuilds: () => bot.guildService.getList(),
|
|
312
|
+
getChannels: (guildId) => bot.channelService.getList(guildId),
|
|
313
|
+
getChannelInfo: (channelId) => bot.channelService.getInfo(channelId),
|
|
314
|
+
getGuildMember: (guildId, userId) => bot.memberService.getGuildMemberInfo(guildId, userId),
|
|
315
|
+
getGuildRoles: (guildId) => bot.guildService.getRoles(guildId),
|
|
316
|
+
createGuildRole: (guildId, name, color) =>
|
|
317
|
+
bot.guildService.createRole(guildId, { name, color: color || 0, hoist: 0 }),
|
|
318
|
+
addMemberRole: async (guildId, channelId, userId, roleId) => {
|
|
319
|
+
await bot.memberService.addMemberRole(guildId, channelId, userId, roleId);
|
|
320
|
+
return true;
|
|
321
|
+
},
|
|
322
|
+
removeMemberRole: async (guildId, channelId, userId, roleId) => {
|
|
323
|
+
await bot.memberService.removeMemberRole(guildId, channelId, userId, roleId);
|
|
324
|
+
return true;
|
|
325
|
+
},
|
|
326
|
+
approveGroupJoinRequest: (groupId, userId, options) =>
|
|
327
|
+
(bot as { approveGroupJoinRequest?: QqBotTransport['approveGroupJoinRequest'] })
|
|
328
|
+
.approveGroupJoinRequest?.(groupId, userId, options)
|
|
329
|
+
?? Promise.reject(new Error('approveGroupJoinRequest is not available')),
|
|
330
|
+
};
|
|
331
|
+
}
|
package/client/Dashboard.tsx
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState, useCallback } from 'react'
|
|
2
|
-
import { apiFetch } from './utils/api'
|
|
3
|
-
import { RefreshCw, Server, Wifi, WifiOff, Power, PowerOff, ChevronRight, ChevronDown, Hash, Loader2 } from 'lucide-react'
|
|
4
|
-
|
|
5
|
-
interface BotInfo {
|
|
6
|
-
name: string
|
|
7
|
-
connected: boolean
|
|
8
|
-
guildCount: number
|
|
9
|
-
status: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Guild {
|
|
13
|
-
id: string
|
|
14
|
-
name: string
|
|
15
|
-
icon?: string
|
|
16
|
-
description?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface Channel {
|
|
20
|
-
id: string
|
|
21
|
-
name: string
|
|
22
|
-
type?: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type Tab = 'overview' | 'guilds'
|
|
26
|
-
|
|
27
|
-
export default function QQDashboard() {
|
|
28
|
-
const [bots, setBots] = useState<BotInfo[]>([])
|
|
29
|
-
const [loading, setLoading] = useState(true)
|
|
30
|
-
const [error, setError] = useState('')
|
|
31
|
-
const [tab, setTab] = useState<Tab>('overview')
|
|
32
|
-
const [actionLoading, setActionLoading] = useState<Record<string, boolean>>({})
|
|
33
|
-
|
|
34
|
-
// Guild browser state
|
|
35
|
-
const [selectedBot, setSelectedBot] = useState('')
|
|
36
|
-
const [guilds, setGuilds] = useState<Guild[]>([])
|
|
37
|
-
const [guildsLoading, setGuildsLoading] = useState(false)
|
|
38
|
-
const [expandedGuild, setExpandedGuild] = useState<string | null>(null)
|
|
39
|
-
const [channels, setChannels] = useState<Record<string, Channel[]>>({})
|
|
40
|
-
const [channelsLoading, setChannelsLoading] = useState<string | null>(null)
|
|
41
|
-
|
|
42
|
-
const fetchData = useCallback(async () => {
|
|
43
|
-
setLoading(true)
|
|
44
|
-
setError('')
|
|
45
|
-
try {
|
|
46
|
-
const res = await apiFetch('/api/qq/bots')
|
|
47
|
-
const json = await res.json()
|
|
48
|
-
if (json.success) setBots(json.data)
|
|
49
|
-
else setError(json.error || '获取数据失败')
|
|
50
|
-
} catch {
|
|
51
|
-
setError('无法连接服务器')
|
|
52
|
-
} finally {
|
|
53
|
-
setLoading(false)
|
|
54
|
-
}
|
|
55
|
-
}, [])
|
|
56
|
-
|
|
57
|
-
useEffect(() => { fetchData() }, [fetchData])
|
|
58
|
-
|
|
59
|
-
const toggleConnect = async (name: string, connected: boolean) => {
|
|
60
|
-
setActionLoading(prev => ({ ...prev, [name]: true }))
|
|
61
|
-
try {
|
|
62
|
-
const endpoint = connected ? 'disconnect' : 'connect'
|
|
63
|
-
const res = await apiFetch(`/api/qq/bots/${encodeURIComponent(name)}/${endpoint}`, { method: 'POST' })
|
|
64
|
-
const json = await res.json()
|
|
65
|
-
if (!json.success) setError(json.error || '操作失败')
|
|
66
|
-
await fetchData()
|
|
67
|
-
} catch {
|
|
68
|
-
setError('操作失败')
|
|
69
|
-
} finally {
|
|
70
|
-
setActionLoading(prev => ({ ...prev, [name]: false }))
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const loadGuilds = async (botName: string) => {
|
|
75
|
-
setSelectedBot(botName)
|
|
76
|
-
setGuildsLoading(true)
|
|
77
|
-
setGuilds([])
|
|
78
|
-
setExpandedGuild(null)
|
|
79
|
-
setChannels({})
|
|
80
|
-
try {
|
|
81
|
-
const res = await apiFetch(`/api/qq/bots/${encodeURIComponent(botName)}/guilds`)
|
|
82
|
-
const json = await res.json()
|
|
83
|
-
if (json.success) setGuilds(json.data)
|
|
84
|
-
else setError(json.error || '获取频道失败')
|
|
85
|
-
} catch {
|
|
86
|
-
setError('获取频道失败')
|
|
87
|
-
} finally {
|
|
88
|
-
setGuildsLoading(false)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const toggleGuild = async (guildId: string) => {
|
|
93
|
-
if (expandedGuild === guildId) { setExpandedGuild(null); return }
|
|
94
|
-
setExpandedGuild(guildId)
|
|
95
|
-
if (channels[guildId]) return
|
|
96
|
-
setChannelsLoading(guildId)
|
|
97
|
-
try {
|
|
98
|
-
const res = await apiFetch(`/api/qq/bots/${encodeURIComponent(selectedBot)}/guilds/${encodeURIComponent(guildId)}/channels`)
|
|
99
|
-
const json = await res.json()
|
|
100
|
-
if (json.success) setChannels(prev => ({ ...prev, [guildId]: json.data }))
|
|
101
|
-
} catch { /* ignore */ } finally {
|
|
102
|
-
setChannelsLoading(null)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const onlineBots = bots.filter(b => b.connected)
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<div className="p-6 max-w-5xl mx-auto">
|
|
110
|
-
{/* Header */}
|
|
111
|
-
<div className="flex items-center justify-between mb-6">
|
|
112
|
-
<h1 className="text-2xl font-bold flex items-center gap-2">
|
|
113
|
-
<Server className="w-6 h-6" /> QQ 官方机器人
|
|
114
|
-
</h1>
|
|
115
|
-
<button onClick={fetchData} disabled={loading}
|
|
116
|
-
className="flex items-center gap-1 px-3 py-1.5 rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 text-sm">
|
|
117
|
-
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} /> 刷新
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
120
|
-
|
|
121
|
-
{error && <div className="mb-4 p-3 bg-red-50 text-red-600 rounded border border-red-200">{error}</div>}
|
|
122
|
-
|
|
123
|
-
{/* Tabs */}
|
|
124
|
-
<div className="flex gap-1 mb-6 border-b">
|
|
125
|
-
<button onClick={() => setTab('overview')}
|
|
126
|
-
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === 'overview' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`}>
|
|
127
|
-
概览
|
|
128
|
-
</button>
|
|
129
|
-
<button onClick={() => setTab('guilds')}
|
|
130
|
-
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === 'guilds' ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`}>
|
|
131
|
-
频道浏览
|
|
132
|
-
</button>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
{/* Overview Tab */}
|
|
136
|
-
{tab === 'overview' && (
|
|
137
|
-
<>
|
|
138
|
-
{!loading && !bots.length && !error && (
|
|
139
|
-
<div className="text-center text-gray-500 py-12">暂无 QQ 机器人实例</div>
|
|
140
|
-
)}
|
|
141
|
-
<div className="grid gap-4 md:grid-cols-2">
|
|
142
|
-
{bots.map((bot) => (
|
|
143
|
-
<div key={bot.name} className="border rounded-lg p-4 bg-card shadow-sm">
|
|
144
|
-
<div className="flex items-center justify-between mb-3">
|
|
145
|
-
<span className="font-medium text-lg">{bot.name}</span>
|
|
146
|
-
{bot.connected
|
|
147
|
-
? <span className="flex items-center gap-1 text-green-600 text-sm"><Wifi className="w-4 h-4" /> 在线</span>
|
|
148
|
-
: <span className="flex items-center gap-1 text-gray-400 text-sm"><WifiOff className="w-4 h-4" /> 离线</span>}
|
|
149
|
-
</div>
|
|
150
|
-
<div className="grid grid-cols-1 gap-2 text-sm text-gray-600 mb-3">
|
|
151
|
-
<div className="flex justify-between"><span>频道数</span><span className="font-mono">{bot.guildCount}</span></div>
|
|
152
|
-
</div>
|
|
153
|
-
<div className="flex gap-2">
|
|
154
|
-
<button
|
|
155
|
-
onClick={() => toggleConnect(bot.name, bot.connected)}
|
|
156
|
-
disabled={actionLoading[bot.name]}
|
|
157
|
-
className={`flex items-center gap-1 px-3 py-1.5 rounded text-sm text-white ${bot.connected ? 'bg-red-500 hover:bg-red-600' : 'bg-green-500 hover:bg-green-600'} disabled:opacity-50`}>
|
|
158
|
-
{actionLoading[bot.name]
|
|
159
|
-
? <Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
160
|
-
: bot.connected ? <PowerOff className="w-3.5 h-3.5" /> : <Power className="w-3.5 h-3.5" />}
|
|
161
|
-
{bot.connected ? '断开' : '连接'}
|
|
162
|
-
</button>
|
|
163
|
-
{bot.connected && (
|
|
164
|
-
<button onClick={() => { setTab('guilds'); loadGuilds(bot.name) }}
|
|
165
|
-
className="flex items-center gap-1 px-3 py-1.5 rounded text-sm bg-gray-100 hover:bg-gray-200 text-gray-700">
|
|
166
|
-
<Hash className="w-3.5 h-3.5" /> 浏览频道
|
|
167
|
-
</button>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
))}
|
|
172
|
-
</div>
|
|
173
|
-
</>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{/* Guilds Tab */}
|
|
177
|
-
{tab === 'guilds' && (
|
|
178
|
-
<div>
|
|
179
|
-
{/* Bot selector */}
|
|
180
|
-
{onlineBots.length > 0 && (
|
|
181
|
-
<div className="mb-4 flex items-center gap-2">
|
|
182
|
-
<label className="text-sm text-gray-500">选择机器人:</label>
|
|
183
|
-
<select value={selectedBot} onChange={(e) => loadGuilds(e.target.value)}
|
|
184
|
-
className="border rounded px-2 py-1 text-sm">
|
|
185
|
-
<option value="">--</option>
|
|
186
|
-
{onlineBots.map(b => <option key={b.name} value={b.name}>{b.name}</option>)}
|
|
187
|
-
</select>
|
|
188
|
-
</div>
|
|
189
|
-
)}
|
|
190
|
-
{!onlineBots.length && <div className="text-center text-gray-500 py-8">暂无在线机器人</div>}
|
|
191
|
-
{guildsLoading && <div className="flex justify-center py-8"><Loader2 className="w-6 h-6 animate-spin text-blue-500" /></div>}
|
|
192
|
-
|
|
193
|
-
{/* Guild → Channel tree */}
|
|
194
|
-
{!guildsLoading && guilds.length > 0 && (
|
|
195
|
-
<div className="border rounded-lg bg-card shadow-sm divide-y">
|
|
196
|
-
{guilds.map((g) => (
|
|
197
|
-
<div key={g.id}>
|
|
198
|
-
<button onClick={() => toggleGuild(g.id)}
|
|
199
|
-
className="w-full flex items-center gap-2 px-4 py-3 hover:bg-gray-50 text-left">
|
|
200
|
-
{expandedGuild === g.id ? <ChevronDown className="w-4 h-4 text-gray-400" /> : <ChevronRight className="w-4 h-4 text-gray-400" />}
|
|
201
|
-
<span className="font-medium">{g.name}</span>
|
|
202
|
-
<span className="text-xs text-gray-400 ml-auto">{g.id}</span>
|
|
203
|
-
</button>
|
|
204
|
-
{expandedGuild === g.id && (
|
|
205
|
-
<div className="px-4 pb-3">
|
|
206
|
-
{channelsLoading === g.id
|
|
207
|
-
? <div className="flex items-center gap-2 text-gray-400 text-sm py-2 pl-6"><Loader2 className="w-4 h-4 animate-spin" /> 加载中…</div>
|
|
208
|
-
: channels[g.id]?.length
|
|
209
|
-
? <div className="pl-6 space-y-1">
|
|
210
|
-
{channels[g.id].map(ch => (
|
|
211
|
-
<div key={ch.id} className="flex items-center gap-2 text-sm text-gray-600 py-1">
|
|
212
|
-
<Hash className="w-3.5 h-3.5 text-gray-400" />
|
|
213
|
-
<span>{ch.name}</span>
|
|
214
|
-
<span className="text-xs text-gray-400 ml-auto">{ch.id}</span>
|
|
215
|
-
</div>
|
|
216
|
-
))}
|
|
217
|
-
</div>
|
|
218
|
-
: <div className="pl-6 text-sm text-gray-400 py-1">无子频道</div>}
|
|
219
|
-
</div>
|
|
220
|
-
)}
|
|
221
|
-
</div>
|
|
222
|
-
))}
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
{!guildsLoading && selectedBot && !guilds.length && <div className="text-center text-gray-400 py-8">该机器人未加入任何频道</div>}
|
|
226
|
-
</div>
|
|
227
|
-
)}
|
|
228
|
-
</div>
|
|
229
|
-
)
|
|
230
|
-
}
|
package/client/index.tsx
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { PluginRegisterHostApi } from '@zhin.js/console-types'
|
|
2
|
-
import QQDashboard from './Dashboard'
|
|
3
|
-
|
|
4
|
-
export function register(api: PluginRegisterHostApi) {
|
|
5
|
-
api.addRoute({
|
|
6
|
-
path: '/console/qq',
|
|
7
|
-
name: 'QQ官方',
|
|
8
|
-
element: api.React.createElement(QQDashboard, { hostReact: api.React }),
|
|
9
|
-
})
|
|
10
|
-
api.addTool({ id: 'qq', name: 'QQ官方', path: '/console/qq' })
|
|
11
|
-
}
|
package/client/tsconfig.json
DELETED
package/client/utils/api.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const TOKEN_KEY = "zhin_api_token";
|
|
2
|
-
|
|
3
|
-
export function getToken(): string | null {
|
|
4
|
-
return localStorage.getItem(TOKEN_KEY);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function apiFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
|
8
|
-
const token = getToken();
|
|
9
|
-
const headers = new Headers(init?.headers);
|
|
10
|
-
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
11
|
-
const res = await fetch(input, { ...init, headers });
|
|
12
|
-
if (res.status === 401) {
|
|
13
|
-
localStorage.removeItem(TOKEN_KEY);
|
|
14
|
-
window.dispatchEvent(new CustomEvent("zhin:auth-required"));
|
|
15
|
-
}
|
|
16
|
-
return res;
|
|
17
|
-
}
|
package/dist/index.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import{useEffect as Se,useState as c,useCallback as ke}from"react";var Q="zhin_api_token";function ue(){return localStorage.getItem(Q)}async function k(t,o){let l=ue(),f=new Headers(o?.headers);l&&f.set("Authorization",`Bearer ${l}`);let n=await fetch(t,{...o,headers:f});return n.status===401&&(localStorage.removeItem(Q),window.dispatchEvent(new CustomEvent("zhin:auth-required"))),n}import{forwardRef as re,createElement as ie}from"react";var v=(...t)=>t.filter((o,l,f)=>!!o&&o.trim()!==""&&f.indexOf(o)===l).join(" ").trim();var J=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();var _=t=>t.replace(/^([A-Z])|[\s-_]+(\w)/g,(o,l,f)=>f?f.toUpperCase():l.toLowerCase());var V=t=>{let o=_(t);return o.charAt(0).toUpperCase()+o.slice(1)};import{forwardRef as se,createElement as Y}from"react";var U={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};var j=t=>{for(let o in t)if(o.startsWith("aria-")||o==="role"||o==="title")return!0;return!1};import{createContext as de,useContext as le,useMemo as Te,createElement as be}from"react";var fe=de({});var $=()=>le(fe);var ee=se(({color:t,size:o,strokeWidth:l,absoluteStrokeWidth:f,className:n="",children:s,iconNode:m,...L},R)=>{let{size:I=24,strokeWidth:x=2,absoluteStrokeWidth:O=!1,color:h="currentColor",className:q=""}=$()??{},g=f??O?Number(l??x)*24/Number(o??I):l??x;return Y("svg",{ref:R,...U,width:o??I??U.width,height:o??I??U.height,stroke:t??h,strokeWidth:g,className:v("lucide",q,n),...!s&&!j(L)&&{"aria-hidden":"true"},...L},[...m.map(([T,S])=>Y(T,S)),...Array.isArray(s)?s:[s]])});var d=(t,o)=>{let l=re(({className:f,...n},s)=>ie(ee,{ref:s,iconNode:o,className:v(`lucide-${J(V(t))}`,`lucide-${t}`,f),...n}));return l.displayName=V(t),l};var ce=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],w=d("chevron-down",ce);var ne=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],P=d("chevron-right",ne);var pe=[["line",{x1:"4",x2:"20",y1:"9",y2:"9",key:"4lhtct"}],["line",{x1:"4",x2:"20",y1:"15",y2:"15",key:"vyu0kd"}],["line",{x1:"10",x2:"8",y1:"3",y2:"21",key:"1ggp8o"}],["line",{x1:"16",x2:"14",y1:"3",y2:"21",key:"weycgp"}]],C=d("hash",pe);var me=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],p=d("loader-circle",me);var Le=[["path",{d:"M18.36 6.64A9 9 0 0 1 20.77 15",key:"dxknvb"}],["path",{d:"M6.16 6.16a9 9 0 1 0 12.68 12.68",key:"1x7qb5"}],["path",{d:"M12 2v4",key:"3427ic"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]],A=d("power-off",Le);var Ie=[["path",{d:"M12 2v10",key:"mnfbl"}],["path",{d:"M18.4 6.6a9 9 0 1 1-12.77.04",key:"obofu9"}]],B=d("power",Ie);var xe=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],M=d("refresh-cw",xe);var Ce=[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]],F=d("server",Ce);var he=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}],["path",{d:"M5 12.859a10 10 0 0 1 5.17-2.69",key:"1dl1wf"}],["path",{d:"M19 12.859a10 10 0 0 0-2.007-1.523",key:"4k23kn"}],["path",{d:"M2 8.82a15 15 0 0 1 4.177-2.643",key:"1grhjp"}],["path",{d:"M22 8.82a15 15 0 0 0-11.288-3.764",key:"z3jwby"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]],D=d("wifi-off",he);var ge=[["path",{d:"M12 20h.01",key:"zekei9"}],["path",{d:"M2 8.82a15 15 0 0 1 20 0",key:"dnpr2z"}],["path",{d:"M5 12.859a10 10 0 0 1 14 0",key:"1x1e6c"}],["path",{d:"M8.5 16.429a5 5 0 0 1 7 0",key:"1bycff"}]],y=d("wifi",ge);import{Fragment as we,jsx as a,jsxs as u}from"react/jsx-runtime";function E(){let[t,o]=c([]),[l,f]=c(!0),[n,s]=c(""),[m,L]=c("overview"),[R,I]=c({}),[x,O]=c(""),[h,q]=c([]),[g,T]=c(!1),[S,H]=c(null),[G,z]=c({}),[ae,N]=c(null),b=ke(async()=>{f(!0),s("");try{let i=await(await k("/api/qq/bots")).json();i.success?o(i.data):s(i.error||"\u83B7\u53D6\u6570\u636E\u5931\u8D25")}catch{s("\u65E0\u6CD5\u8FDE\u63A5\u670D\u52A1\u5668")}finally{f(!1)}},[]);Se(()=>{b()},[b]);let te=async(e,i)=>{I(r=>({...r,[e]:!0}));try{let r=i?"disconnect":"connect",Z=await(await k(`/api/qq/bots/${encodeURIComponent(e)}/${r}`,{method:"POST"})).json();Z.success||s(Z.error||"\u64CD\u4F5C\u5931\u8D25"),await b()}catch{s("\u64CD\u4F5C\u5931\u8D25")}finally{I(r=>({...r,[e]:!1}))}},X=async e=>{O(e),T(!0),q([]),H(null),z({});try{let r=await(await k(`/api/qq/bots/${encodeURIComponent(e)}/guilds`)).json();r.success?q(r.data):s(r.error||"\u83B7\u53D6\u9891\u9053\u5931\u8D25")}catch{s("\u83B7\u53D6\u9891\u9053\u5931\u8D25")}finally{T(!1)}},oe=async e=>{if(S===e){H(null);return}if(H(e),!G[e]){N(e);try{let r=await(await k(`/api/qq/bots/${encodeURIComponent(x)}/guilds/${encodeURIComponent(e)}/channels`)).json();r.success&&z(K=>({...K,[e]:r.data}))}catch{}finally{N(null)}}},W=t.filter(e=>e.connected);return u("div",{className:"p-6 max-w-5xl mx-auto",children:[u("div",{className:"flex items-center justify-between mb-6",children:[u("h1",{className:"text-2xl font-bold flex items-center gap-2",children:[a(F,{className:"w-6 h-6"})," QQ \u5B98\u65B9\u673A\u5668\u4EBA"]}),u("button",{onClick:b,disabled:l,className:"flex items-center gap-1 px-3 py-1.5 rounded bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 text-sm",children:[a(M,{className:`w-4 h-4 ${l?"animate-spin":""}`})," \u5237\u65B0"]})]}),n&&a("div",{className:"mb-4 p-3 bg-red-50 text-red-600 rounded border border-red-200",children:n}),u("div",{className:"flex gap-1 mb-6 border-b",children:[a("button",{onClick:()=>L("overview"),className:`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${m==="overview"?"border-blue-500 text-blue-600":"border-transparent text-gray-500 hover:text-gray-700"}`,children:"\u6982\u89C8"}),a("button",{onClick:()=>L("guilds"),className:`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${m==="guilds"?"border-blue-500 text-blue-600":"border-transparent text-gray-500 hover:text-gray-700"}`,children:"\u9891\u9053\u6D4F\u89C8"})]}),m==="overview"&&u(we,{children:[!l&&!t.length&&!n&&a("div",{className:"text-center text-gray-500 py-12",children:"\u6682\u65E0 QQ \u673A\u5668\u4EBA\u5B9E\u4F8B"}),a("div",{className:"grid gap-4 md:grid-cols-2",children:t.map(e=>u("div",{className:"border rounded-lg p-4 bg-card shadow-sm",children:[u("div",{className:"flex items-center justify-between mb-3",children:[a("span",{className:"font-medium text-lg",children:e.name}),e.connected?u("span",{className:"flex items-center gap-1 text-green-600 text-sm",children:[a(y,{className:"w-4 h-4"})," \u5728\u7EBF"]}):u("span",{className:"flex items-center gap-1 text-gray-400 text-sm",children:[a(D,{className:"w-4 h-4"})," \u79BB\u7EBF"]})]}),a("div",{className:"grid grid-cols-1 gap-2 text-sm text-gray-600 mb-3",children:u("div",{className:"flex justify-between",children:[a("span",{children:"\u9891\u9053\u6570"}),a("span",{className:"font-mono",children:e.guildCount})]})}),u("div",{className:"flex gap-2",children:[u("button",{onClick:()=>te(e.name,e.connected),disabled:R[e.name],className:`flex items-center gap-1 px-3 py-1.5 rounded text-sm text-white ${e.connected?"bg-red-500 hover:bg-red-600":"bg-green-500 hover:bg-green-600"} disabled:opacity-50`,children:[R[e.name]?a(p,{className:"w-3.5 h-3.5 animate-spin"}):e.connected?a(A,{className:"w-3.5 h-3.5"}):a(B,{className:"w-3.5 h-3.5"}),e.connected?"\u65AD\u5F00":"\u8FDE\u63A5"]}),e.connected&&u("button",{onClick:()=>{L("guilds"),X(e.name)},className:"flex items-center gap-1 px-3 py-1.5 rounded text-sm bg-gray-100 hover:bg-gray-200 text-gray-700",children:[a(C,{className:"w-3.5 h-3.5"})," \u6D4F\u89C8\u9891\u9053"]})]})]},e.name))})]}),m==="guilds"&&u("div",{children:[W.length>0&&u("div",{className:"mb-4 flex items-center gap-2",children:[a("label",{className:"text-sm text-gray-500",children:"\u9009\u62E9\u673A\u5668\u4EBA\uFF1A"}),u("select",{value:x,onChange:e=>X(e.target.value),className:"border rounded px-2 py-1 text-sm",children:[a("option",{value:"",children:"--"}),W.map(e=>a("option",{value:e.name,children:e.name},e.name))]})]}),!W.length&&a("div",{className:"text-center text-gray-500 py-8",children:"\u6682\u65E0\u5728\u7EBF\u673A\u5668\u4EBA"}),g&&a("div",{className:"flex justify-center py-8",children:a(p,{className:"w-6 h-6 animate-spin text-blue-500"})}),!g&&h.length>0&&a("div",{className:"border rounded-lg bg-card shadow-sm divide-y",children:h.map(e=>u("div",{children:[u("button",{onClick:()=>oe(e.id),className:"w-full flex items-center gap-2 px-4 py-3 hover:bg-gray-50 text-left",children:[S===e.id?a(w,{className:"w-4 h-4 text-gray-400"}):a(P,{className:"w-4 h-4 text-gray-400"}),a("span",{className:"font-medium",children:e.name}),a("span",{className:"text-xs text-gray-400 ml-auto",children:e.id})]}),S===e.id&&a("div",{className:"px-4 pb-3",children:ae===e.id?u("div",{className:"flex items-center gap-2 text-gray-400 text-sm py-2 pl-6",children:[a(p,{className:"w-4 h-4 animate-spin"})," \u52A0\u8F7D\u4E2D\u2026"]}):G[e.id]?.length?a("div",{className:"pl-6 space-y-1",children:G[e.id].map(i=>u("div",{className:"flex items-center gap-2 text-sm text-gray-600 py-1",children:[a(C,{className:"w-3.5 h-3.5 text-gray-400"}),a("span",{children:i.name}),a("span",{className:"text-xs text-gray-400 ml-auto",children:i.id})]},i.id))}):a("div",{className:"pl-6 text-sm text-gray-400 py-1",children:"\u65E0\u5B50\u9891\u9053"})})]},e.id))}),!g&&x&&!h.length&&a("div",{className:"text-center text-gray-400 py-8",children:"\u8BE5\u673A\u5668\u4EBA\u672A\u52A0\u5165\u4EFB\u4F55\u9891\u9053"})]})]})}function Ra(t){t.addRoute({path:"/console/qq",name:"QQ\u5B98\u65B9",element:t.React.createElement(E,{hostReact:t.React})}),t.addTool({id:"qq",name:"QQ\u5B98\u65B9",path:"/console/qq"})}export{Ra as register};
|
|
2
|
-
/*! Bundled license information:
|
|
3
|
-
|
|
4
|
-
lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs:
|
|
5
|
-
lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs:
|
|
6
|
-
lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs:
|
|
7
|
-
lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs:
|
|
8
|
-
lucide-react/dist/esm/defaultAttributes.mjs:
|
|
9
|
-
lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs:
|
|
10
|
-
lucide-react/dist/esm/context.mjs:
|
|
11
|
-
lucide-react/dist/esm/Icon.mjs:
|
|
12
|
-
lucide-react/dist/esm/createLucideIcon.mjs:
|
|
13
|
-
lucide-react/dist/esm/icons/chevron-down.mjs:
|
|
14
|
-
lucide-react/dist/esm/icons/chevron-right.mjs:
|
|
15
|
-
lucide-react/dist/esm/icons/hash.mjs:
|
|
16
|
-
lucide-react/dist/esm/icons/loader-circle.mjs:
|
|
17
|
-
lucide-react/dist/esm/icons/power-off.mjs:
|
|
18
|
-
lucide-react/dist/esm/icons/power.mjs:
|
|
19
|
-
lucide-react/dist/esm/icons/refresh-cw.mjs:
|
|
20
|
-
lucide-react/dist/esm/icons/server.mjs:
|
|
21
|
-
lucide-react/dist/esm/icons/wifi-off.mjs:
|
|
22
|
-
lucide-react/dist/esm/icons/wifi.mjs:
|
|
23
|
-
lucide-react/dist/esm/lucide-react.mjs:
|
|
24
|
-
(**
|
|
25
|
-
* @license lucide-react v1.16.0 - ISC
|
|
26
|
-
*
|
|
27
|
-
* This source code is licensed under the ISC license.
|
|
28
|
-
* See the LICENSE file in the root directory of this source tree.
|
|
29
|
-
*)
|
|
30
|
-
*/
|
package/lib/adapter.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQ 官方适配器
|
|
3
|
-
*/
|
|
4
|
-
import { Adapter, Plugin } from "zhin.js";
|
|
5
|
-
import { QQBot } from "./bot.js";
|
|
6
|
-
import type { QQBotConfig, ReceiverMode } from "./types.js";
|
|
7
|
-
export declare class QQAdapter extends Adapter<QQBot<ReceiverMode>> {
|
|
8
|
-
constructor(plugin: Plugin);
|
|
9
|
-
createBot(config: QQBotConfig<ReceiverMode>): QQBot<ReceiverMode>;
|
|
10
|
-
kickMember(botId: string, sceneId: string, userId: string): Promise<boolean>;
|
|
11
|
-
muteMember(botId: string, sceneId: string, userId: string, duration?: number): Promise<boolean>;
|
|
12
|
-
muteAll(botId: string, sceneId: string, enable?: boolean): Promise<boolean>;
|
|
13
|
-
listMembers(botId: string, sceneId: string): Promise<any[]>;
|
|
14
|
-
getGroupInfo(botId: string, sceneId: string): Promise<any>;
|
|
15
|
-
start(): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=adapter.d.ts.map
|