@zaofan/dsh-qqbot 0.6.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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/README_EN.md +273 -0
  4. package/client/qqbot-settings.js +1463 -0
  5. package/cordis.patch.yml +36 -0
  6. package/dist/api/group-admin.d.ts +112 -0
  7. package/dist/api/group-admin.d.ts.map +1 -0
  8. package/dist/api/group-admin.js +179 -0
  9. package/dist/api/group-admin.js.map +1 -0
  10. package/dist/channel-tools.d.ts +14 -0
  11. package/dist/channel-tools.d.ts.map +1 -0
  12. package/dist/channel-tools.js +880 -0
  13. package/dist/channel-tools.js.map +1 -0
  14. package/dist/commands/help.d.ts +11 -0
  15. package/dist/commands/help.d.ts.map +1 -0
  16. package/dist/commands/help.js +21 -0
  17. package/dist/commands/help.js.map +1 -0
  18. package/dist/commands/index.d.ts +13 -0
  19. package/dist/commands/index.d.ts.map +1 -0
  20. package/dist/commands/index.js +29 -0
  21. package/dist/commands/index.js.map +1 -0
  22. package/dist/commands/misc.d.ts +14 -0
  23. package/dist/commands/misc.d.ts.map +1 -0
  24. package/dist/commands/misc.js +39 -0
  25. package/dist/commands/misc.js.map +1 -0
  26. package/dist/commands/model.d.ts +7 -0
  27. package/dist/commands/model.d.ts.map +1 -0
  28. package/dist/commands/model.js +58 -0
  29. package/dist/commands/model.js.map +1 -0
  30. package/dist/commands/session.d.ts +10 -0
  31. package/dist/commands/session.d.ts.map +1 -0
  32. package/dist/commands/session.js +26 -0
  33. package/dist/commands/session.js.map +1 -0
  34. package/dist/commands/status.d.ts +8 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +24 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/commands/types.d.ts +12 -0
  39. package/dist/commands/types.d.ts.map +1 -0
  40. package/dist/commands/types.js +2 -0
  41. package/dist/commands/types.js.map +1 -0
  42. package/dist/config.d.ts +205 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +203 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/features/chat-ledger.d.ts +53 -0
  47. package/dist/features/chat-ledger.d.ts.map +1 -0
  48. package/dist/features/chat-ledger.js +185 -0
  49. package/dist/features/chat-ledger.js.map +1 -0
  50. package/dist/features/face-tags.d.ts +17 -0
  51. package/dist/features/face-tags.d.ts.map +1 -0
  52. package/dist/features/face-tags.js +79 -0
  53. package/dist/features/face-tags.js.map +1 -0
  54. package/dist/features/group-join-request.d.ts +65 -0
  55. package/dist/features/group-join-request.d.ts.map +1 -0
  56. package/dist/features/group-join-request.js +190 -0
  57. package/dist/features/group-join-request.js.map +1 -0
  58. package/dist/features/history-store.d.ts +8 -0
  59. package/dist/features/history-store.d.ts.map +1 -0
  60. package/dist/features/history-store.js +24 -0
  61. package/dist/features/history-store.js.map +1 -0
  62. package/dist/features/qq-approval.d.ts +78 -0
  63. package/dist/features/qq-approval.d.ts.map +1 -0
  64. package/dist/features/qq-approval.js +165 -0
  65. package/dist/features/qq-approval.js.map +1 -0
  66. package/dist/features/schedule-store.d.ts +62 -0
  67. package/dist/features/schedule-store.d.ts.map +1 -0
  68. package/dist/features/schedule-store.js +253 -0
  69. package/dist/features/schedule-store.js.map +1 -0
  70. package/dist/features/scheduler.d.ts +9 -0
  71. package/dist/features/scheduler.d.ts.map +1 -0
  72. package/dist/features/scheduler.js +146 -0
  73. package/dist/features/scheduler.js.map +1 -0
  74. package/dist/features/sticker-gate.d.ts +90 -0
  75. package/dist/features/sticker-gate.d.ts.map +1 -0
  76. package/dist/features/sticker-gate.js +391 -0
  77. package/dist/features/sticker-gate.js.map +1 -0
  78. package/dist/features/sticker-store.d.ts +158 -0
  79. package/dist/features/sticker-store.d.ts.map +1 -0
  80. package/dist/features/sticker-store.js +676 -0
  81. package/dist/features/sticker-store.js.map +1 -0
  82. package/dist/features/sticker-tagger.d.ts +10 -0
  83. package/dist/features/sticker-tagger.d.ts.map +1 -0
  84. package/dist/features/sticker-tagger.js +139 -0
  85. package/dist/features/sticker-tagger.js.map +1 -0
  86. package/dist/gateway/bootstrap.d.ts +12 -0
  87. package/dist/gateway/bootstrap.d.ts.map +1 -0
  88. package/dist/gateway/bootstrap.js +346 -0
  89. package/dist/gateway/bootstrap.js.map +1 -0
  90. package/dist/gateway/debounce.d.ts +40 -0
  91. package/dist/gateway/debounce.d.ts.map +1 -0
  92. package/dist/gateway/debounce.js +262 -0
  93. package/dist/gateway/debounce.js.map +1 -0
  94. package/dist/gateway/index.d.ts +3 -0
  95. package/dist/gateway/index.d.ts.map +1 -0
  96. package/dist/gateway/index.js +3 -0
  97. package/dist/gateway/index.js.map +1 -0
  98. package/dist/gateway/middleware-setup.d.ts +12 -0
  99. package/dist/gateway/middleware-setup.d.ts.map +1 -0
  100. package/dist/gateway/middleware-setup.js +156 -0
  101. package/dist/gateway/middleware-setup.js.map +1 -0
  102. package/dist/index.d.ts +14 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +187 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/middleware/attachment.d.ts +11 -0
  107. package/dist/middleware/attachment.d.ts.map +1 -0
  108. package/dist/middleware/attachment.js +17 -0
  109. package/dist/middleware/attachment.js.map +1 -0
  110. package/dist/middleware/media-history.d.ts +30 -0
  111. package/dist/middleware/media-history.d.ts.map +1 -0
  112. package/dist/middleware/media-history.js +54 -0
  113. package/dist/middleware/media-history.js.map +1 -0
  114. package/dist/middleware/sticker-capture.d.ts +17 -0
  115. package/dist/middleware/sticker-capture.d.ts.map +1 -0
  116. package/dist/middleware/sticker-capture.js +97 -0
  117. package/dist/middleware/sticker-capture.js.map +1 -0
  118. package/dist/model/index.d.ts +10 -0
  119. package/dist/model/index.d.ts.map +1 -0
  120. package/dist/model/index.js +9 -0
  121. package/dist/model/index.js.map +1 -0
  122. package/dist/model/model-resolver.d.ts +88 -0
  123. package/dist/model/model-resolver.d.ts.map +1 -0
  124. package/dist/model/model-resolver.js +151 -0
  125. package/dist/model/model-resolver.js.map +1 -0
  126. package/dist/model/prefs-store.d.ts +24 -0
  127. package/dist/model/prefs-store.d.ts.map +1 -0
  128. package/dist/model/prefs-store.js +126 -0
  129. package/dist/model/prefs-store.js.map +1 -0
  130. package/dist/model/settings-reader.d.ts +19 -0
  131. package/dist/model/settings-reader.d.ts.map +1 -0
  132. package/dist/model/settings-reader.js +76 -0
  133. package/dist/model/settings-reader.js.map +1 -0
  134. package/dist/model/types.d.ts +15 -0
  135. package/dist/model/types.d.ts.map +1 -0
  136. package/dist/model/types.js +5 -0
  137. package/dist/model/types.js.map +1 -0
  138. package/dist/session/idle-evictor.d.ts +18 -0
  139. package/dist/session/idle-evictor.d.ts.map +1 -0
  140. package/dist/session/idle-evictor.js +31 -0
  141. package/dist/session/idle-evictor.js.map +1 -0
  142. package/dist/session/index.d.ts +9 -0
  143. package/dist/session/index.d.ts.map +1 -0
  144. package/dist/session/index.js +8 -0
  145. package/dist/session/index.js.map +1 -0
  146. package/dist/session/session-manager.d.ts +97 -0
  147. package/dist/session/session-manager.d.ts.map +1 -0
  148. package/dist/session/session-manager.js +624 -0
  149. package/dist/session/session-manager.js.map +1 -0
  150. package/dist/session/types.d.ts +130 -0
  151. package/dist/session/types.d.ts.map +1 -0
  152. package/dist/session/types.js +2 -0
  153. package/dist/session/types.js.map +1 -0
  154. package/dist/session/workspace-attach.d.ts +22 -0
  155. package/dist/session/workspace-attach.d.ts.map +1 -0
  156. package/dist/session/workspace-attach.js +21 -0
  157. package/dist/session/workspace-attach.js.map +1 -0
  158. package/dist/setup.d.ts +25 -0
  159. package/dist/setup.d.ts.map +1 -0
  160. package/dist/setup.js +198 -0
  161. package/dist/setup.js.map +1 -0
  162. package/dist/shared/index.d.ts +7 -0
  163. package/dist/shared/index.d.ts.map +1 -0
  164. package/dist/shared/index.js +7 -0
  165. package/dist/shared/index.js.map +1 -0
  166. package/dist/shared/scope.d.ts +14 -0
  167. package/dist/shared/scope.d.ts.map +1 -0
  168. package/dist/shared/scope.js +12 -0
  169. package/dist/shared/scope.js.map +1 -0
  170. package/dist/shared/send-helper.d.ts +11 -0
  171. package/dist/shared/send-helper.d.ts.map +1 -0
  172. package/dist/shared/send-helper.js +13 -0
  173. package/dist/shared/send-helper.js.map +1 -0
  174. package/dist/shared/utils.d.ts +28 -0
  175. package/dist/shared/utils.d.ts.map +1 -0
  176. package/dist/shared/utils.js +143 -0
  177. package/dist/shared/utils.js.map +1 -0
  178. package/dist/transport/attachment.d.ts +18 -0
  179. package/dist/transport/attachment.d.ts.map +1 -0
  180. package/dist/transport/attachment.js +106 -0
  181. package/dist/transport/attachment.js.map +1 -0
  182. package/dist/transport/chunker.d.ts +14 -0
  183. package/dist/transport/chunker.d.ts.map +1 -0
  184. package/dist/transport/chunker.js +78 -0
  185. package/dist/transport/chunker.js.map +1 -0
  186. package/dist/transport/events.d.ts +80 -0
  187. package/dist/transport/events.d.ts.map +1 -0
  188. package/dist/transport/events.js +62 -0
  189. package/dist/transport/events.js.map +1 -0
  190. package/dist/transport/inbound.d.ts +8 -0
  191. package/dist/transport/inbound.d.ts.map +1 -0
  192. package/dist/transport/inbound.js +292 -0
  193. package/dist/transport/inbound.js.map +1 -0
  194. package/dist/transport/index.d.ts +10 -0
  195. package/dist/transport/index.d.ts.map +1 -0
  196. package/dist/transport/index.js +10 -0
  197. package/dist/transport/index.js.map +1 -0
  198. package/dist/transport/inject-rules.d.ts +24 -0
  199. package/dist/transport/inject-rules.d.ts.map +1 -0
  200. package/dist/transport/inject-rules.js +72 -0
  201. package/dist/transport/inject-rules.js.map +1 -0
  202. package/dist/transport/outbound-buffer.d.ts +56 -0
  203. package/dist/transport/outbound-buffer.d.ts.map +1 -0
  204. package/dist/transport/outbound-buffer.js +131 -0
  205. package/dist/transport/outbound-buffer.js.map +1 -0
  206. package/dist/transport/outbound.d.ts +30 -0
  207. package/dist/transport/outbound.d.ts.map +1 -0
  208. package/dist/transport/outbound.js +139 -0
  209. package/dist/transport/outbound.js.map +1 -0
  210. package/dist/transport/rich-media.d.ts +41 -0
  211. package/dist/transport/rich-media.d.ts.map +1 -0
  212. package/dist/transport/rich-media.js +104 -0
  213. package/dist/transport/rich-media.js.map +1 -0
  214. package/dist/transport/streaming-writer.d.ts +47 -0
  215. package/dist/transport/streaming-writer.d.ts.map +1 -0
  216. package/dist/transport/streaming-writer.js +110 -0
  217. package/dist/transport/streaming-writer.js.map +1 -0
  218. package/dist/transport/tool-presenter.d.ts +38 -0
  219. package/dist/transport/tool-presenter.d.ts.map +1 -0
  220. package/dist/transport/tool-presenter.js +121 -0
  221. package/dist/transport/tool-presenter.js.map +1 -0
  222. package/dist/types.d.ts +45 -0
  223. package/dist/types.d.ts.map +1 -0
  224. package/dist/types.js +5 -0
  225. package/dist/types.js.map +1 -0
  226. package/package.json +106 -0
  227. package/settings-host.js +927 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * 通用工具函数
