@jerryliang122/openclaw-qqbot 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.
Files changed (120) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +967 -0
  3. package/README.zh.md +790 -0
  4. package/dist/index.cjs +18072 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +1222 -0
  7. package/index.ts +92 -0
  8. package/openclaw.plugin.json +38 -0
  9. package/package.json +69 -0
  10. package/preload.cjs +19 -0
  11. package/scripts/link-sdk-core.cjs +268 -0
  12. package/scripts/proactive-api-server.ts +369 -0
  13. package/scripts/send-proactive.ts +293 -0
  14. package/scripts/test-sendmedia.ts +116 -0
  15. package/skills/qqbot-channel/SKILL.md +285 -0
  16. package/skills/qqbot-channel/references/api_references.md +521 -0
  17. package/skills/qqbot-remind/SKILL.md +159 -0
  18. package/skills/qqbot-upgrade/SKILL.md +56 -0
  19. package/src/adapter/contract.ts +63 -0
  20. package/src/adapter/lint.ts +144 -0
  21. package/src/adapter/media.ts +40 -0
  22. package/src/adapter/pairing.ts +95 -0
  23. package/src/adapter/resolve.ts +255 -0
  24. package/src/adapter/setup.ts +13 -0
  25. package/src/adapter/webhook.ts +248 -0
  26. package/src/adapter/workspace.ts +21 -0
  27. package/src/agent-prompt-adapter.ts +26 -0
  28. package/src/bot-instance.ts +60 -0
  29. package/src/channel.ts +230 -0
  30. package/src/commands/bot-approve.ts +143 -0
  31. package/src/commands/bot-clear-storage.ts +114 -0
  32. package/src/commands/bot-group-always.ts +62 -0
  33. package/src/commands/bot-group-info.ts +48 -0
  34. package/src/commands/bot-help.ts +40 -0
  35. package/src/commands/bot-logs.ts +248 -0
  36. package/src/commands/bot-me.ts +18 -0
  37. package/src/commands/bot-pairing.ts +50 -0
  38. package/src/commands/bot-ping.ts +33 -0
  39. package/src/commands/bot-streaming.ts +55 -0
  40. package/src/commands/bot-upgrade.ts +56 -0
  41. package/src/commands/bot-version.ts +41 -0
  42. package/src/commands/config-util.ts +96 -0
  43. package/src/commands/index.ts +51 -0
  44. package/src/config.ts +403 -0
  45. package/src/constants.ts +6 -0
  46. package/src/dispatch/body-assembler.ts +308 -0
  47. package/src/dispatch/ctx-builder.ts +127 -0
  48. package/src/dispatch/dispatch.ts +667 -0
  49. package/src/dispatch/envelope-builder.ts +112 -0
  50. package/src/dispatch/index.ts +2 -0
  51. package/src/features/approval-capability.ts +302 -0
  52. package/src/features/approval-helpers.ts +271 -0
  53. package/src/features/approval-utils.ts +21 -0
  54. package/src/features/command-panel.ts +301 -0
  55. package/src/features/credential-backup.ts +74 -0
  56. package/src/features/group-mode-store.ts +79 -0
  57. package/src/features/history-store.ts +75 -0
  58. package/src/features/msgid-cache.ts +55 -0
  59. package/src/features/outbound-echo-store.ts +46 -0
  60. package/src/features/proactive-budget.ts +57 -0
  61. package/src/features/proactive.ts +549 -0
  62. package/src/features/question-helpers.ts +771 -0
  63. package/src/features/quota-manager.ts +173 -0
  64. package/src/features/ref-index-store.ts +289 -0
  65. package/src/features/secret-input-store.ts +118 -0
  66. package/src/features/secret-store-cli.ts +324 -0
  67. package/src/features/typing-refresh.ts +51 -0
  68. package/src/features/update-checker.ts +166 -0
  69. package/src/gateway/event-handlers.ts +456 -0
  70. package/src/gateway/index.ts +3 -0
  71. package/src/gateway/lifecycle.ts +236 -0
  72. package/src/gateway/middleware-setup.ts +173 -0
  73. package/src/gateway/qqbot-gateway.ts +458 -0
  74. package/src/gateway-adapter.ts +44 -0
  75. package/src/heartbeat-adapter.ts +57 -0
  76. package/src/message-adapter.ts +40 -0
  77. package/src/messaging-adapter.ts +78 -0
  78. package/src/middleware/access-control.ts +125 -0
  79. package/src/middleware/attachment.ts +373 -0
  80. package/src/middleware/inbound-guard.ts +102 -0
  81. package/src/middleware/policy-injector.ts +71 -0
  82. package/src/middleware/secret-capture.ts +161 -0
  83. package/src/middleware/typing.ts +110 -0
  84. package/src/openclaw-plugin-sdk.d.ts +543 -0
  85. package/src/outbound/chunker.ts +80 -0
  86. package/src/outbound/debounce.ts +102 -0
  87. package/src/outbound/deliver-pipeline.ts +235 -0
  88. package/src/outbound/index.ts +3 -0
  89. package/src/outbound/local-file-router.ts +145 -0
  90. package/src/outbound/media-send.ts +408 -0
  91. package/src/outbound/outbound-service.ts +298 -0
  92. package/src/outbound/reply-limiter.ts +139 -0
  93. package/src/outbound/sanitize.ts +32 -0
  94. package/src/outbound/streaming-controller.ts +332 -0
  95. package/src/outbound/target.ts +109 -0
  96. package/src/outbound-adapter.ts +323 -0
  97. package/src/plugin-base.ts +42 -0
  98. package/src/request-context.ts +50 -0
  99. package/src/runtime.ts +42 -0
  100. package/src/setup/account-key.ts +41 -0
  101. package/src/setup/finalize.ts +110 -0
  102. package/src/setup/login.ts +197 -0
  103. package/src/setup/surface.ts +40 -0
  104. package/src/status-adapter.ts +56 -0
  105. package/src/tools/platform.ts +149 -0
  106. package/src/tools/remind.ts +308 -0
  107. package/src/tools/secret-input.ts +185 -0
  108. package/src/types-augment.d.ts +54 -0
  109. package/src/types-plugin.ts +82 -0
  110. package/src/types.ts +620 -0
  111. package/src/typing-lifecycle.ts +182 -0
  112. package/src/utils/mention.ts +52 -0
  113. package/src/utils/pkg-version.ts +23 -0
  114. package/src/utils/platform.ts +459 -0
  115. package/src/utils/plugin-logger.ts +104 -0
  116. package/src/utils/ssrf-guard.ts +132 -0
  117. package/src/utils/stt.ts +150 -0
  118. package/src/utils/voice-text.ts +61 -0
  119. package/tsconfig.json +17 -0
  120. package/tsup.config.ts +64 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * QQ Bot QR 登录 — 两阶段接口适配
