@krischoichoi/channel-qq 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.
- package/README.md +93 -0
- package/lib/adapter.d.ts +76 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +299 -0
- package/lib/adapter.js.map +1 -0
- package/lib/config.d.ts +62 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +43 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +75 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +232 -0
- package/lib/definition.js.map +1 -0
- package/lib/image-hydrator.d.ts +13 -0
- package/lib/image-hydrator.d.ts.map +1 -0
- package/lib/image-hydrator.js +13 -0
- package/lib/image-hydrator.js.map +1 -0
- package/lib/inbound.d.ts +49 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +98 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +53 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +113 -0
- package/lib/index.js.map +1 -0
- package/lib/interaction-mapper.d.ts +43 -0
- package/lib/interaction-mapper.d.ts.map +1 -0
- package/lib/interaction-mapper.js +91 -0
- package/lib/interaction-mapper.js.map +1 -0
- package/lib/manifest.d.ts +22 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +35 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +38 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +109 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-hydrator.d.ts +61 -0
- package/lib/media-hydrator.d.ts.map +1 -0
- package/lib/media-hydrator.js +107 -0
- package/lib/media-hydrator.js.map +1 -0
- package/lib/outbound.d.ts +41 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +111 -0
- package/lib/outbound.js.map +1 -0
- package/lib/sdk-client.d.ts +247 -0
- package/lib/sdk-client.d.ts.map +1 -0
- package/lib/sdk-client.js +312 -0
- package/lib/sdk-client.js.map +1 -0
- package/lib/streaming-reply.d.ts +19 -0
- package/lib/streaming-reply.d.ts.map +1 -0
- package/lib/streaming-reply.js +26 -0
- package/lib/streaming-reply.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary hydration for the QQ inbound path.
|
|
3
|
+
*
|
|
4
|
+
* The mapper stays pure: it preserves the real `attachment.url` on image,
|
|
5
|
+
* generic-file, audio (`voice_wav_url` ?? `url`) and video parts.
|
|
6
|
+
* This module is the single place that turns a genuine `http(s)` URL into
|
|
7
|
+
* trusted bytes, using the shared `SecureRemoteMediaFetcher` from
|
|
8
|
+
* `@krischoichoi/channel-core` as the DSH host's generic security boundary. It
|
|
9
|
+
* never implements QQ upload / token / gateway protocol —
|
|
10
|
+
* those belong to `qqbot-nodejs`.
|
|
11
|
+
*
|
|
12
|
+
* Native image ingress (M2A) hydrates `image` parts so the harness
|
|
13
|
+
* `saveImage()` / `ImageBlock` path receives real bytes. Generic file
|
|
14
|
+
* ingress hydrates `file` parts the same way: the produced
|
|
15
|
+
* `localData` is picked up automatically by the harness private asset store
|
|
16
|
+
* + extractor, so the adapter never implements QQ file upload. Since
|
|
17
|
+
* `audio` and `video` parts are hydrated through the same secure
|
|
18
|
+
* fetcher — the adapter is the transport layer and must deliver bytes for
|
|
19
|
+
* every binary kind it maps, regardless of whether a model consumer exists
|
|
20
|
+
* yet.
|
|
21
|
+
*
|
|
22
|
+
* The apply step reuses core's protocol-agnostic `applyHydrationResult`
|
|
23
|
+
* for all four binary kinds: it owns the byte cap, the
|
|
24
|
+
* AbortSignal handling, the localData/size/mime merge and the stable
|
|
25
|
+
* `ingressFailure` mapping. This module only owns the QQ-specific gate
|
|
26
|
+
* (which parts, which URLs) and the URL-level failure mapping.
|
|
27
|
+
*
|
|
28
|
+
* Key guarantees:
|
|
29
|
+
* - Only the four binary kinds (`image` / `file` / `audio` / `video`) with a
|
|
30
|
+
* genuine `http(s)` `url` are hydrated. A part already carrying `localData`
|
|
31
|
+
* / `dataUri`, or carrying an opaque `resourceRef` / no locator, is left
|
|
32
|
+
* untouched.
|
|
33
|
+
* - On success the part gets `localData` (the downloaded bytes), `size` (the
|
|
34
|
+
* hydrated byte length) and `mimeType` (prefer the fetcher's Content-Type,
|
|
35
|
+
* else keep the platform hint, else sniff the filename). Image parts keep
|
|
36
|
+
* their intrinsic shape: the `size` field is intentionally NOT set there.
|
|
37
|
+
* - On ANY failure the part is NOT dropped: its `url` is kept, a stable
|
|
38
|
+
* `ingressFailure` code is set, and hydration of other parts continues.
|
|
39
|
+
* A download failure must never block text delivery, and this
|
|
40
|
+
* function never throws.
|
|
41
|
+
*/
|
|
42
|
+
import { SecureRemoteMediaFetcher, applyHydrationResult, isHydratableBinaryPart, mimeHintFromFilename, } from '@krischoichoi/channel-core';
|
|
43
|
+
/** True when `value` is an absolute `http` / `https` URL. */
|
|
44
|
+
function isHttpUrl(value) {
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = new URL(value);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Hydrate binary bytes on `parts` in place (the same array the mapper
|
|
56
|
+
* produced). Returns the mutated array. Never throws — every download failure
|
|
57
|
+
* is recorded as `ingressFailure` on the part and the event still carries
|
|
58
|
+
* the part's `url` plus any text parts.
|
|
59
|
+
*/
|
|
60
|
+
export async function hydrateMediaParts(parts, fetcher, options = {}) {
|
|
61
|
+
const { maxBytes = 20 * 1024 * 1024, idleTimeoutMs = 15_000, timeoutMs = 15_000, signal } = options;
|
|
62
|
+
await Promise.allSettled(parts.map(async (part) => {
|
|
63
|
+
// Only the four binary kinds participate in hydration.
|
|
64
|
+
if (!isHydratableBinaryPart(part))
|
|
65
|
+
return;
|
|
66
|
+
const url = part.url;
|
|
67
|
+
if (url === undefined || url === '') {
|
|
68
|
+
// resourceRef-only / dataUri-only / locator-free parts are left
|
|
69
|
+
// untouched by hydration.
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!isHttpUrl(url)) {
|
|
73
|
+
// A `url` that is not a genuine http(s) URL cannot be ingested by the
|
|
74
|
+
// secure fetcher → resource-unavailable.
|
|
75
|
+
part.ingressFailure = 'resource-unavailable';
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Trusted bytes already in hand take precedence — never re-download.
|
|
79
|
+
// (`applyHydrationResult` also skips parts with `localData`; the
|
|
80
|
+
// `dataUri` skip is QQ's own gate to keep the legacy behavior.)
|
|
81
|
+
if (part.localData !== undefined || part.dataUri !== undefined) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const platformMime = part.mimeType;
|
|
85
|
+
const isImage = part.type === 'image';
|
|
86
|
+
await applyHydrationResult(part, async () => {
|
|
87
|
+
const result = await fetcher.fetchBounded(url, {
|
|
88
|
+
maxBytes,
|
|
89
|
+
idleTimeoutMs,
|
|
90
|
+
timeoutMs,
|
|
91
|
+
signal,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
data: result.data,
|
|
95
|
+
// Prefer the fetcher's Content-Type, else keep the platform hint,
|
|
96
|
+
// else fall back to a filename sniff.
|
|
97
|
+
mimeType: result.mimeType ?? platformMime ?? mimeHintFromFilename(part.name),
|
|
98
|
+
};
|
|
99
|
+
}, { maxBytes, signal });
|
|
100
|
+
// Image parts never carry `size` (keep that shape stable), while
|
|
101
|
+
// audio/video/file take the authoritative `size` from the real bytes.
|
|
102
|
+
if (isImage)
|
|
103
|
+
delete part.size;
|
|
104
|
+
}));
|
|
105
|
+
return parts;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=media-hydrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-hydrator.js","sourceRoot":"","sources":["../src/media-hydrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAGpC,6DAA6D;AAC7D,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;AACrE,CAAC;AAaD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAoB,EACpB,OAAiC,EACjC,UAAgC,EAAE;IAElC,MAAM,EAAE,QAAQ,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEpG,MAAM,OAAO,CAAC,UAAU,CACtB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACvB,uDAAuD;QACvD,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC;YAAE,OAAO;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QAErB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YACpC,gEAAgE;YAChE,0BAA0B;YAC1B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,sEAAsE;YACtE,yCAAyC;YACzC,IAAI,CAAC,cAAc,GAAG,sBAAsB,CAAC;YAC7C,OAAO;QACT,CAAC;QACD,qEAAqE;QACrE,iEAAiE;QACjE,gEAAgE;QAChE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC;QACtC,MAAM,oBAAoB,CACxB,IAAI,EACJ,KAAK,IAAI,EAAE;YACT,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE;gBAC7C,QAAQ;gBACR,aAAa;gBACb,SAAS;gBACT,MAAM;aACP,CAAC,CAAC;YACH,OAAO;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,kEAAkE;gBAClE,sCAAsC;gBACtC,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,YAAY,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;aAC7E,CAAC;QACJ,CAAC,EACD,EAAE,QAAQ,EAAE,MAAM,EAAE,CACrB,CAAC;QACF,iEAAiE;QACjE,sEAAsE;QACtE,IAAI,OAAO;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IAChC,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound sending: channel message → QQReplyTarget → QQSdkClient.
|
|
3
|
+
*
|
|
4
|
+
* Text-only messages go through `sendText`; messages carrying a media part
|
|
5
|
+
* with a resolvable source go through `sendMedia` (fileType mapped from the
|
|
6
|
+
* part type). A part's `localData` bytes (image or generic file) are sent via
|
|
7
|
+
* `sendMedia` with the SDK `fileData` base64 carrier.
|
|
8
|
+
*
|
|
9
|
+
* Interactive actions (`OutboundMessage.actions`) map to the QQ platform
|
|
10
|
+
* inline keyboard: one DSH action row → one QQ keyboard row, each action → one
|
|
11
|
+
* `KeyboardButton`. Buttons are only attached to plain-text sends — QQ does
|
|
12
|
+
* not reliably support buttons on media messages, so media + actions degrades
|
|
13
|
+
* to a plain media send (documented; never fabricated).
|
|
14
|
+
* Failures are wrapped in `ChannelSendError`.
|
|
15
|
+
*/
|
|
16
|
+
import type { ChannelLogger, ChannelTarget, OutboundActionRow, OutboundMessage, SendResult } from '@krischoichoi/channel-core';
|
|
17
|
+
import type { QQInlineKeyboardLike, QQReplyTarget, QQSdkClient } from './sdk-client.js';
|
|
18
|
+
export declare class OutboundSender {
|
|
19
|
+
private readonly client;
|
|
20
|
+
private readonly logger;
|
|
21
|
+
constructor(client: QQSdkClient, logger: ChannelLogger);
|
|
22
|
+
send(target: ChannelTarget, message: OutboundMessage): Promise<SendResult>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Map DSH `OutboundActionRow[]` → QQ `InlineKeyboard` shape.
|
|
26
|
+
*
|
|
27
|
+
* Each action's opaque `id` (the `uq_*` id minted by `QuestionStateStore`) is
|
|
28
|
+
* the value that must round-trip back on a button press: it is placed in both
|
|
29
|
+
* `action.data` (echoed verbatim by the platform as
|
|
30
|
+
* `InteractionEvent.data.resolved.button_data`) and the button `id` (the
|
|
31
|
+
* platform-stable handle). The adapter recovers the action id from
|
|
32
|
+
* `button_data` on the interaction event.
|
|
33
|
+
*
|
|
34
|
+
* `style` maps only the QQ button render styles the platform actually
|
|
35
|
+
* supports: DSH `primary` → QQ style 1 (primary fill), everything else →
|
|
36
|
+
* default style 0. Unknown styles are never invented.
|
|
37
|
+
*/
|
|
38
|
+
export declare function toQqKeyboard(rows: OutboundActionRow[]): QQInlineKeyboardLike;
|
|
39
|
+
/** Build a port `QQReplyTarget` from a DSH `ChannelTarget`. */
|
|
40
|
+
export declare function toReplyTarget(target: ChannelTarget): QQReplyTarget;
|
|
41
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EAEb,iBAAiB,EACjB,eAAe,EACf,UAAU,EACX,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EACV,oBAAoB,EAEpB,aAAa,EACb,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAQzB,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,aAAa;IAGlC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;CAiCjF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,oBAAoB,CAM5E;AA+BD,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAMlE"}
|
package/lib/outbound.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ChannelSendError } from '@krischoichoi/channel-core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { QQ_BUTTON_ACTION_TYPE, QQ_BUTTON_PERMISSION_TYPE, QQ_BUTTON_STYLE_DEFAULT, QQ_BUTTON_STYLE_PRIMARY, } from './sdk-client.js';
|
|
4
|
+
export class OutboundSender {
|
|
5
|
+
client;
|
|
6
|
+
logger;
|
|
7
|
+
constructor(client, logger) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
}
|
|
11
|
+
async send(target, message) {
|
|
12
|
+
try {
|
|
13
|
+
const replyTarget = toReplyTarget(target);
|
|
14
|
+
if (hasMedia(message)) {
|
|
15
|
+
// Media messages carry no buttons: QQ does not reliably support an
|
|
16
|
+
// inline keyboard on a media send, so actions are dropped (debug note,
|
|
17
|
+
// never a fabricated button attachment).
|
|
18
|
+
if (message.actions?.length) {
|
|
19
|
+
this.logger.debug(`[channel-qq] media message with actions — buttons unsupported on QQ media sends; dropping actions`);
|
|
20
|
+
}
|
|
21
|
+
const response = await this.client.sendMedia(replyTarget, message);
|
|
22
|
+
return sendResult(response);
|
|
23
|
+
}
|
|
24
|
+
const text = message.text ?? '';
|
|
25
|
+
if (message.actions?.length) {
|
|
26
|
+
const keyboard = toQqKeyboard(message.actions);
|
|
27
|
+
const response = await this.client.sendMarkdownWithKeyboard(replyTarget, text, keyboard);
|
|
28
|
+
return sendResult(response);
|
|
29
|
+
}
|
|
30
|
+
const response = await this.client.sendText(replyTarget, text);
|
|
31
|
+
return sendResult(response);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
this.logger.error(`[channel-qq] send failed to '${target.conversationId}'`, error instanceof Error ? error.message : error);
|
|
35
|
+
throw new ChannelSendError(`qq send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Map DSH `OutboundActionRow[]` → QQ `InlineKeyboard` shape.
|
|
41
|
+
*
|
|
42
|
+
* Each action's opaque `id` (the `uq_*` id minted by `QuestionStateStore`) is
|
|
43
|
+
* the value that must round-trip back on a button press: it is placed in both
|
|
44
|
+
* `action.data` (echoed verbatim by the platform as
|
|
45
|
+
* `InteractionEvent.data.resolved.button_data`) and the button `id` (the
|
|
46
|
+
* platform-stable handle). The adapter recovers the action id from
|
|
47
|
+
* `button_data` on the interaction event.
|
|
48
|
+
*
|
|
49
|
+
* `style` maps only the QQ button render styles the platform actually
|
|
50
|
+
* supports: DSH `primary` → QQ style 1 (primary fill), everything else →
|
|
51
|
+
* default style 0. Unknown styles are never invented.
|
|
52
|
+
*/
|
|
53
|
+
export function toQqKeyboard(rows) {
|
|
54
|
+
return {
|
|
55
|
+
content: {
|
|
56
|
+
rows: rows.map((row) => ({ buttons: row.actions.map(toQqButton) })),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const qqMessageResponseSchema = z.object({ id: z.string().min(1) });
|
|
61
|
+
/** Normalize the official SDK response without trusting an unchecked cast. */
|
|
62
|
+
function sendResult(raw) {
|
|
63
|
+
const parsed = qqMessageResponseSchema.safeParse(raw);
|
|
64
|
+
return {
|
|
65
|
+
delivered: true,
|
|
66
|
+
...(parsed.success ? { messageId: parsed.data.id } : {}),
|
|
67
|
+
raw,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function toQqButton(action) {
|
|
71
|
+
return {
|
|
72
|
+
id: action.id,
|
|
73
|
+
render_data: {
|
|
74
|
+
label: action.label,
|
|
75
|
+
visited_label: action.label,
|
|
76
|
+
style: action.style === 'primary' ? QQ_BUTTON_STYLE_PRIMARY : QQ_BUTTON_STYLE_DEFAULT,
|
|
77
|
+
},
|
|
78
|
+
action: {
|
|
79
|
+
type: QQ_BUTTON_ACTION_TYPE,
|
|
80
|
+
permission: { type: QQ_BUTTON_PERMISSION_TYPE },
|
|
81
|
+
data: action.id,
|
|
82
|
+
click_limit: 1,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Build a port `QQReplyTarget` from a DSH `ChannelTarget`. */
|
|
87
|
+
export function toReplyTarget(target) {
|
|
88
|
+
return {
|
|
89
|
+
scope: target.conversationType === 'group' ? 'group' : 'c2c',
|
|
90
|
+
targetId: target.conversationId,
|
|
91
|
+
msgId: target.replyToMessageId,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Whether the message carries a media part with a resolvable source. */
|
|
95
|
+
function hasMedia(message) {
|
|
96
|
+
for (const part of message.parts ?? []) {
|
|
97
|
+
switch (part.type) {
|
|
98
|
+
case 'image':
|
|
99
|
+
case 'audio':
|
|
100
|
+
case 'video':
|
|
101
|
+
case 'file':
|
|
102
|
+
if (part.url || part.dataUri || part.localData !== undefined)
|
|
103
|
+
return true;
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH SDK port — the thin seam between the QQ adapter and the Tencent SDK.
|
|
3
|
+
*
|
|
4
|
+
* The adapter never touches `QQBot` directly; everything flows through
|
|
5
|
+
* `QQSdkClient`. Production wraps the real `QQBot` (`TencentQQSdkClient`);
|
|
6
|
+
* tests inject a `FakeQQSdkClient` so the entire adapter contract suite runs
|
|
7
|
+
* fully offline (no network, no real credentials).
|
|
8
|
+
*/
|
|
9
|
+
import { MediaFileType, QQBot, type Logger as SdkLogger, type QQBotInboundMessage } from '@tencent-connect/qqbot-nodejs';
|
|
10
|
+
import { type ChannelLogger, type OutboundMessage } from '@krischoichoi/channel-core';
|
|
11
|
+
import type { QQConfig } from './config.js';
|
|
12
|
+
/**
|
|
13
|
+
* QQ Gateway intents the adapter actually consumes today (minimal-intent
|
|
14
|
+
* principle): `GROUP_AND_C2C` covers the C2C/group message events this adapter
|
|
15
|
+
* processes inbound, and `INTERACTION` covers the button `INTERACTION_CREATE`
|
|
16
|
+
* callbacks that drive `interaction.received`. The SDK defaults to
|
|
17
|
+
* `FULL_INTENTS` (guilids + members + guild messages + dm + group/c2c +
|
|
18
|
+
* interaction); requesting only what we use avoids `4914 INSUFFICIENT_INTENTS`
|
|
19
|
+
* / `4915 DISALLOWED_INTENTS` gateway rejections for privileges the bridge
|
|
20
|
+
* never exercises, and keeps the permission surface minimal. Bit values are
|
|
21
|
+
* verified from the pinned SDK (`dist/protocol/gateway/constants.js`):
|
|
22
|
+
* `GROUP_AND_C2C = 1 << 25`, `INTERACTION = 1 << 26`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const QQ_MINIMAL_INTENTS: number;
|
|
25
|
+
/** Reply target for outbound text/media send (port-local structural type). */
|
|
26
|
+
export interface QQReplyTarget {
|
|
27
|
+
scope: 'c2c' | 'group';
|
|
28
|
+
targetId: string;
|
|
29
|
+
msgId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Stream target for native C2C streaming (port-local structural type). */
|
|
32
|
+
export interface QQStreamTarget {
|
|
33
|
+
scope: 'c2c' | 'group';
|
|
34
|
+
targetId: string;
|
|
35
|
+
msgId?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Structural slice of the SDK `StreamSession` the adapter needs. Kept as an
|
|
39
|
+
* interface so both the real SDK `StreamSession` and the offline fake satisfy
|
|
40
|
+
* it (the SDK class carries private members a plain fake cannot implement).
|
|
41
|
+
*/
|
|
42
|
+
export interface QQStreamSession {
|
|
43
|
+
update(fullText: string): Promise<void>;
|
|
44
|
+
complete(): Promise<unknown>;
|
|
45
|
+
cancel(): void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Structural slice of the SDK `InteractionEvent` the adapter needs for an
|
|
49
|
+
* `interaction.received` round-trip. Kept port-local (no platform import in
|
|
50
|
+
* the adapter) so the real SDK type and the offline fake both satisfy it.
|
|
51
|
+
*
|
|
52
|
+
* All fields are optional here to mirror the untrusted wire shape; the
|
|
53
|
+
* adapter validates the actual payload with a zod schema at the trust
|
|
54
|
+
* boundary before emitting a canonical event.
|
|
55
|
+
*/
|
|
56
|
+
export interface QQInteractionLike {
|
|
57
|
+
id?: string;
|
|
58
|
+
chat_type?: number;
|
|
59
|
+
user_openid?: string;
|
|
60
|
+
group_openid?: string;
|
|
61
|
+
group_member_openid?: string;
|
|
62
|
+
data?: {
|
|
63
|
+
resolved?: {
|
|
64
|
+
button_id?: string;
|
|
65
|
+
button_data?: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Structural slice of the SDK `KeyboardButton` row shape the outbound mapper
|
|
71
|
+
* serializes into. Mirrors the QQ platform inline-keyboard contract:
|
|
72
|
+
* `action.data` is the opaque value echoed back verbatim in
|
|
73
|
+
* `InteractionEvent.data.resolved.button_data`, so the `uq_*` action id rides
|
|
74
|
+
* there (and in `id`, the platform-stable button handle).
|
|
75
|
+
*/
|
|
76
|
+
export interface QQKeyboardButton {
|
|
77
|
+
id: string;
|
|
78
|
+
render_data: {
|
|
79
|
+
label: string;
|
|
80
|
+
visited_label: string;
|
|
81
|
+
style: number;
|
|
82
|
+
};
|
|
83
|
+
action: {
|
|
84
|
+
type: number;
|
|
85
|
+
permission: {
|
|
86
|
+
type: number;
|
|
87
|
+
};
|
|
88
|
+
data: string;
|
|
89
|
+
click_limit?: number;
|
|
90
|
+
};
|
|
91
|
+
group_id?: string;
|
|
92
|
+
}
|
|
93
|
+
/** Port-local structural slice of the SDK `InlineKeyboard`. */
|
|
94
|
+
export interface QQInlineKeyboardLike {
|
|
95
|
+
content: {
|
|
96
|
+
rows: Array<{
|
|
97
|
+
buttons: QQKeyboardButton[];
|
|
98
|
+
}>;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** QQ inline-keyboard callback action (emits `INTERACTION_CREATE`). */
|
|
102
|
+
export declare const QQ_BUTTON_ACTION_TYPE = 1;
|
|
103
|
+
/** QQ inline-keyboard permission: any user may press. */
|
|
104
|
+
export declare const QQ_BUTTON_PERMISSION_TYPE = 2;
|
|
105
|
+
/** QQ button render style for the `primary` DSH action style. */
|
|
106
|
+
export declare const QQ_BUTTON_STYLE_PRIMARY = 1;
|
|
107
|
+
/** QQ button render style default. */
|
|
108
|
+
export declare const QQ_BUTTON_STYLE_DEFAULT = 0;
|
|
109
|
+
export interface QQSdkClient {
|
|
110
|
+
onReady(handler: () => void): void;
|
|
111
|
+
onResumed(handler: () => void): void;
|
|
112
|
+
onError(handler: (error: Error) => void): void;
|
|
113
|
+
onMessage(handler: (message: QQBotInboundMessage) => void): void;
|
|
114
|
+
onInteraction(handler: (event: QQInteractionLike) => void): void;
|
|
115
|
+
start(signal: AbortSignal): Promise<void>;
|
|
116
|
+
stop(): void;
|
|
117
|
+
sendText(target: QQReplyTarget, text: string): Promise<unknown>;
|
|
118
|
+
sendMarkdownWithKeyboard(target: QQReplyTarget, text: string, keyboard: QQInlineKeyboardLike): Promise<unknown>;
|
|
119
|
+
sendMedia(target: QQReplyTarget, message: OutboundMessage): Promise<unknown>;
|
|
120
|
+
acknowledgeInteraction(id: string, code?: number, data?: Record<string, unknown>): Promise<unknown>;
|
|
121
|
+
openStream(target: QQStreamTarget, options: {
|
|
122
|
+
throttleMs: number;
|
|
123
|
+
}): QQStreamSession;
|
|
124
|
+
}
|
|
125
|
+
/** Bridge a DSH `ChannelLogger` (variadic) to the SDK `Logger` shape. */
|
|
126
|
+
export declare function adaptLogger(logger: ChannelLogger): SdkLogger;
|
|
127
|
+
/** Production `QQSdkClient`: wraps a real `QQBot` built from config. */
|
|
128
|
+
export declare class TencentQQSdkClient implements QQSdkClient {
|
|
129
|
+
readonly bot: QQBot;
|
|
130
|
+
constructor(config: QQConfig, logger: ChannelLogger, appSecret: string);
|
|
131
|
+
onReady(handler: () => void): void;
|
|
132
|
+
onResumed(handler: () => void): void;
|
|
133
|
+
onError(handler: (error: Error) => void): void;
|
|
134
|
+
onMessage(handler: (message: QQBotInboundMessage) => void): void;
|
|
135
|
+
onInteraction(handler: (event: QQInteractionLike) => void): void;
|
|
136
|
+
start(signal: AbortSignal): Promise<void>;
|
|
137
|
+
stop(): void;
|
|
138
|
+
sendText(target: QQReplyTarget, text: string): Promise<unknown>;
|
|
139
|
+
sendMarkdownWithKeyboard(target: QQReplyTarget, text: string, keyboard: QQInlineKeyboardLike): Promise<unknown>;
|
|
140
|
+
acknowledgeInteraction(id: string, code?: number, data?: Record<string, unknown>): Promise<unknown>;
|
|
141
|
+
sendMedia(target: QQReplyTarget, message: OutboundMessage): Promise<unknown>;
|
|
142
|
+
openStream(target: QQStreamTarget, options: {
|
|
143
|
+
throttleMs: number;
|
|
144
|
+
}): QQStreamSession;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Map an outbound message's first resolvable media part to SDK `sendMedia`
|
|
148
|
+
* options (fileType + single source + optional caption).
|
|
149
|
+
*
|
|
150
|
+
* A `dataUri` (`data:<mime>;base64,<payload>`) is decoded to its raw base64
|
|
151
|
+
* payload and sent as `fileData` — never as `url`: the QQ upload API fetches
|
|
152
|
+
* `url` over HTTP and cannot resolve an inline `data:` URL. Only a real
|
|
153
|
+
* `http(s)` `url` is passed through as `url`.
|
|
154
|
+
*/
|
|
155
|
+
export interface MediaOptions {
|
|
156
|
+
fileType: MediaFileType;
|
|
157
|
+
url?: string;
|
|
158
|
+
fileData?: string;
|
|
159
|
+
fileName?: string;
|
|
160
|
+
content?: string;
|
|
161
|
+
}
|
|
162
|
+
export declare function mediaOpts(message: OutboundMessage): MediaOptions;
|
|
163
|
+
/**
|
|
164
|
+
* Decode a `data:<mime>;base64,<payload>` URI into the raw base64 payload the
|
|
165
|
+
* Tencent `uploadMedia` `fileData` field expects. Rejects non-base64 data URIs
|
|
166
|
+
* (the QQ upload API ingests raw base64, not arbitrary URL-encoded payloads).
|
|
167
|
+
*/
|
|
168
|
+
export declare function decodeDataUri(dataUri: string): string;
|
|
169
|
+
/** Offline fake SDK client: records calls and exposes controllable emits. */
|
|
170
|
+
export declare class FakeQQSdkClient implements QQSdkClient {
|
|
171
|
+
private readyHandlers;
|
|
172
|
+
private resumedHandlers;
|
|
173
|
+
private errorHandlers;
|
|
174
|
+
private messageHandlers;
|
|
175
|
+
private interactionHandlers;
|
|
176
|
+
started: boolean;
|
|
177
|
+
stopped: boolean;
|
|
178
|
+
/** If set, `start()` throws this error (never resolves). */
|
|
179
|
+
startError?: Error;
|
|
180
|
+
/** If true, `start()` resolves only when the signal aborts. */
|
|
181
|
+
hangStart: boolean;
|
|
182
|
+
/** If true, `start()` emits `ready` (then still resolves immediately). */
|
|
183
|
+
autoReady: boolean;
|
|
184
|
+
/** Last signal passed to `start()`. */
|
|
185
|
+
lastSignal?: AbortSignal;
|
|
186
|
+
readonly textCalls: {
|
|
187
|
+
target: QQReplyTarget;
|
|
188
|
+
text: string;
|
|
189
|
+
}[];
|
|
190
|
+
readonly keyboardCalls: {
|
|
191
|
+
target: QQReplyTarget;
|
|
192
|
+
text: string;
|
|
193
|
+
keyboard: QQInlineKeyboardLike;
|
|
194
|
+
format: 'markdown';
|
|
195
|
+
}[];
|
|
196
|
+
readonly mediaCalls: {
|
|
197
|
+
target: QQReplyTarget;
|
|
198
|
+
message: OutboundMessage;
|
|
199
|
+
}[];
|
|
200
|
+
readonly acknowledgeCalls: {
|
|
201
|
+
id: string;
|
|
202
|
+
code?: number;
|
|
203
|
+
data?: Record<string, unknown>;
|
|
204
|
+
}[];
|
|
205
|
+
readonly streamCalls: {
|
|
206
|
+
target: QQStreamTarget;
|
|
207
|
+
options: {
|
|
208
|
+
throttleMs: number;
|
|
209
|
+
};
|
|
210
|
+
}[];
|
|
211
|
+
/** The live stream sessions opened so far (same order as `streamCalls`). */
|
|
212
|
+
readonly streams: QQStreamSession[];
|
|
213
|
+
/** If set, `sendText`/`sendMedia` reject with this error. */
|
|
214
|
+
sendError?: Error;
|
|
215
|
+
onReady(handler: () => void): void;
|
|
216
|
+
onResumed(handler: () => void): void;
|
|
217
|
+
onError(handler: (error: Error) => void): void;
|
|
218
|
+
onMessage(handler: (message: QQBotInboundMessage) => void): void;
|
|
219
|
+
onInteraction(handler: (event: QQInteractionLike) => void): void;
|
|
220
|
+
start(signal: AbortSignal): Promise<void>;
|
|
221
|
+
stop(): void;
|
|
222
|
+
sendText(target: QQReplyTarget, text: string): Promise<unknown>;
|
|
223
|
+
sendMarkdownWithKeyboard(target: QQReplyTarget, text: string, keyboard: QQInlineKeyboardLike): Promise<unknown>;
|
|
224
|
+
sendMedia(target: QQReplyTarget, message: OutboundMessage): Promise<unknown>;
|
|
225
|
+
acknowledgeInteraction(id: string, code?: number, data?: Record<string, unknown>): Promise<unknown>;
|
|
226
|
+
openStream(target: QQStreamTarget, options: {
|
|
227
|
+
throttleMs: number;
|
|
228
|
+
}): QQStreamSession;
|
|
229
|
+
emitReady(): void;
|
|
230
|
+
emitResumed(): void;
|
|
231
|
+
emitError(error: Error): void;
|
|
232
|
+
emitMessage(message: QQBotInboundMessage): void;
|
|
233
|
+
emitInteraction(event: QQInteractionLike): void;
|
|
234
|
+
}
|
|
235
|
+
/** Minimal offline `QQStreamSession` double for fake-client tests. */
|
|
236
|
+
export declare class FakeStreamSession implements QQStreamSession {
|
|
237
|
+
updates: string[];
|
|
238
|
+
completed: boolean;
|
|
239
|
+
cancelled: boolean;
|
|
240
|
+
update(fullText: string): Promise<void>;
|
|
241
|
+
complete(): Promise<{
|
|
242
|
+
id: string;
|
|
243
|
+
timestamp: number;
|
|
244
|
+
} | undefined>;
|
|
245
|
+
cancel(): void;
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=sdk-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-client.d.ts","sourceRoot":"","sources":["../src/sdk-client.ts"],"names":[],"mappings":"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"}
|