@tencent-connect/dsh-qqbot 0.4.0 → 0.5.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/.orange-ci.yml +2 -68
- package/CHANGELOG.md +45 -0
- package/CHANGELOG_EN.md +84 -0
- package/README.md +107 -17
- package/README_EN.md +107 -17
- package/cordis.patch.yml +1 -1
- package/cordis.yml +46 -3
- package/dist/commands/help.d.ts +6 -7
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +23 -7
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/index.d.ts +7 -5
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +12 -12
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/misc.d.ts +10 -8
- package/dist/commands/misc.d.ts.map +1 -1
- package/dist/commands/misc.js +45 -8
- package/dist/commands/misc.js.map +1 -1
- package/dist/commands/model.d.ts +3 -4
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +8 -7
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/preset.d.ts +8 -0
- package/dist/commands/preset.d.ts.map +1 -0
- package/dist/commands/preset.js +52 -0
- package/dist/commands/preset.js.map +1 -0
- package/dist/commands/session.d.ts +11 -7
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +29 -13
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts +3 -4
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts +9 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -1
- package/dist/config.js.map +1 -1
- package/dist/features/answer-parser.d.ts +9 -0
- package/dist/features/answer-parser.d.ts.map +1 -0
- package/dist/features/answer-parser.js +23 -0
- package/dist/features/answer-parser.js.map +1 -0
- package/dist/features/approval-channel.d.ts +76 -0
- package/dist/features/approval-channel.d.ts.map +1 -0
- package/dist/features/approval-channel.js +142 -0
- package/dist/features/approval-channel.js.map +1 -0
- package/dist/features/approval-renderer.d.ts +18 -0
- package/dist/features/approval-renderer.d.ts.map +1 -0
- package/dist/features/approval-renderer.js +52 -0
- package/dist/features/approval-renderer.js.map +1 -0
- package/dist/features/button-utils.d.ts +33 -0
- package/dist/features/button-utils.d.ts.map +1 -0
- package/dist/features/button-utils.js +27 -0
- package/dist/features/button-utils.js.map +1 -0
- package/dist/features/history-store.d.ts.map +1 -1
- package/dist/features/history-store.js +0 -1
- package/dist/features/history-store.js.map +1 -1
- package/dist/features/question-channel.d.ts +96 -0
- package/dist/features/question-channel.d.ts.map +1 -0
- package/dist/features/question-channel.js +234 -0
- package/dist/features/question-channel.js.map +1 -0
- package/dist/features/question-renderer.d.ts +20 -0
- package/dist/features/question-renderer.d.ts.map +1 -0
- package/dist/features/question-renderer.js +70 -0
- package/dist/features/question-renderer.js.map +1 -0
- package/dist/gateway/bootstrap.d.ts +3 -3
- package/dist/gateway/bootstrap.d.ts.map +1 -1
- package/dist/gateway/bootstrap.js +74 -8
- package/dist/gateway/bootstrap.js.map +1 -1
- package/dist/gateway/index.d.ts +2 -2
- package/dist/gateway/index.js +2 -2
- package/dist/gateway/middleware-setup.d.ts +3 -3
- package/dist/gateway/middleware-setup.d.ts.map +1 -1
- package/dist/gateway/middleware-setup.js +9 -7
- package/dist/gateway/middleware-setup.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/media/media-cleaner.d.ts +19 -0
- package/dist/media/media-cleaner.d.ts.map +1 -0
- package/dist/media/media-cleaner.js +81 -0
- package/dist/media/media-cleaner.js.map +1 -0
- package/dist/media/send-file-tool.d.ts +35 -0
- package/dist/media/send-file-tool.d.ts.map +1 -0
- package/dist/media/send-file-tool.js +150 -0
- package/dist/media/send-file-tool.js.map +1 -0
- package/dist/media/vision-tool.d.ts +16 -0
- package/dist/media/vision-tool.d.ts.map +1 -0
- package/dist/media/vision-tool.js +241 -0
- package/dist/media/vision-tool.js.map +1 -0
- package/dist/middleware/attachment.d.ts +3 -3
- package/dist/middleware/attachment.d.ts.map +1 -1
- package/dist/middleware/attachment.js +17 -4
- package/dist/middleware/attachment.js.map +1 -1
- package/dist/middleware/question-answer.d.ts +10 -0
- package/dist/middleware/question-answer.d.ts.map +1 -0
- package/dist/middleware/question-answer.js +19 -0
- package/dist/middleware/question-answer.js.map +1 -0
- package/dist/model/index.d.ts +4 -4
- package/dist/model/index.js +3 -3
- package/dist/model/model-resolver.d.ts +27 -13
- package/dist/model/model-resolver.d.ts.map +1 -1
- package/dist/model/model-resolver.js +82 -27
- package/dist/model/model-resolver.js.map +1 -1
- package/dist/model/prefs-store.d.ts +6 -1
- package/dist/model/prefs-store.d.ts.map +1 -1
- package/dist/model/prefs-store.js +25 -1
- package/dist/model/prefs-store.js.map +1 -1
- package/dist/model/settings-reader.d.ts +1 -1
- package/dist/session/idle-evictor.d.ts +2 -2
- package/dist/session/idle-evictor.d.ts.map +1 -1
- package/dist/session/idle-evictor.js +1 -1
- package/dist/session/idle-evictor.js.map +1 -1
- package/dist/session/index.d.ts +3 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -2
- package/dist/session/session-manager.d.ts +67 -9
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +216 -20
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +47 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/shared/index.d.ts +3 -3
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +3 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/send-helper.js +1 -1
- package/dist/shared/utils.d.ts +2 -0
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +2 -2
- package/dist/shared/utils.js.map +1 -1
- package/dist/transport/attachment.d.ts +18 -7
- package/dist/transport/attachment.d.ts.map +1 -1
- package/dist/transport/attachment.js +77 -30
- package/dist/transport/attachment.js.map +1 -1
- package/dist/transport/inbound.d.ts +5 -4
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +120 -80
- package/dist/transport/inbound.js.map +1 -1
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +4 -4
- package/dist/transport/msgid-cache.d.ts +23 -0
- package/dist/transport/msgid-cache.d.ts.map +1 -0
- package/dist/transport/msgid-cache.js +57 -0
- package/dist/transport/msgid-cache.js.map +1 -0
- package/dist/transport/outbound-buffer.d.ts +9 -6
- package/dist/transport/outbound-buffer.d.ts.map +1 -1
- package/dist/transport/outbound-buffer.js +5 -5
- package/dist/transport/outbound-buffer.js.map +1 -1
- package/dist/transport/outbound.d.ts +9 -9
- package/dist/transport/outbound.d.ts.map +1 -1
- package/dist/transport/outbound.js +7 -7
- package/dist/transport/outbound.js.map +1 -1
- package/dist/transport/reply-limiter.d.ts +31 -0
- package/dist/transport/reply-limiter.d.ts.map +1 -0
- package/dist/transport/reply-limiter.js +50 -0
- package/dist/transport/reply-limiter.js.map +1 -0
- package/dist/transport/reply-target.d.ts +29 -0
- package/dist/transport/reply-target.d.ts.map +1 -0
- package/dist/transport/reply-target.js +55 -0
- package/dist/transport/reply-target.js.map +1 -0
- package/dist/transport/streaming-writer.d.ts +3 -3
- package/dist/transport/streaming-writer.d.ts.map +1 -1
- package/dist/transport/streaming-writer.js +1 -1
- package/dist/transport/streaming-writer.js.map +1 -1
- package/dist/transport/tool-presenter.d.ts +0 -1
- package/dist/transport/tool-presenter.d.ts.map +1 -1
- package/dist/transport/tool-presenter.js +0 -1
- package/dist/transport/tool-presenter.js.map +1 -1
- package/dist/types.d.ts +9 -15
- package/dist/types.d.ts.map +1 -1
- package/docs/assets/qqchannel.jpg +0 -0
- package/docs/assets/qqgroup.jpg +0 -0
- package/docs/assets/qrcode.png +0 -0
- package/package.json +21 -4
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* 中间件负责过滤与上下文富化;最终消息统一由 bot.on('message') 处理转发。
|
|
6
6
|
*/
|
|
7
7
|
import type { QQBot } from '@tencent-connect/qqbot-nodejs';
|
|
8
|
-
import type { ImQQBotConfig } from '../config.
|
|
9
|
-
import type { SessionManager } from '../session/index.
|
|
10
|
-
import type { Logger } from '../types.
|
|
8
|
+
import type { ImQQBotConfig } from '../config.ts';
|
|
9
|
+
import type { SessionManager } from '../session/index.ts';
|
|
10
|
+
import type { Logger } from '../types.ts';
|
|
11
11
|
export declare function setupMiddlewares(bot: QQBot, config: ImQQBotConfig, manager: SessionManager, logger: Logger): void;
|
|
12
12
|
//# sourceMappingURL=middleware-setup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware-setup.d.ts","sourceRoot":"","sources":["../../src/gateway/middleware-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"middleware-setup.d.ts","sourceRoot":"","sources":["../../src/gateway/middleware-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAe3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM1C,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,KAAK,EACV,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,MAAM,GACb,IAAI,CAuFN"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { errorHandler, messageFilter, accessPolicy, mentionGate, contentSanitizer, rateLimiter, slashCommand, concurrencyGuard, typingIndicator, quoteRef, historyBuffer, envelopeFormatter, } from '@tencent-connect/qqbot-nodejs';
|
|
2
|
-
import { buildCommandList } from
|
|
3
|
-
import { attachmentProcessor } from
|
|
4
|
-
import {
|
|
2
|
+
import { buildCommandList } from "../commands/index.js";
|
|
3
|
+
import { attachmentProcessor } from "../middleware/attachment.js";
|
|
4
|
+
import { questionAnswer } from "../middleware/question-answer.js";
|
|
5
|
+
import { getHistoryStore, historyGroupKey } from "../features/history-store.js";
|
|
5
6
|
export function setupMiddlewares(bot, config, manager, logger) {
|
|
6
7
|
// 1. 错误兜底(最外层洋葱皮)
|
|
7
8
|
bot.use(errorHandler());
|
|
@@ -48,18 +49,19 @@ export function setupMiddlewares(bot, config, manager, logger) {
|
|
|
48
49
|
bot.use(rateLimiter());
|
|
49
50
|
// 8. 斜杠命令(在 concurrencyGuard 之前,命令匹配后不排队直接响应)
|
|
50
51
|
const slash = slashCommand({
|
|
51
|
-
autoHelp
|
|
52
|
+
// 关闭 autoHelp:help 命令由 qqbot 注册为 /bot-help,
|
|
53
|
+
// 避免 SDK 再自动注册一个无前缀的 /help 造成重复
|
|
54
|
+
autoHelp: false,
|
|
52
55
|
commands: buildCommandList({ manager, config }),
|
|
53
56
|
});
|
|
54
57
|
bot.use(slash.middleware);
|
|
58
|
+
// 8.5. 待答问题截获(在 concurrencyGuard 之前,答案消息不排队直接作答)
|
|
59
|
+
bot.use(questionAnswer(manager));
|
|
55
60
|
// 9. 并发串行 + 消息合并(同 peer 排队,避免 session 冲突)
|
|
56
61
|
bot.use(concurrencyGuard({
|
|
57
62
|
strategy: 'merge',
|
|
58
63
|
maxQueue: config.maxQueue,
|
|
59
64
|
maxProcessingMs: config.processingTimeoutMs,
|
|
60
|
-
urgentPredicate: (mCtx) => {
|
|
61
|
-
return (mCtx.message.content ?? '').trim() === '/bot-stop';
|
|
62
|
-
},
|
|
63
65
|
}));
|
|
64
66
|
// 10. C2C 输入状态指示
|
|
65
67
|
bot.use(typingIndicator());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware-setup.js","sourceRoot":"","sources":["../../src/gateway/middleware-setup.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,aAAa,EACb,iBAAiB,GAClB,MAAM,+BAA+B,CAAC;AAIvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEhF,MAAM,UAAU,gBAAgB,CAC9B,GAAU,EACV,MAAqB,EACrB,OAAuB,EACvB,MAAc;IAEd,kBAAkB;IAClB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IAExB,wBAAwB;IACxB,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAEhD,qBAAqB;IACrB,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;QACnB,GAAG,EAAE;YACH,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO;YAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;SAC1E;QACD,KAAK,EAAE;YACL,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS;YAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;SAC9E;QACD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACzB,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,0CAA0C;IAC1C,6DAA6D;IAC7D,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC,YAAY;QAC1B,KAAK,EAAE,eAAe,EAAE;QACxB,YAAY,EAAE,IAAI;QAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC;YACpC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,GAAG;gBAAE,OAAO,SAAS,CAAC;YAC3D,OAAO,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5C,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,gBAAgB;IAChB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;QAClB,qBAAqB,EAAE,MAAM,CAAC,cAAc;KAC7C,CAAC,CAAC,CAAC;IAEJ,+BAA+B;IAC/B,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC;QACvB,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC,CAAC;IAEJ,mCAAmC;IACnC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IAEvB,8CAA8C;IAC9C,MAAM,KAAK,GAAG,YAAY,CAAC;QACzB,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"middleware-setup.js","sourceRoot":"","sources":["../../src/gateway/middleware-setup.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,aAAa,EACb,iBAAiB,GAClB,MAAM,+BAA+B,CAAC;AAIvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEhF,MAAM,UAAU,gBAAgB,CAC9B,GAAU,EACV,MAAqB,EACrB,OAAuB,EACvB,MAAc;IAEd,kBAAkB;IAClB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IAExB,wBAAwB;IACxB,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAEhD,qBAAqB;IACrB,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;QACnB,GAAG,EAAE;YACH,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO;YAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;SAC1E;QACD,KAAK,EAAE;YACL,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS;YAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;SAC9E;QACD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YACzB,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,0CAA0C;IAC1C,6DAA6D;IAC7D,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC,YAAY;QAC1B,KAAK,EAAE,eAAe,EAAE;QACxB,YAAY,EAAE,IAAI;QAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;YAChB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC;YACpC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,GAAG;gBAAE,OAAO,SAAS,CAAC;YAC3D,OAAO,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5C,CAAC;KACF,CAAC,CAAC,CAAC;IAEJ,gBAAgB;IAChB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC;QAClB,qBAAqB,EAAE,MAAM,CAAC,cAAc;KAC7C,CAAC,CAAC,CAAC;IAEJ,+BAA+B;IAC/B,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC;QACvB,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC,CAAC;IAEJ,mCAAmC;IACnC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IAEvB,8CAA8C;IAC9C,MAAM,KAAK,GAAG,YAAY,CAAC;QACzB,4CAA4C;QAC5C,gCAAgC;QAChC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;KAChD,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAE1B,iDAAiD;IACjD,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAEjC,0CAA0C;IAC1C,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC;QACvB,QAAQ,EAAE,OAAO;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,eAAe,EAAE,MAAM,CAAC,mBAAmB;KAC5C,CAAC,CAAC,CAAC;IAEJ,iBAAiB;IACjB,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC;IAE3B,2BAA2B;IAC3B,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;QACf,OAAO,EAAE,GAAG;QACZ,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAC,CAAC;IAEJ,wCAAwC;IACxC,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAE7C,oDAAoD;IACpD,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC,CAAC;AACN,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* 网关组装(中间件编排 + 事件 + 出站 + 生命周期)见 src/gateway/。
|
|
6
6
|
*/
|
|
7
7
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
-
import { type ImQQBotConfig } from './config.
|
|
8
|
+
import { type ImQQBotConfig } from './config.ts';
|
|
9
9
|
export declare const name = "im-qqbot";
|
|
10
10
|
export declare const inject: string[];
|
|
11
11
|
export declare const Config: import("@deepseek-ai/schemastery").default<ImQQBotConfig>;
|
|
12
|
-
export type { ImQQBotConfig } from './config.
|
|
12
|
+
export type { ImQQBotConfig } from './config.ts';
|
|
13
13
|
export declare function apply(ctx: Context, config: ImQQBotConfig): Promise<void>;
|
|
14
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ConfigSchema } from
|
|
2
|
-
import { bootstrapGateway } from
|
|
3
|
-
import { getProfileDir, resolveEnv } from
|
|
4
|
-
import { runQrSetup, persistCredentialsToProfile } from
|
|
1
|
+
import { ConfigSchema } from "./config.js";
|
|
2
|
+
import { bootstrapGateway } from "./gateway/index.js";
|
|
3
|
+
import { getProfileDir, resolveEnv } from "./shared/index.js";
|
|
4
|
+
import { runQrSetup, persistCredentialsToProfile } from "./setup.js";
|
|
5
5
|
// ── Cordis 插件元数据 ──
|
|
6
6
|
export const name = 'im-qqbot';
|
|
7
7
|
export const inject = ['agents'];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { Logger } from '../types.ts';
|
|
3
|
+
/** 富媒体下载根目录(attachment.ts 复用) */
|
|
4
|
+
export declare const MEDIA_ROOT: string;
|
|
5
|
+
/**
|
|
6
|
+
* 清理过期的富媒体文件(按文件 mtime 判断,异步并发执行,不阻塞事件循环)
|
|
7
|
+
*
|
|
8
|
+
* @param ttlHours 保留时长(小时),<=0 表示永不过期(跳过清理)
|
|
9
|
+
* @returns 删除的文件数
|
|
10
|
+
*/
|
|
11
|
+
export declare function cleanupExpiredMedia(ttlHours: number, logger?: Logger): Promise<number>;
|
|
12
|
+
/**
|
|
13
|
+
* 启动富媒体过期清理:启动时清理一次 + 定期清理(每 1 小时)
|
|
14
|
+
*
|
|
15
|
+
* 用 ctx.effect 挂载定时器,插件卸载时自动 clearInterval。
|
|
16
|
+
* ttlHours <= 0 时不启动(永不过期)。
|
|
17
|
+
*/
|
|
18
|
+
export declare function startMediaCleanup(ctx: Context, ttlHours: number, logger: Logger): void;
|
|
19
|
+
//# sourceMappingURL=media-cleaner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-cleaner.d.ts","sourceRoot":"","sources":["../../src/media/media-cleaner.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,iCAAiC;AACjC,eAAO,MAAM,UAAU,QAA4C,CAAC;AAKpE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyB5F;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CA4BtF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 富媒体过期清理 — 避免下载文件持续膨胀
|
|
3
|
+
*
|
|
4
|
+
* 下载目录 ~/.dsh-qqbot/media,文件写入后不再变更,故文件 mtime 即「下载时间」。
|
|
5
|
+
* 清理时按文件 mtime 判断是否超过保留时长。
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { readdir, rm, stat } from 'node:fs/promises';
|
|
9
|
+
import { join, resolve } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
/** 富媒体下载根目录(attachment.ts 复用) */
|
|
12
|
+
export const MEDIA_ROOT = resolve(homedir(), '.dsh-qqbot', 'media');
|
|
13
|
+
/** 定期清理间隔(毫秒),固定 1 小时 */
|
|
14
|
+
const CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
|
|
15
|
+
/**
|
|
16
|
+
* 清理过期的富媒体文件(按文件 mtime 判断,异步并发执行,不阻塞事件循环)
|
|
17
|
+
*
|
|
18
|
+
* @param ttlHours 保留时长(小时),<=0 表示永不过期(跳过清理)
|
|
19
|
+
* @returns 删除的文件数
|
|
20
|
+
*/
|
|
21
|
+
export async function cleanupExpiredMedia(ttlHours, logger) {
|
|
22
|
+
if (ttlHours <= 0)
|
|
23
|
+
return 0;
|
|
24
|
+
if (!existsSync(MEDIA_ROOT))
|
|
25
|
+
return 0;
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
const ttlMs = ttlHours * 60 * 60 * 1000;
|
|
28
|
+
const entries = await readdir(MEDIA_ROOT, { withFileTypes: true });
|
|
29
|
+
const files = entries.filter(entry => entry.isFile()).map(entry => entry.name);
|
|
30
|
+
const results = await Promise.all(files.map(async (name) => {
|
|
31
|
+
const filePath = join(MEDIA_ROOT, name);
|
|
32
|
+
try {
|
|
33
|
+
const info = await stat(filePath);
|
|
34
|
+
if (now - info.mtimeMs <= ttlMs)
|
|
35
|
+
return false;
|
|
36
|
+
await rm(filePath, { force: true });
|
|
37
|
+
logger?.debug(`im-qqbot: cleaned expired media: ${name}`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger?.warn(`im-qqbot: media cleanup failed for ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
return results.filter(Boolean).length;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 启动富媒体过期清理:启动时清理一次 + 定期清理(每 1 小时)
|
|
49
|
+
*
|
|
50
|
+
* 用 ctx.effect 挂载定时器,插件卸载时自动 clearInterval。
|
|
51
|
+
* ttlHours <= 0 时不启动(永不过期)。
|
|
52
|
+
*/
|
|
53
|
+
export function startMediaCleanup(ctx, ttlHours, logger) {
|
|
54
|
+
if (ttlHours <= 0)
|
|
55
|
+
return;
|
|
56
|
+
logger.info(`im-qqbot: media cleanup started (ttl=${ttlHours}h, interval=1h, root=${MEDIA_ROOT})`);
|
|
57
|
+
// 启动时立即清理一次(异步,不阻塞启动流程)
|
|
58
|
+
void cleanupExpiredMedia(ttlHours, logger)
|
|
59
|
+
.then((cleaned) => {
|
|
60
|
+
if (cleaned > 0)
|
|
61
|
+
logger.info(`im-qqbot: cleaned ${cleaned} expired media files`);
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
logger.warn(`im-qqbot: media cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
});
|
|
66
|
+
ctx
|
|
67
|
+
.effect(() => {
|
|
68
|
+
const timer = setInterval(() => {
|
|
69
|
+
void cleanupExpiredMedia(ttlHours, logger)
|
|
70
|
+
.then((n) => {
|
|
71
|
+
if (n > 0)
|
|
72
|
+
logger.info(`im-qqbot: cleaned ${n} expired media files`);
|
|
73
|
+
})
|
|
74
|
+
.catch((err) => {
|
|
75
|
+
logger.warn(`im-qqbot: media cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
+
});
|
|
77
|
+
}, CLEANUP_INTERVAL_MS);
|
|
78
|
+
return async () => { clearInterval(timer); };
|
|
79
|
+
}, 'im-qqbot.media-cleanup');
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=media-cleaner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-cleaner.js","sourceRoot":"","sources":["../../src/media/media-cleaner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { ImQQBotConfig } from '../config.ts';
|
|
3
|
+
import type { SessionManager } from '../session/index.ts';
|
|
4
|
+
import type { Logger, ReplyTarget } from '../types.ts';
|
|
5
|
+
/** 工具名(qqbot 前缀避免与其他通道的同名工具冲突) */
|
|
6
|
+
export declare const SEND_FILE_TOOL_NAME = "qqbot_send_file";
|
|
7
|
+
/** 发送器最小接口(QQBot 实例满足) */
|
|
8
|
+
interface MediaSendResult {
|
|
9
|
+
upload: {
|
|
10
|
+
file_uuid: string;
|
|
11
|
+
};
|
|
12
|
+
message?: {
|
|
13
|
+
id?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface MediaSenderLike {
|
|
17
|
+
sendImage(target: ReplyTarget, source: {
|
|
18
|
+
localPath?: string;
|
|
19
|
+
}): Promise<MediaSendResult>;
|
|
20
|
+
sendVideo(target: ReplyTarget, source: {
|
|
21
|
+
localPath?: string;
|
|
22
|
+
}): Promise<MediaSendResult>;
|
|
23
|
+
sendVoice(target: ReplyTarget, source: {
|
|
24
|
+
localPath?: string;
|
|
25
|
+
}): Promise<MediaSendResult>;
|
|
26
|
+
sendFile(target: ReplyTarget, source: {
|
|
27
|
+
localPath?: string;
|
|
28
|
+
}, opts?: {
|
|
29
|
+
fileName?: string;
|
|
30
|
+
}): Promise<MediaSendResult>;
|
|
31
|
+
}
|
|
32
|
+
/** 注册 qqbot_send_file 工具(tools 服务缺失时优雅降级,不阻断插件启动) */
|
|
33
|
+
export declare function registerSendFileTool(ctx: Context, bot: MediaSenderLike, manager: SessionManager, config: ImQQBotConfig, logger: Logger): void;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=send-file-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-file-tool.d.ts","sourceRoot":"","sources":["../../src/media/send-file-tool.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAY,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGvD,kCAAkC;AAClC,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAQrD,0BAA0B;AAC1B,UAAU,eAAe;IACvB,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACzF,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACzF,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACzF,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACvH;AAwCD,qDAAqD;AACrD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GACb,IAAI,CA4GN"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置 qqbot_send_file 工具 — 将本地文件发送给 QQ 用户或群。
|
|
3
|
+
*
|
|
4
|
+
* - 发送目标默认自动定位(exec.agent → SessionManager.findByAgent → replyTarget),
|
|
5
|
+
* 无需模型手动传 openid;显式 target 参数作为覆盖。
|
|
6
|
+
* - 按扩展名分发到 SDK 的 sendImage / sendVideo / sendVoice / sendFile。
|
|
7
|
+
* - 路径白名单:默认只允许访问 media 目录 + agent cwd,可开关 + 扩展白名单。
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { stat } from 'node:fs/promises';
|
|
11
|
+
import { basename, isAbsolute, relative, resolve } from 'node:path';
|
|
12
|
+
import { MEDIA_ROOT } from "./media-cleaner.js";
|
|
13
|
+
/** 工具名(qqbot 前缀避免与其他通道的同名工具冲突) */
|
|
14
|
+
export const SEND_FILE_TOOL_NAME = 'qqbot_send_file';
|
|
15
|
+
const DESCRIPTION = 'Send a local file (image, video, voice, or generic file) to the QQ user or group '
|
|
16
|
+
+ 'of the current conversation. Pass the absolute path of a file that already exists on disk. '
|
|
17
|
+
+ 'Use this when the user asks you to deliver a generated file (chart, report, exported data, '
|
|
18
|
+
+ 'screenshot, etc.) or to send an existing media/file back to them.';
|
|
19
|
+
const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']);
|
|
20
|
+
const VIDEO_EXTS = new Set(['.mp4', '.mov', '.avi', '.mkv', '.webm']);
|
|
21
|
+
const VOICE_EXTS = new Set(['.mp3', '.wav', '.ogg', '.aac', '.silk', '.amr']);
|
|
22
|
+
/** 解析显式 target("c2c:openid" / "group:openid"),非法返回 undefined */
|
|
23
|
+
function parseTarget(input) {
|
|
24
|
+
const idx = input.indexOf(':');
|
|
25
|
+
if (idx <= 0)
|
|
26
|
+
return undefined;
|
|
27
|
+
const scope = input.slice(0, idx);
|
|
28
|
+
const targetId = input.slice(idx + 1);
|
|
29
|
+
if (!targetId || (scope !== 'c2c' && scope !== 'group'))
|
|
30
|
+
return undefined;
|
|
31
|
+
return { scope, targetId };
|
|
32
|
+
}
|
|
33
|
+
/** 判断 filePath 是否位于 root 目录内(含 root 本身) */
|
|
34
|
+
function isWithinRoot(filePath, root) {
|
|
35
|
+
const rel = relative(resolve(root), filePath);
|
|
36
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
37
|
+
}
|
|
38
|
+
/** 路径白名单校验:restrictPaths 关闭时放行任意路径,否则限定 media + cwd + extraRoots */
|
|
39
|
+
function isPathAllowed(filePath, config, cwd) {
|
|
40
|
+
if (!config.restrictPaths)
|
|
41
|
+
return true;
|
|
42
|
+
const roots = [MEDIA_ROOT, cwd, ...config.extraRoots];
|
|
43
|
+
return roots.some((root) => isWithinRoot(filePath, root));
|
|
44
|
+
}
|
|
45
|
+
/** 注册 qqbot_send_file 工具(tools 服务缺失时优雅降级,不阻断插件启动) */
|
|
46
|
+
export function registerSendFileTool(ctx, bot, manager, config, logger) {
|
|
47
|
+
const tools = ctx.get('tools');
|
|
48
|
+
if (!tools?.register) {
|
|
49
|
+
logger.warn('im-qqbot: tools 服务不可用,qqbot_send_file 工具未注册');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const cwd = config.cwd || process.cwd();
|
|
53
|
+
const definition = {
|
|
54
|
+
name: SEND_FILE_TOOL_NAME,
|
|
55
|
+
description: DESCRIPTION,
|
|
56
|
+
parameters: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
file_path: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Absolute path to the local file to send (image/video/voice/generic file).',
|
|
62
|
+
},
|
|
63
|
+
target: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Optional send target as "c2c:openid" or "group:openid". Omit to send to the current conversation.',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['file_path'],
|
|
69
|
+
additionalProperties: false,
|
|
70
|
+
},
|
|
71
|
+
output: {
|
|
72
|
+
schema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
fileName: { type: 'string' },
|
|
76
|
+
fileSize: { type: 'integer' },
|
|
77
|
+
target: { type: 'string' },
|
|
78
|
+
fileUuid: { type: 'string' },
|
|
79
|
+
messageId: { type: 'string' },
|
|
80
|
+
},
|
|
81
|
+
required: ['fileName', 'fileSize', 'target', 'fileUuid'],
|
|
82
|
+
additionalProperties: false,
|
|
83
|
+
},
|
|
84
|
+
render: (_args, value) => {
|
|
85
|
+
const v = value;
|
|
86
|
+
return [{ type: 'text', text: `已发送 ${v.fileName} → ${v.target}` }];
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
async execute(args, exec) {
|
|
90
|
+
const { file_path, target } = args;
|
|
91
|
+
if (typeof file_path !== 'string' || file_path.length === 0) {
|
|
92
|
+
throw new Error('qqbot_send_file: `file_path` must be a non-empty string');
|
|
93
|
+
}
|
|
94
|
+
// 1. 解析发送目标:显式 target > 当前会话(exec.agent → SessionRecord)
|
|
95
|
+
let replyTarget;
|
|
96
|
+
if (target !== undefined && target !== '') {
|
|
97
|
+
replyTarget = parseTarget(target);
|
|
98
|
+
if (replyTarget === undefined) {
|
|
99
|
+
throw new Error(`qqbot_send_file: target 格式错误,需要 c2c:openid 或 group:openid,收到: ${target}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (exec.agent !== undefined) {
|
|
103
|
+
replyTarget = manager.findByAgent(exec.agent)?.replyTarget;
|
|
104
|
+
}
|
|
105
|
+
if (replyTarget === undefined) {
|
|
106
|
+
throw new Error('qqbot_send_file: 无法确定发送目标,请显式传 target 参数');
|
|
107
|
+
}
|
|
108
|
+
// 2. 校验文件 + 路径白名单
|
|
109
|
+
const absPath = resolve(file_path);
|
|
110
|
+
if (!isPathAllowed(absPath, config.sendFile, cwd)) {
|
|
111
|
+
throw new Error(`qqbot_send_file: file_path 不在允许的目录内: ${absPath}`);
|
|
112
|
+
}
|
|
113
|
+
if (!existsSync(absPath)) {
|
|
114
|
+
throw new Error(`qqbot_send_file: 文件不存在: ${absPath}`);
|
|
115
|
+
}
|
|
116
|
+
const info = await stat(absPath);
|
|
117
|
+
if (!info.isFile()) {
|
|
118
|
+
throw new Error(`qqbot_send_file: 路径不是普通文件: ${absPath}`);
|
|
119
|
+
}
|
|
120
|
+
// 3. 按扩展名分类发送
|
|
121
|
+
const fileName = basename(absPath);
|
|
122
|
+
const ext = absPath.slice(absPath.lastIndexOf('.')).toLowerCase();
|
|
123
|
+
const source = { localPath: absPath };
|
|
124
|
+
let result;
|
|
125
|
+
if (IMAGE_EXTS.has(ext)) {
|
|
126
|
+
result = await bot.sendImage(replyTarget, source);
|
|
127
|
+
}
|
|
128
|
+
else if (VIDEO_EXTS.has(ext)) {
|
|
129
|
+
result = await bot.sendVideo(replyTarget, source);
|
|
130
|
+
}
|
|
131
|
+
else if (VOICE_EXTS.has(ext)) {
|
|
132
|
+
result = await bot.sendVoice(replyTarget, source);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
result = await bot.sendFile(replyTarget, source, { fileName });
|
|
136
|
+
}
|
|
137
|
+
logger.info(`im-qqbot: qqbot_send_file 发送 ${fileName} (${info.size} bytes) → ${replyTarget.scope}:${replyTarget.targetId}`);
|
|
138
|
+
return {
|
|
139
|
+
fileName,
|
|
140
|
+
fileSize: info.size,
|
|
141
|
+
target: `${replyTarget.scope}:${replyTarget.targetId}`,
|
|
142
|
+
fileUuid: result.upload.file_uuid,
|
|
143
|
+
...(result.message?.id !== undefined ? { messageId: result.message.id } : {}),
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
tools.register(definition);
|
|
148
|
+
logger.info('im-qqbot: qqbot_send_file 工具已注册');
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=send-file-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-file-tool.js","sourceRoot":"","sources":["../../src/media/send-file-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { VisionConfig } from '../config.ts';
|
|
3
|
+
import type { Logger } from '../types.ts';
|
|
4
|
+
/** 工具名(qqbot 前缀避免与外部 describe-image 包的同名工具冲突) */
|
|
5
|
+
export declare const DESCRIBE_IMAGE_TOOL_NAME = "qqbot_describe_image";
|
|
6
|
+
/**
|
|
7
|
+
* 启动时检查 settings.yaml 里 vision 模型的 input 模态是否声明了 image。
|
|
8
|
+
*
|
|
9
|
+
* 模型配置界面不暴露 `input` 字段,用户通过界面配置的视觉模型默认是纯文本,
|
|
10
|
+
* 会导致 pi-ai adapter 拒绝图片输入。这里在启动时兜底:未声明 image 时自动补 `[text, image]`。
|
|
11
|
+
* 幂等(已声明则跳过),解析/写入失败仅 warn 不阻断启动。
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureVisionInputModal(vision: VisionConfig, logger: Logger): void;
|
|
14
|
+
/** 注册 qqbot_describe_image 工具(服务缺失时优雅降级,不阻断插件启动) */
|
|
15
|
+
export declare function registerDescribeImageTool(ctx: Context, vision: VisionConfig, logger: Logger): void;
|
|
16
|
+
//# sourceMappingURL=vision-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-tool.d.ts","sourceRoot":"","sources":["../../src/media/vision-tool.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AASnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA2B1C,iDAAiD;AACjD,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAwH/D;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAwCjF;AAED,oDAAoD;AACpD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAqElG"}
|