3
+ *
4
+ * 使用 @tencent-connect/qqbot-connector 的 startQrConnect 回调 API,
5
+ * 将扫码绑定流程拆分为 start / wait 两个阶段,对接 ChannelPlugin 的
6
+ * gateway.loginWithQrStart / loginWithQrWait。
7
+ */
8
+ import { startQrConnect, type QrConnectCredentials } from '@tencent-connect/qqbot-connector';
9
+
10
+ // ── 类型 ──
11
+
12
+ export interface QrLoginStartResult {
13
+ qrDataUrl?: string;
14
+ message: string;
15
+ }
16
+
17
+ export interface QrLoginWaitResult {
18
+ connected: boolean;
19
+ message: string;
20
+ credentials?: QrConnectCredentials[];
21
+ }
22
+
23
+ // ── 挂起的登录会话 ──
24
+
25
+ interface PendingSession {
26
+ /** 等待扫码结果的 Promise(onSuccess resolve, onFailure reject) */
27
+ credentialsPromise: Promise<QrConnectCredentials[]>;
28
+ /** 取消当前流程 */
29
+ dispose: () => void;
30
+ }
31
+
32
+ const pendingSessions = new Map<string, PendingSession>();
33
+
34
+ // ── 启动 QR 登录 ──
35
+
36
+ export function startQrLogin(accountId?: string, source = 'openclaw'): Promise<QrLoginStartResult> {
37
+ const key = accountId ?? 'default';
38
+
39
+ // 清理旧会话
40
+ pendingSessions.get(key)?.dispose();
41
+ pendingSessions.delete(key);
42
+
43
+ return new Promise((resolve) => {
44
+ let credentialsResolve: (creds: QrConnectCredentials[]) => void;
45
+ let credentialsReject: (err: Error) => void;
46
+
47
+ // 凭据 Promise — 在 onSuccess/onFailure 中 settle
48
+ const credentialsPromise = new Promise<QrConnectCredentials[]>((res, rej) => {
49
+ credentialsResolve = res;
50
+ credentialsReject = rej;
51
+ });
52
+
53
+ const dispose = startQrConnect(
54
+ {
55
+ onQrDisplayed(url) {
56
+ resolve({
57
+ qrDataUrl: url,
58
+ message: '请使用手机 QQ 扫描二维码完成绑定',
59
+ });
60
+ },
61
+ onSuccess: (creds) => credentialsResolve!(creds),
62
+ onFailure: (err) => credentialsReject!(err),
63
+ },
64
+ { displayQrCodeToConsole: true, source },
65
+ );
66
+
67
+ pendingSessions.set(key, { dispose, credentialsPromise });
68
+ });
69
+ }
70
+
71
+ // ── 等待扫码结果 ──
72
+
73
+ export async function waitQrLogin(accountId?: string): Promise<QrLoginWaitResult> {
74
+ const key = accountId ?? 'default';
75
+ const session = pendingSessions.get(key);
76
+
77
+ if (!session) {
78
+ return { connected: false, message: '没有正在进行的登录会话,请先运行 login 命令。' };
79
+ }
80
+
81
+ try {
82
+ const credentials = await session.credentialsPromise;
83
+ pendingSessions.delete(key);
84
+
85
+ if (credentials.length === 0) {
86
+ return { connected: false, message: '未获取到 QQ Bot 凭据。' };
87
+ }
88
+
89
+ return {
90
+ connected: true,
91
+ message: `绑定成功!AppID: ${credentials.map((c) => c.appId).join(', ')}`,
92
+ credentials,
93
+ };
94
+ } catch (err) {
95
+ pendingSessions.delete(key);
96
+ return {
97
+ connected: false,
98
+ message: `绑定失败: ${err instanceof Error ? err.message : String(err)}`,
99
+ };
100
+ }
101
+ }
102
+
103
+ // ── 主动取消 ──
104
+
105
+ export function cancelQrLogin(accountId?: string): void {
106
+ const key = accountId ?? 'default';
107
+ pendingSessions.get(key)?.dispose();
108
+ pendingSessions.delete(key);
109
+ }
110
+
111
+ // ── 解析 channelInput (appId:clientSecret) ──
112
+
113
+ export function parseChannelInput(channelInput?: string | null): { appId: string; clientSecret: string } | null {
114
+ if (!channelInput) return null;
115
+ const parts = channelInput.trim().split(':');
116
+ if (parts.length === 2 && parts[0] && parts[1]) {
117
+ return { appId: parts[0], clientSecret: parts[1] };
118
+ }
119
+ return null;
120
+ }
121
+
122
+ // ── auth.login 入口 ──
123
+
124
+ export interface QqbotLoginParams {
125
+ cfg: Record<string, unknown>;
126
+ accountId?: string | null;
127
+ channelInput?: string | null;
128
+ verbose?: boolean;
129
+ /** 框架注入的 runtime(含 config 持久化 API) */
130
+ [key: string]: unknown;
131
+ }
132
+
133
+ /**
134
+ * auth.login 完整实现。
135
+ *
136
+ * 账号策略:
137
+ * --account <id> → 写入指定账户
138
+ * channelInput → 用 appId 作为账户键
139
+ * QR 扫码 → 用扫码返回的 appId 作为新账户键
140
+ */
141
+ export async function qqbotLogin({
142
+ cfg,
143
+ accountId,
144
+ channelInput,
145
+ verbose,
146
+ ...rest
147
+ }: QqbotLoginParams): Promise<void> {
148
+ // 延迟导入,避免循环依赖
149
+ const { applyQQBotAccountConfig } = await import('../config.js');
150
+ const { persistAuthConfig } = await import('../adapter/resolve.js');
151
+ const { applyAccountDefaults } = await import('./finalize.js');
152
+ const { resolveAccountKey } = await import('./account-key.js');
153
+
154
+ const runtime: Record<string, unknown> = rest as any;
155
+ const resolvedId = accountId ? accountId.trim().toLowerCase() : null;
156
+
157
+ // 如果传了 appId:clientSecret,直接写入配置
158
+ const parsed = parseChannelInput(channelInput);
159
+ if (parsed) {
160
+ const key = resolveAccountKey(cfg as any, parsed.appId, resolvedId);
161
+ const result = applyQQBotAccountConfig(cfg as any, key, {
162
+ appId: parsed.appId,
163
+ clientSecret: parsed.clientSecret,
164
+ });
165
+ Object.assign(cfg, result);
166
+ // 对齐 setup:写入默认 streaming/dmPolicy
167
+ const withDefaults = applyAccountDefaults(cfg as any, key);
168
+ Object.assign(cfg, withDefaults);
169
+ await persistAuthConfig(runtime, cfg, 'restart');
170
+ if (verbose) console.log(`QQ Bot 已配置 (${key}) AppID: ${parsed.appId}`);
171
+ return;
172
+ }
173
+
174
+ // QR 扫码登录
175
+ const { qrDataUrl, message } = await startQrLogin(resolvedId || 'pending');
176
+ console.log(`\n${message}`);
177
+ if (qrDataUrl) console.log(`QR 链接: ${qrDataUrl}`);
178
+
179
+ const result = await waitQrLogin(resolvedId || 'pending');
180
+ if (!result.connected || !result.credentials) {
181
+ throw new Error(result.message);
182
+ }
183
+
184
+ for (const cred of result.credentials) {
185
+ const key = resolveAccountKey(cfg as any, cred.appId, resolvedId);
186
+ const next = applyQQBotAccountConfig(cfg as any, key, {
187
+ appId: cred.appId,
188
+ clientSecret: cred.appSecret,
189
+ });
190
+ Object.assign(cfg, next);
191
+ // 对齐 setup:写入默认 streaming/dmPolicy,扫码用户加入白名单
192
+ const withDefaults = applyAccountDefaults(cfg as any, key, cred.userOpenid);
193
+ Object.assign(cfg, withDefaults);
194
+ }
195
+ await persistAuthConfig(runtime, cfg, 'restart');
196
+ console.log(`QQ Bot 登录成功!`);
197
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * OpenClaw setup 向导 — QQ Bot 配置界面
3
+ */
4
+ import type { ChannelSetupWizard } from '../adapter/setup.js';
5
+ import { createStandardChannelSetupStatus, setSetupChannelEnabled } from '../adapter/setup.js';
6
+ import { listQQBotAccountIds, resolveQQBotAccount, resolveDefaultQQBotAccountId } from '../config.js';
7
+ import { finalizeQQBotSetup } from './finalize.js';
8
+
9
+ const CHANNEL = 'qqbot' as const;
10
+
11
+ export const qqbotSetupWizard: ChannelSetupWizard = {
12
+ channel: CHANNEL,
13
+ status: createStandardChannelSetupStatus({
14
+ channelLabel: 'QQ Bot',
15
+ configuredLabel: 'configured',
16
+ unconfiguredLabel: 'needs AppID + AppSecret',
17
+ configuredHint: 'configured',
18
+ unconfiguredHint: 'needs AppID + AppSecret',
19
+ configuredScore: 1,
20
+ unconfiguredScore: 6,
21
+ resolveConfigured: ({ cfg, accountId }) =>
22
+ (accountId ? [accountId] : listQQBotAccountIds(cfg as any)).some((id) => {
23
+ const account = resolveQQBotAccount(cfg as any, id);
24
+ return Boolean(account.appId && account.clientSecret);
25
+ }),
26
+ }),
27
+ // 未配置时默认使用 default 账号,有账户时框架会提示选择
28
+ resolveAccountIdForConfigure: async ({ cfg, shouldPromptAccountIds, accountOverride, defaultAccountId }) => {
29
+ if (accountOverride) return accountOverride;
30
+ const ids = listQQBotAccountIds(cfg as any);
31
+ if (ids.length === 0) return 'default';
32
+ if (!shouldPromptAccountIds) return ids[0];
33
+ return defaultAccountId || resolveDefaultQQBotAccountId(cfg as any);
34
+ },
35
+ credentials: [],
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ finalize: (async ({ cfg, accountId, prompter, runtime }: any) =>
38
+ finalizeQQBotSetup({ cfg, accountId, prompter: prompter as any, runtime: runtime as any })) as any,
39
+ disable: (cfg) => { setSetupChannelEnabled(cfg, CHANNEL, false); return cfg; },
40
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * QQBot Status 适配器
3
+ *
4
+ * 账户状态探测、审计、快照构建
5
+ */
6
+
7
+ import { DEFAULT_ACCOUNT_ID } from './config.js';
8
+ import type { ResolvedQQBotAccount } from './types.js';
9
+
10
+ /**
11
+ * 默认运行时状态
12
+ */
13
+ function createDefaultChannelRuntimeState(accountId: string) {
14
+ return {
15
+ accountId,
16
+ running: false,
17
+ connected: false,
18
+ lastConnectedAt: null as number | null,
19
+ lastError: null as string | null,
20
+ lastInboundAt: null as number | null,
21
+ lastOutboundAt: null as number | null,
22
+ };
23
+ }
24
+
25
+ /**
26
+ * QQBot Status Adapter
27
+ */
28
+ export const qqbotStatusAdapter = {
29
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
30
+
31
+ buildChannelSummary: ({ snapshot }: { snapshot: any }) => ({
32
+ configured: snapshot.configured ?? false,
33
+ tokenSource: snapshot.tokenSource ?? 'none',
34
+ running: snapshot.running ?? false,
35
+ connected: snapshot.connected ?? false,
36
+ lastConnectedAt: snapshot.lastConnectedAt ?? null,
37
+ lastError: snapshot.lastError ?? null,
38
+ }),
39
+
40
+ resolveAccountSnapshot: ({ account, runtime }: {
41
+ account?: ResolvedQQBotAccount;
42
+ runtime?: any;
43
+ }) => ({
44
+ accountId: account?.accountId ?? DEFAULT_ACCOUNT_ID,
45
+ name: account?.name,
46
+ enabled: account?.enabled ?? false,
47
+ configured: Boolean(account?.appId && account?.clientSecret),
48
+ tokenSource: account?.secretSource,
49
+ running: Boolean(runtime?.running ?? false),
50
+ connected: Boolean(runtime?.connected ?? false),
51
+ lastConnectedAt: runtime?.lastConnectedAt ?? null,
52
+ lastError: runtime?.lastError ?? null,
53
+ lastInboundAt: runtime?.lastInboundAt ?? null,
54
+ lastOutboundAt: runtime?.lastOutboundAt ?? null,
55
+ }),
56
+ };
@@ -0,0 +1,149 @@
1
+ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
+ import { listQQBotAccountIds } from '../config.js';
3
+ import { getBotForAccount } from '../bot-instance.js';
4
+ import { getRequestAccountId } from '../request-context.js';
5
+
6
+ // ========== JSON Schema ==========
7
+
8
+ const PlatformApiSchema = {
9
+ type: 'object',
10
+ properties: {
11
+ method: {
12
+ type: 'string',
13
+ description:
14
+ 'HTTP 请求方法。可选值:GET, POST, PUT, PATCH, DELETE',
15
+ enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
16
+ },
17
+ path: {
18
+ type: 'string',
19
+ description:
20
+ 'API 路径(不含域名),占位符需替换为实际值。' +
21
+ '示例:/users/@me/guilds, /guilds/{guild_id}/channels, ' +
22
+ '/v2/groups/{group_id}/bot_state',
23
+ },
24
+ body: {
25
+ type: 'object',
26
+ description:
27
+ '请求体(JSON),用于 POST/PUT/PATCH 请求。' +
28
+ 'GET/DELETE 请求不需要此参数。',
29
+ },
30
+ query: {
31
+ type: 'object',
32
+ description:
33
+ 'URL 查询参数(键值对),会拼接到路径后面。' +
34
+ '如 { "limit": "100", "after": "0" } 会拼接为 ?limit=100&after=0',
35
+ additionalProperties: { type: 'string' },
36
+ },
37
+ },
38
+ required: ['method', 'path'],
39
+ } as const;
40
+
41
+ // ========== 工具函数 ==========
42
+
43
+ function json(data: unknown) {
44
+ return {
45
+ content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }],
46
+ details: data,
47
+ };
48
+ }
49
+
50
+ function validatePath(path: string): string | null {
51
+ if (!path.startsWith('/')) return "path 必须以 / 开头";
52
+ if (path.includes('..') || path.includes('//')) return "path 不允许包含 .. 或 //";
53
+ if (!/^\/[a-zA-Z0-9\-._~:@!$&'()*+,;=/%]+$/.test(path) && path !== '/') {
54
+ return 'path 包含非法字符';
55
+ }
56
+ return null;
57
+ }
58
+
59
+ // ========== 注册入口 ==========
60
+
61
+ /**
62
+ * 注册 QQBot 平台统一 API 网关工具。
63
+ * 通过 SDK 的 bot.api 网关代理所有 QQ 开放平台 HTTP 接口 — 自动鉴权、重试、结构化错误。
64
+ *
65
+ * 覆盖范围:
66
+ * - 频道(Guild/Channel/Member/Announce/Forum/Schedule)→ 见 qqbot-channel skill
67
+ * - 群(Group/Member)→ 见 qqbot-group skill
68
+ */
69
+ export function registerPlatformTool(api: OpenClawPluginApi): void {
70
+ const cfg = api.config;
71
+ if (!cfg) return;
72
+
73
+ const accountIds = listQQBotAccountIds(cfg);
74
+ if (accountIds.length === 0) return;
75
+
76
+ api.registerTool(
77
+ {
78
+ name: 'qqbot_platform_api',
79
+ label: 'QQBot Platform API Gateway',
80
+ description:
81
+ 'QQ 开放平台统一 HTTP API 网关,自动填充鉴权 Token。' +
82
+ '常用接口速查:' +
83
+ '【频道】GET /users/@me/guilds | /guilds/{guild_id}/channels | /channels/{channel_id} | ' +
84
+ '【群】GET /v2/groups/{group_id}/bot_state | /v2/groups/{group_id}/members/{member_id} | /v2/groups/{group_id}/info。' +
85
+ '更多接口和参数详情请阅读 qqbot-channel 和 qqbot-group skill。',
86
+ parameters: PlatformApiSchema,
87
+ async execute(_toolCallId, params) {
88
+ const p = params as {
89
+ method: string;
90
+ path: string;
91
+ body?: Record<string, unknown>;
92
+ query?: Record<string, string>;
93
+ };
94
+
95
+ if (!p.method) return json({ error: 'method 为必填参数' });
96
+ if (!p.path) return json({ error: 'path 为必填参数' });
97
+
98
+ const method = p.method.toUpperCase();
99
+ if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
100
+ return json({ error: `不支持的 HTTP 方法: ${method}` });
101
+ }
102
+
103
+ const pathError = validatePath(p.path);
104
+ if (pathError) return json({ error: pathError });
105
+
106
+ const accountId = getRequestAccountId();
107
+ if (!accountId) {
108
+ return json({ error: '无法获取当前请求的账号信息,此工具仅支持在消息会话中使用' });
109
+ }
110
+
111
+ try {
112
+ const bot = getBotForAccount(accountId);
113
+ const apiGateway = bot.api;
114
+
115
+ let data: unknown;
116
+ switch (method) {
117
+ case 'GET':
118
+ data = await apiGateway.get(p.path, p.query);
119
+ break;
120
+ case 'POST':
121
+ data = await apiGateway.post(p.path, p.body);
122
+ break;
123
+ case 'PUT':
124
+ data = await apiGateway.put(p.path, p.body);
125
+ break;
126
+ case 'PATCH':
127
+ data = await apiGateway.patch(p.path, p.body);
128
+ break;
129
+ case 'DELETE':
130
+ data = await apiGateway.delete(p.path);
131
+ break;
132
+ }
133
+
134
+ return json(data);
135
+ } catch (err: unknown) {
136
+ const errMsg = err instanceof Error ? err.message : String(err);
137
+ const apiErr = err as { httpStatus?: number; bizCode?: number; path?: string };
138
+ return json({
139
+ error: errMsg,
140
+ status: apiErr.httpStatus,
141
+ code: apiErr.bizCode,
142
+ path: p.path,
143
+ });
144
+ }
145
+ },
146
+ },
147
+ { name: 'qqbot_platform_api' },
148
+ );
149
+ }