3
+ *
4
+ * 纯函数与常量,供插件入口与其他模块复用。
5
+ */
6
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
7
+ import os from 'node:os';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { basename, dirname, join, resolve } from 'node:path';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+ const PLUGIN_ROOT = resolve(__dirname, '../..');
13
+ /** 插件包名(从 package.json 读取,随发布名自动对齐) */
14
+ const PLUGIN_NAME = readPluginName();
15
+ /** 插件版本号(从 package.json 读取) */
16
+ const PLUGIN_VERSION = readPluginVersion();
17
+ function readPluginName() {
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(resolve(PLUGIN_ROOT, 'package.json'), 'utf8'));
20
+ return pkg.name ?? '';
21
+ }
22
+ catch {
23
+ return '';
24
+ }
25
+ }
26
+ function readPluginVersion() {
27
+ try {
28
+ const pkgPath = resolve(PLUGIN_ROOT, 'package.json');
29
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
30
+ return pkg.version ?? '0.0.0';
31
+ }
32
+ catch {
33
+ return '0.0.0';
34
+ }
35
+ }
36
+ /**
37
+ * 构造 User-Agent 头
38
+ *
39
+ * 格式: dsh-qqbot/{version} (Node/{nodeVersion}; {platform})
40
+ */
41
+ export function buildUserAgent() {
42
+ return `dsh-qqbot/${PLUGIN_VERSION} (Node/${process.versions.node}; ${os.platform()})`;
43
+ }
44
+ /**
45
+ * 定位当前 dsh profile 目录(装本插件的那个 profile 根,如 …/profiles/web)
46
+ *
47
+ * 探测链(前两级对 pnpm link:/symlink/isolated 等任意安装形态都可靠,不依赖代码文件位置):
48
+ * 0) 环境变量 QQBOT_PROFILE_DIR 显式指定(安装脚本/高级用户);
49
+ * 1) 宿主扫描:遍历 ~/.dsh/profiles/<name>,找到「装着本插件」的 profile
50
+ * (node_modules/<scope>/<name> 存在,或 package.json 的 bundles/dependencies 提及本包名);
51
+ * 2) 兜底:从代码安装路径逐级向上找名为 node_modules 的目录并返回其父目录
52
+ * (仅实体安装/hoisted 布局有效)。
53
+ *
54
+ * @param baseDir - 起始目录,缺省为插件根(可注入便于测试)
55
+ */
56
+ export function getProfileDir(baseDir = PLUGIN_ROOT) {
57
+ // 0) 显式覆盖
58
+ const override = (process.env.QQBOT_PROFILE_DIR ?? '').trim();
59
+ if (override && existsSync(override))
60
+ return override;
61
+ // 1) 宿主扫描(link / symlink / isolated nodeLinker 都可靠)
62
+ const scanned = scanDshProfiles();
63
+ if (scanned)
64
+ return scanned;
65
+ // 2) 兜底:代码位置向上找 node_modules 的父目录
66
+ let dir = baseDir;
67
+ for (let i = 0; i < 32; i++) {
68
+ if (basename(dir) === 'node_modules')
69
+ return dirname(dir);
70
+ const parent = dirname(dir);
71
+ if (parent === dir)
72
+ break;
73
+ dir = parent;
74
+ }
75
+ return null;
76
+ }
77
+ /** 遍历 ~/.dsh/profiles/<name>,返回装着本插件的 profile 根目录(找不到返回 null) */
78
+ function scanDshProfiles() {
79
+ const profilesRoot = join(os.homedir(), '.dsh', 'profiles');
80
+ let entries;
81
+ try {
82
+ entries = readdirSync(profilesRoot);
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ for (const name of entries) {
88
+ const prof = resolve(profilesRoot, name);
89
+ try {
90
+ if (!statSync(prof).isDirectory())
91
+ continue;
92
+ }
93
+ catch {
94
+ continue;
95
+ }
96
+ // 判据 1: node_modules/<scope>/<pkg> 已安装(实体目录或 link 都能命中)
97
+ if (PLUGIN_NAME) {
98
+ const scope = PLUGIN_NAME.startsWith('@') ? PLUGIN_NAME.split('/')[0] : '';
99
+ const pkg = scope ? PLUGIN_NAME.slice(scope.length + 1) : PLUGIN_NAME;
100
+ const installed = scope
101
+ ? join(prof, 'node_modules', scope, pkg)
102
+ : join(prof, 'node_modules', pkg);
103
+ if (existsSync(installed))
104
+ return prof;
105
+ }
106
+ // 判据 2: package.json 的 bundles / dependencies 提及本包名(尚未实体安装时)
107
+ try {
108
+ const pkg = JSON.parse(readFileSync(join(prof, 'package.json'), 'utf8'));
109
+ const blob = JSON.stringify(pkg.dsh?.profile?.bundles ?? []) + JSON.stringify(pkg.dependencies ?? {});
110
+ if (PLUGIN_NAME && blob.includes(PLUGIN_NAME))
111
+ return prof;
112
+ }
113
+ catch {
114
+ /* 无 package.json 的目录跳过 */
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ /**
120
+ * 解析环境变量占位配置
121
+ */
122
+ export function resolveEnv(configValue, envKey) {
123
+ if (configValue && configValue !== '__FROM_ENV__' && !configValue.startsWith('process.env')) {
124
+ return configValue;
125
+ }
126
+ return process.env[envKey] ?? '';
127
+ }
128
+ /**
129
+ * 格式化相对时间
130
+ */
131
+ export function formatRelativeTime(ts) {
132
+ if (!ts)
133
+ return '—';
134
+ const diff = Date.now() - ts;
135
+ if (diff < 60_000)
136
+ return `${Math.floor(diff / 1000)}s 前`;
137
+ if (diff < 3_600_000)
138
+ return `${Math.floor(diff / 60_000)}m 前`;
139
+ if (diff < 86_400_000)
140
+ return `${Math.floor(diff / 3_600_000)}h 前`;
141
+ return `${Math.floor(diff / 86_400_000)}d 前`;
142
+ }
143
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Logger, RawAttachment } from '../types.js';
2
+ /** 下载结果 */
3
+ export interface DownloadedFile {
4
+ /** 原始文件名(用于与消息附件关联) */
5
+ filename: string;
6
+ /** 本地绝对路径 */
7
+ localPath: string;
8
+ /** 相对 cwd 的显示路径(@提及 / 工具访问用) */
9
+ displayPath: string;
10
+ }
11
+ /**
12
+ * 下载消息中的 file 附件到本地
13
+ *
14
+ * 下载目录为 {cwd}/.qqbot/{messageId},用 messageId 隔离跨消息重名。
15
+ * 下载失败不返回(由调用方回退描述),下载成功仅保留路径供模型用工具读取。
16
+ */
17
+ export declare function downloadFileAttachments(attachments: RawAttachment[] | undefined, cwd: string, messageId: string, logger: Logger): Promise<DownloadedFile[]>;
18
+ //# sourceMappingURL=attachment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/transport/attachment.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAOzD,WAAW;AACX,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB;AAkED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,aAAa,EAAE,GAAG,SAAS,EACxC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,cAAc,EAAE,CAAC,CAgC3B"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 附件下载 — 将 QQ 文件附件安全下载到本地
3
+ *
4
+ * 下载后仅通过 @路径 提示模型,由模型通过 tool-bash / tool-fs 自行读取,
5
+ * 不内联内容(避免 token 浪费与上下文污染)。
6
+ */
7
+ import { mkdirSync, writeFileSync } from 'node:fs';
8
+ import { basename, join } from 'node:path';
9
+ import * as dns from 'node:dns';
10
+ /** 下载大小上限(超过则跳过下载,仅保留路径提示) */
11
+ const MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;
12
+ /** 下载超时(毫秒) */
13
+ const DOWNLOAD_TIMEOUT_MS = 15_000;
14
+ /** 文件名净化:去路径、过滤危险字符,防止路径穿越 */
15
+ function sanitizeFilename(name) {
16
+ const base = basename(name.replace(/\\/g, '/'));
17
+ const safe = base.replace(/[^a-zA-Z0-9._-]/g, '_');
18
+ return safe || 'attachment';
19
+ }
20
+ /** 字节数格式化 */
21
+ function formatSize(bytes) {
22
+ if (bytes >= 1024 * 1024)
23
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
24
+ if (bytes >= 1024)
25
+ return `${(bytes / 1024).toFixed(1)}KB`;
26
+ return `${bytes}B`;
27
+ }
28
+ // ── SSRF 防护:阻断解析到私有/保留地址的下载 ──
29
+ const PRIVATE_RANGES = [
30
+ [0x0a000000n, 8], // 10.0.0.0/8
31
+ [0xac100000n, 12], // 172.16.0.0/12
32
+ [0xc0a80000n, 16], // 192.168.0.0/16
33
+ [0x7f000000n, 8], // 127.0.0.0/8
34
+ [0xa9fe0000n, 16], // 169.254.0.0/16
35
+ [0xe0000000n, 4], // 224.0.0.0/4 (multicast)
36
+ ];
37
+ function ipToBigInt(ip) {
38
+ return ip.split('.').reduce((acc, octet) => (acc << 8n) | BigInt(Number(octet)), 0n);
39
+ }
40
+ function isPrivateIP(ip) {
41
+ const val = ipToBigInt(ip);
42
+ return PRIVATE_RANGES.some(([mask, prefix]) => (val >> (32n - BigInt(prefix))) === (mask >> (32n - BigInt(prefix))));
43
+ }
44
+ /** 解析 hostname 并阻断指向私有/内网地址的 URL(SSRF 防护) */
45
+ async function assertSafeHostname(hostname) {
46
+ const addresses = await dns.promises.resolve4(hostname).catch(() => []);
47
+ if (addresses.length === 0)
48
+ throw new Error(`DNS resolution failed: ${hostname}`);
49
+ for (const addr of addresses) {
50
+ if (isPrivateIP(addr))
51
+ throw new Error(`SSRF blocked: ${hostname} resolves to private IP ${addr}`);
52
+ }
53
+ }
54
+ /** 安全下载:仅 HTTPS + SSRF 防护 + 大小上限 + 超时,返回下载字节数 */
55
+ async function download(url, destPath, maxBytes) {
56
+ const parsed = new URL(url);
57
+ if (parsed.protocol !== 'https:') {
58
+ throw new Error(`Only HTTPS allowed: ${parsed.protocol}`);
59
+ }
60
+ await assertSafeHostname(parsed.hostname);
61
+ const resp = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
62
+ if (!resp.ok)
63
+ throw new Error(`HTTP ${resp.status}`);
64
+ const buf = Buffer.from(await resp.arrayBuffer());
65
+ if (buf.length > maxBytes) {
66
+ throw new Error(`Download exceeds ${Math.floor(maxBytes / 1024 / 1024)}MB`);
67
+ }
68
+ writeFileSync(destPath, buf);
69
+ return buf.length;
70
+ }
71
+ /**
72
+ * 下载消息中的 file 附件到本地
73
+ *
74
+ * 下载目录为 {cwd}/.qqbot/{messageId},用 messageId 隔离跨消息重名。
75
+ * 下载失败不返回(由调用方回退描述),下载成功仅保留路径供模型用工具读取。
76
+ */
77
+ export async function downloadFileAttachments(attachments, cwd, messageId, logger) {
78
+ const files = (attachments ?? []).filter(a => a.content_type === 'file' && a.url);
79
+ if (files.length === 0)
80
+ return [];
81
+ const dir = join(cwd, '.qqbot', messageId);
82
+ mkdirSync(dir, { recursive: true });
83
+ const results = [];
84
+ for (const file of files) {
85
+ const safeName = sanitizeFilename(file.filename);
86
+ const localPath = join(dir, safeName);
87
+ const displayPath = `.qqbot/${messageId}/${safeName}`;
88
+ if (file.size > MAX_DOWNLOAD_BYTES) {
89
+ logger.debug(`im-qqbot: skip download (${file.size}B too large): ${file.filename}`);
90
+ results.push({ filename: file.filename, localPath, displayPath });
91
+ continue;
92
+ }
93
+ let bytes;
94
+ try {
95
+ bytes = await download(file.url, localPath, MAX_DOWNLOAD_BYTES);
96
+ }
97
+ catch (err) {
98
+ logger.warn(`im-qqbot: download failed: ${file.filename} — ${err instanceof Error ? err.message : String(err)}`);
99
+ continue; // 下载失败不加入结果,由 buildDynamicCtx 回退为描述
100
+ }
101
+ logger.debug(`im-qqbot: attachment downloaded: ${file.filename} (${formatSize(bytes)}) → ${displayPath}`);
102
+ results.push({ filename: file.filename, localPath, displayPath });
103
+ }
104
+ return results;
105
+ }
106
+ //# sourceMappingURL=attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/transport/attachment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Markdown 文本切分器
3
+ *
4
+ * QQ 单条消息有字符数限制(约 5000),需要在合适边界切分,
5
+ * 并保持 GFM 表格、代码块的完整性。
6
+ */
7
+ /**
8
+ * 按换行边界切分 Markdown 文本
9
+ * - 不在代码块中间断开
10
+ * - 不在 GFM 表格中间断开
11
+ * - 优先在空行处断开
12
+ */
13
+ export declare function chunkMarkdownText(text: string, limit: number): string[];
14
+ //# sourceMappingURL=chunker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunker.d.ts","sourceRoot":"","sources":["../../src/transport/chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAoEvE"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Markdown 文本切分器
3
+ *
4
+ * QQ 单条消息有字符数限制(约 5000),需要在合适边界切分,
5
+ * 并保持 GFM 表格、代码块的完整性。
6
+ */
7
+ const CODE_FENCE_OPEN = /^```/;
8
+ const GFM_TABLE_LINE = /^\|.+\|$/;
9
+ /**
10
+ * 按换行边界切分 Markdown 文本
11
+ * - 不在代码块中间断开
12
+ * - 不在 GFM 表格中间断开
13
+ * - 优先在空行处断开
14
+ */
15
+ export function chunkMarkdownText(text, limit) {
16
+ if (text.length <= limit)
17
+ return [text];
18
+ const lines = text.split('\n');
19
+ const chunks = [];
20
+ let current = '';
21
+ let inCodeBlock = false;
22
+ let tableBuffer = [];
23
+ const flushTable = () => {
24
+ if (tableBuffer.length === 0)
25
+ return;
26
+ const block = tableBuffer.join('\n');
27
+ tableBuffer = [];
28
+ if (!current) {
29
+ current = block;
30
+ return;
31
+ }
32
+ const candidate = `${current}\n${block}`;
33
+ if (candidate.length > limit) {
34
+ chunks.push(current);
35
+ current = block;
36
+ }
37
+ else {
38
+ current = candidate;
39
+ }
40
+ };
41
+ const appendLine = (line) => {
42
+ if (!current) {
43
+ current = line;
44
+ return;
45
+ }
46
+ const candidate = `${current}\n${line}`;
47
+ if (candidate.length > limit) {
48
+ chunks.push(current);
49
+ current = line;
50
+ }
51
+ else {
52
+ current = candidate;
53
+ }
54
+ };
55
+ for (const line of lines) {
56
+ if (CODE_FENCE_OPEN.test(line)) {
57
+ flushTable();
58
+ inCodeBlock = !inCodeBlock;
59
+ appendLine(line);
60
+ continue;
61
+ }
62
+ if (inCodeBlock) {
63
+ appendLine(line);
64
+ continue;
65
+ }
66
+ if (GFM_TABLE_LINE.test(line)) {
67
+ tableBuffer.push(line);
68
+ continue;
69
+ }
70
+ flushTable();
71
+ appendLine(line);
72
+ }
73
+ flushTable();
74
+ if (current)
75
+ chunks.push(current);
76
+ return chunks.length > 0 ? chunks : [text];
77
+ }
78
+ //# sourceMappingURL=chunker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunker.js","sourceRoot":"","sources":["../../src/transport/chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAa;IAC3D,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,WAAW,GAAa,EAAE,CAAC;IAE/B,MAAM,UAAU,GAAG,GAAS,EAAE;QAC5B,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACrC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,WAAW,GAAG,EAAE,CAAC;QAEjB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,KAAK,CAAC;YAChB,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,OAAO,KAAK,KAAK,EAAE,CAAC;QACzC,IAAI,SAAS,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAQ,EAAE;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,IAAI,CAAC;YACf,OAAO;QACT,CAAC;QACD,MAAM,SAAS,GAAG,GAAG,OAAO,KAAK,IAAI,EAAE,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,UAAU,EAAE,CAAC;YACb,WAAW,GAAG,CAAC,WAAW,CAAC;YAC3B,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS;QACX,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YAChB,UAAU,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS;QACX,CAAC;QAED,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QAED,UAAU,EAAE,CAAC;QACb,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,UAAU,EAAE,CAAC;IACb,IAAI,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * 出站事件解析 — 将 dsh session/event 归一化为强类型
3
+ *
4
+ * 原始事件(ctx.on('session/event') 回调参数)是弱类型:
5
+ * { type: string, data: Record<string, unknown> }
6
+ * 这里把可识别的事件解析为 discriminated union,未知类型或字段缺失返回 undefined。
7
+ */
8
+ /** 原始 dsh 事件(弱类型,来自 ctx.on('session/event')) */
9
+ export interface RawSessionEvent {
10
+ type: string;
11
+ data: Record<string, unknown>;
12
+ }
13
+ /** turn/end 事件的 reason 结构(兼容 error/failure/顶层 message 三种形态) */
14
+ export interface TurnEndReason {
15
+ kind?: string;
16
+ error?: {
17
+ message?: string;
18
+ code?: string;
19
+ };
20
+ failure?: {
21
+ message?: string;
22
+ code?: string;
23
+ };
24
+ message?: string;
25
+ code?: string;
26
+ }
27
+ /** assistant/chunk 事件(仅保留 text-delta) */
28
+ export interface ChunkEvent {
29
+ type: 'assistant/chunk';
30
+ text: string;
31
+ }
32
+ /** assistant/message 事件 */
33
+ export interface MessageEvent {
34
+ type: 'assistant/message';
35
+ content: Array<{
36
+ type: string;
37
+ text?: string;
38
+ }>;
39
+ }
40
+ /** tool/call 事件 */
41
+ export interface ToolCallEvent {
42
+ type: 'tool/call';
43
+ callId: string;
44
+ name: string;
45
+ arguments: string;
46
+ }
47
+ /** tool/result 事件 */
48
+ export interface ToolResultEvent {
49
+ type: 'tool/result';
50
+ callId: string;
51
+ error?: {
52
+ name: string;
53
+ code: string;
54
+ };
55
+ /** 原始 data 透传给 presenter 做结构化展示 */
56
+ raw: Record<string, unknown>;
57
+ }
58
+ /** turn/end 事件 */
59
+ export interface TurnEndEvent {
60
+ type: 'turn/end';
61
+ reason: TurnEndReason;
62
+ }
63
+ /** 出站事件联合类型 */
64
+ export type OutboundEvent = ChunkEvent | MessageEvent | ToolCallEvent | ToolResultEvent | TurnEndEvent;
65
+ /**
66
+ * 解析原始事件为强类型
67
+ *
68
+ * 未知类型或缺失关键字段时返回 undefined,由调用方直接跳过。
69
+ */
70
+ export declare function parseEvent(raw: RawSessionEvent): OutboundEvent | undefined;
71
+ /**
72
+ * 从 turn/end reason 提取错误信息
73
+ *
74
+ * 兼容三种形态:error(新格式)、failure(旧格式)、顶层 message/code(最旧格式)。
75
+ */
76
+ export declare function extractTurnError(reason: TurnEndReason): {
77
+ code: string;
78
+ message: string;
79
+ } | undefined;
80
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/transport/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yCAAyC;AACzC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2BAA2B;AAC3B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjD;AAED,mBAAmB;AACnB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAqB;AACrB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,kBAAkB;AAClB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,eAAe;AACf,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,YAAY,GACZ,aAAa,GACb,eAAe,GACf,YAAY,CAAC;AAEjB;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS,CAmC1E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAQrG"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 出站事件解析 — 将 dsh session/event 归一化为强类型
3
+ *
4
+ * 原始事件(ctx.on('session/event') 回调参数)是弱类型:
5
+ * { type: string, data: Record<string, unknown> }
6
+ * 这里把可识别的事件解析为 discriminated union,未知类型或字段缺失返回 undefined。
7
+ */
8
+ /**
9
+ * 解析原始事件为强类型
10
+ *
11
+ * 未知类型或缺失关键字段时返回 undefined,由调用方直接跳过。
12
+ */
13
+ export function parseEvent(raw) {
14
+ switch (raw.type) {
15
+ case 'assistant/chunk': {
16
+ const chunk = raw.data.chunk;
17
+ if (chunk === undefined || chunk.type !== 'text-delta' || !chunk.text)
18
+ return undefined;
19
+ return { type: 'assistant/chunk', text: chunk.text };
20
+ }
21
+ case 'assistant/message': {
22
+ const message = raw.data.message;
23
+ if (message === undefined || !Array.isArray(message.content))
24
+ return undefined;
25
+ return { type: 'assistant/message', content: message.content };
26
+ }
27
+ case 'tool/call': {
28
+ const data = raw.data;
29
+ if (!data.callId || !data.name)
30
+ return undefined;
31
+ return { type: 'tool/call', callId: data.callId, name: data.name, arguments: data.arguments ?? '' };
32
+ }
33
+ case 'tool/result': {
34
+ const data = raw.data;
35
+ const callId = data.message?.source?.callId;
36
+ if (!callId)
37
+ return undefined;
38
+ return { type: 'tool/result', callId, error: data.error, raw: raw.data };
39
+ }
40
+ case 'turn/end': {
41
+ const reason = raw.data.reason ?? {};
42
+ return { type: 'turn/end', reason };
43
+ }
44
+ default:
45
+ return undefined;
46
+ }
47
+ }
48
+ /**
49
+ * 从 turn/end reason 提取错误信息
50
+ *
51
+ * 兼容三种形态:error(新格式)、failure(旧格式)、顶层 message/code(最旧格式)。
52
+ */
53
+ export function extractTurnError(reason) {
54
+ if (reason.kind !== 'error')
55
+ return undefined;
56
+ const detail = reason.error ?? reason.failure;
57
+ return {
58
+ code: detail?.code ?? reason.code ?? 'UNKNOWN',
59
+ message: detail?.message ?? reason.message ?? 'unknown error',
60
+ };
61
+ }
62
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/transport/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4DH;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,GAAoB;IAC7C,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,iBAAiB,CAAC,CAAC,CAAC;YACvB,MAAM,KAAK,GAAI,GAAG,CAAC,IAAqD,CAAC,KAAK,CAAC;YAC/E,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,KAAK,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YACxF,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QACvD,CAAC;QAED,KAAK,mBAAmB,CAAC,CAAC,CAAC;YACzB,MAAM,OAAO,GAAI,GAAG,CAAC,IAA2E,CAAC,OAAO,CAAC;YACzG,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;gBAAE,OAAO,SAAS,CAAC;YAC/E,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACjE,CAAC;QAED,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,IAAI,GAAG,GAAG,CAAC,IAA8D,CAAC;YAChF,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YACjD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QACtG,CAAC;QAED,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,IAAI,GAAG,GAAG,CAAC,IAA8F,CAAC;YAChH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC;YAC5C,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YAC9B,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3E,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,MAAM,GAAI,GAAG,CAAC,IAAmC,CAAC,MAAM,IAAI,EAAE,CAAC;YACrE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QACtC,CAAC;QAED;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAqB;IACpD,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAE9C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC;IAC9C,OAAO;QACL,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,SAAS;QAC9C,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,MAAM,CAAC,OAAO,IAAI,eAAe;KAC9D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { SessionManager } from '../session/index.js';
2
+ import type { ImQQBotConfig } from '../config.js';
3
+ import type { Logger } from '../types.js';
4
+ /**
5
+ * 处理 QQ 入站消息(已经过 SDK 中间件链)
6
+ */
7
+ export declare function handleInbound(rawMsg: unknown, manager: SessionManager, config: ImQQBotConfig, logger: Logger, state?: Record<string, unknown>): Promise<void>;
8
+ //# sourceMappingURL=inbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../src/transport/inbound.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAa,MAAM,EAA8B,MAAM,aAAa,CAAC;AAkEjF;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgFf"}