@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.
Files changed (54) hide show
  1. package/README.md +93 -0
  2. package/lib/adapter.d.ts +76 -0
  3. package/lib/adapter.d.ts.map +1 -0
  4. package/lib/adapter.js +299 -0
  5. package/lib/adapter.js.map +1 -0
  6. package/lib/config.d.ts +62 -0
  7. package/lib/config.d.ts.map +1 -0
  8. package/lib/config.js +43 -0
  9. package/lib/config.js.map +1 -0
  10. package/lib/definition.d.ts +75 -0
  11. package/lib/definition.d.ts.map +1 -0
  12. package/lib/definition.js +232 -0
  13. package/lib/definition.js.map +1 -0
  14. package/lib/image-hydrator.d.ts +13 -0
  15. package/lib/image-hydrator.d.ts.map +1 -0
  16. package/lib/image-hydrator.js +13 -0
  17. package/lib/image-hydrator.js.map +1 -0
  18. package/lib/inbound.d.ts +49 -0
  19. package/lib/inbound.d.ts.map +1 -0
  20. package/lib/inbound.js +98 -0
  21. package/lib/inbound.js.map +1 -0
  22. package/lib/index.d.ts +53 -0
  23. package/lib/index.d.ts.map +1 -0
  24. package/lib/index.js +113 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/interaction-mapper.d.ts +43 -0
  27. package/lib/interaction-mapper.d.ts.map +1 -0
  28. package/lib/interaction-mapper.js +91 -0
  29. package/lib/interaction-mapper.js.map +1 -0
  30. package/lib/manifest.d.ts +22 -0
  31. package/lib/manifest.d.ts.map +1 -0
  32. package/lib/manifest.js +35 -0
  33. package/lib/manifest.js.map +1 -0
  34. package/lib/mapper.d.ts +38 -0
  35. package/lib/mapper.d.ts.map +1 -0
  36. package/lib/mapper.js +109 -0
  37. package/lib/mapper.js.map +1 -0
  38. package/lib/media-hydrator.d.ts +61 -0
  39. package/lib/media-hydrator.d.ts.map +1 -0
  40. package/lib/media-hydrator.js +107 -0
  41. package/lib/media-hydrator.js.map +1 -0
  42. package/lib/outbound.d.ts +41 -0
  43. package/lib/outbound.d.ts.map +1 -0
  44. package/lib/outbound.js +111 -0
  45. package/lib/outbound.js.map +1 -0
  46. package/lib/sdk-client.d.ts +247 -0
  47. package/lib/sdk-client.d.ts.map +1 -0
  48. package/lib/sdk-client.js +312 -0
  49. package/lib/sdk-client.js.map +1 -0
  50. package/lib/streaming-reply.d.ts +19 -0
  51. package/lib/streaming-reply.d.ts.map +1 -0
  52. package/lib/streaming-reply.js +26 -0
  53. package/lib/streaming-reply.js.map +1 -0
  54. 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"}
@@ -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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"}