@krischoichoi/channel-telegram 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 +129 -0
- package/lib/adapter.d.ts +94 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +417 -0
- package/lib/adapter.js.map +1 -0
- package/lib/api-error.d.ts +74 -0
- package/lib/api-error.d.ts.map +1 -0
- package/lib/api-error.js +103 -0
- package/lib/api-error.js.map +1 -0
- package/lib/config.d.ts +115 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +67 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +59 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +158 -0
- package/lib/definition.js.map +1 -0
- package/lib/inbound.d.ts +61 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +125 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +114 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest.d.ts +16 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +26 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +51 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +314 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-hydrator.d.ts +46 -0
- package/lib/media-hydrator.d.ts.map +1 -0
- package/lib/media-hydrator.js +85 -0
- package/lib/media-hydrator.js.map +1 -0
- package/lib/outbound.d.ts +42 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +215 -0
- package/lib/outbound.js.map +1 -0
- package/lib/render/html.d.ts +13 -0
- package/lib/render/html.d.ts.map +1 -0
- package/lib/render/html.js +110 -0
- package/lib/render/html.js.map +1 -0
- package/lib/render/index.d.ts +64 -0
- package/lib/render/index.d.ts.map +1 -0
- package/lib/render/index.js +76 -0
- package/lib/render/index.js.map +1 -0
- package/lib/render/markdown.d.ts +38 -0
- package/lib/render/markdown.d.ts.map +1 -0
- package/lib/render/markdown.js +187 -0
- package/lib/render/markdown.js.map +1 -0
- package/lib/render/plain.d.ts +43 -0
- package/lib/render/plain.d.ts.map +1 -0
- package/lib/render/plain.js +97 -0
- package/lib/render/plain.js.map +1 -0
- package/lib/render/segment.d.ts +39 -0
- package/lib/render/segment.d.ts.map +1 -0
- package/lib/render/segment.js +117 -0
- package/lib/render/segment.js.map +1 -0
- package/lib/rich-message.d.ts +75 -0
- package/lib/rich-message.d.ts.map +1 -0
- package/lib/rich-message.js +11 -0
- package/lib/rich-message.js.map +1 -0
- package/lib/rich-streaming-reply.d.ts +69 -0
- package/lib/rich-streaming-reply.d.ts.map +1 -0
- package/lib/rich-streaming-reply.js +207 -0
- package/lib/rich-streaming-reply.js.map +1 -0
- package/lib/streaming-reply.d.ts +98 -0
- package/lib/streaming-reply.d.ts.map +1 -0
- package/lib/streaming-reply.js +274 -0
- package/lib/streaming-reply.js.map +1 -0
- package/lib/transport.d.ts +38 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +91 -0
- package/lib/transport.js.map +1 -0
- package/lib/upstream.d.ts +174 -0
- package/lib/upstream.d.ts.map +1 -0
- package/lib/upstream.js +452 -0
- package/lib/upstream.js.map +1 -0
- package/package.json +52 -0
package/lib/inbound.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound processing: dedup window + structured mapping + media hydration +
|
|
3
|
+
* emit. Handles both `message` and `callback_query` updates.
|
|
4
|
+
*
|
|
5
|
+
* The getUpdates offset ack already prevents protocol-level redelivery; this
|
|
6
|
+
* dedup window is the adapter-level second layer for redelivery inside one
|
|
7
|
+
* cycle (e.g. a webhook-style retry with the same update_id).
|
|
8
|
+
*
|
|
9
|
+
* Media hydration runs after the duplicate check and before emit. The dedup
|
|
10
|
+
* entry is committed only after emit succeeds, so a failed dispatch remains
|
|
11
|
+
* retryable. Each binary part (`image`, `file`, `audio` — voice notes and
|
|
12
|
+
* audio messages — and `video`) with an opaque Telegram `resourceRef`
|
|
13
|
+
* (file_id) is resolved and downloaded through the platform upstream and its
|
|
14
|
+
* bytes are placed on `localData`. A download failure never blocks text
|
|
15
|
+
* delivery — the part keeps its `resourceRef` and records a stable
|
|
16
|
+
* `ingressFailure` code.
|
|
17
|
+
*
|
|
18
|
+
* ## Interactions (plan §5 / §12.2)
|
|
19
|
+
*
|
|
20
|
+
* A `callback_query` update maps to `interaction.received` with
|
|
21
|
+
* `action = callback_data` (untrusted, zod-validated, never interpreted by the
|
|
22
|
+
* adapter). The adapter immediately issues a best-effort `answerCallbackQuery`
|
|
23
|
+
* ACK so Telegram clears the button progress spinner — this must NOT block on
|
|
24
|
+
* agent resolution, so the ACK is fired and never gates the emit.
|
|
25
|
+
*/
|
|
26
|
+
import type { ChannelAdapterContext } from '@krischoichoi/channel-core';
|
|
27
|
+
import { type TelegramFileResolver } from './media-hydrator.js';
|
|
28
|
+
import { type TelegramInboundMeta } from './mapper.js';
|
|
29
|
+
export interface InboundProcessorOptions {
|
|
30
|
+
ctx: ChannelAdapterContext;
|
|
31
|
+
meta: TelegramInboundMeta;
|
|
32
|
+
dedupEnabled: boolean;
|
|
33
|
+
dedupWindowMs: number;
|
|
34
|
+
/** Platform file resolver used to hydrate image/file `resourceRef` parts. */
|
|
35
|
+
files?: TelegramFileResolver;
|
|
36
|
+
/** Hard byte cap for one inbound media download. */
|
|
37
|
+
maxDownloadBytes?: number;
|
|
38
|
+
/** Injectable clock (tests). */
|
|
39
|
+
now?: () => number;
|
|
40
|
+
/**
|
|
41
|
+
* Best-effort `answerCallbackQuery` ACK for callback_query updates. The
|
|
42
|
+
* adapter wires this to the upstream; the call is fired immediately and never
|
|
43
|
+
* gates the emit (plan §12.2).
|
|
44
|
+
*/
|
|
45
|
+
ackCallback?: (callbackQueryId: string) => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export declare class InboundProcessor {
|
|
48
|
+
private readonly options;
|
|
49
|
+
private readonly now;
|
|
50
|
+
/** dedup key -> last-seen timestamp, pruned on every handle. */
|
|
51
|
+
private readonly seen;
|
|
52
|
+
constructor(options: InboundProcessorOptions);
|
|
53
|
+
/** Process one raw update from the upstream; dedup, hydrate, then emit. */
|
|
54
|
+
handle(raw: unknown): Promise<void>;
|
|
55
|
+
/** Map + ACK + emit a callback_query interaction. */
|
|
56
|
+
private handleCallbackQuery;
|
|
57
|
+
/** Map + hydrate + emit a message update. */
|
|
58
|
+
private handleMessage;
|
|
59
|
+
private prune;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAgDrB,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,qBAAqB,CAAC;IAC3B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D;AAED,qBAAa,gBAAgB;IAKf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;gBAErB,OAAO,EAAE,uBAAuB;IAI7D,2EAA2E;IACrE,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BzC,qDAAqD;YACvC,mBAAmB;IAoBjC,6CAA6C;YAC/B,aAAa;IAkB3B,OAAO,CAAC,KAAK;CAKd"}
|
package/lib/inbound.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { hydrateTelegramParts } from './media-hydrator.js';
|
|
2
|
+
import { dedupKey, isCallbackQueryUpdate, mapCallbackQuery, mapInbound, TelegramInboundValidationError, } from './mapper.js';
|
|
3
|
+
/** Compact per-part summary for inbound message logs (debug diagnostics). */
|
|
4
|
+
function summarizeParts(parts) {
|
|
5
|
+
return parts.map((part) => {
|
|
6
|
+
switch (part.type) {
|
|
7
|
+
case 'text':
|
|
8
|
+
return { type: 'text', text: part.text.slice(0, 80) };
|
|
9
|
+
case 'image':
|
|
10
|
+
return {
|
|
11
|
+
type: 'image',
|
|
12
|
+
url: part.url,
|
|
13
|
+
resourceRef: part.resourceRef,
|
|
14
|
+
mimeType: part.mimeType,
|
|
15
|
+
localDataBytes: part.localData?.byteLength,
|
|
16
|
+
ingressFailure: part.ingressFailure,
|
|
17
|
+
};
|
|
18
|
+
case 'file':
|
|
19
|
+
return {
|
|
20
|
+
type: 'file',
|
|
21
|
+
name: part.name,
|
|
22
|
+
size: part.size,
|
|
23
|
+
mimeType: part.mimeType,
|
|
24
|
+
localDataBytes: part.localData?.byteLength,
|
|
25
|
+
ingressFailure: part.ingressFailure,
|
|
26
|
+
};
|
|
27
|
+
case 'audio':
|
|
28
|
+
return {
|
|
29
|
+
type: 'audio',
|
|
30
|
+
durationMs: part.durationMs,
|
|
31
|
+
mimeType: part.mimeType,
|
|
32
|
+
localDataBytes: part.localData?.byteLength,
|
|
33
|
+
ingressFailure: part.ingressFailure,
|
|
34
|
+
};
|
|
35
|
+
case 'video':
|
|
36
|
+
return {
|
|
37
|
+
type: 'video',
|
|
38
|
+
durationMs: part.durationMs,
|
|
39
|
+
mimeType: part.mimeType,
|
|
40
|
+
localDataBytes: part.localData?.byteLength,
|
|
41
|
+
ingressFailure: part.ingressFailure,
|
|
42
|
+
};
|
|
43
|
+
default:
|
|
44
|
+
return { type: part.type };
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export class InboundProcessor {
|
|
49
|
+
options;
|
|
50
|
+
now;
|
|
51
|
+
/** dedup key -> last-seen timestamp, pruned on every handle. */
|
|
52
|
+
seen = new Map();
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.options = options;
|
|
55
|
+
this.now = options.now ?? Date.now;
|
|
56
|
+
}
|
|
57
|
+
/** Process one raw update from the upstream; dedup, hydrate, then emit. */
|
|
58
|
+
async handle(raw) {
|
|
59
|
+
const key = dedupKey(raw);
|
|
60
|
+
if (this.options.dedupEnabled) {
|
|
61
|
+
const now = this.now();
|
|
62
|
+
const last = this.seen.get(key);
|
|
63
|
+
if (last !== undefined && now - last < this.options.dedupWindowMs) {
|
|
64
|
+
this.options.ctx.logger.debug(`[channel-telegram] dropped duplicate update '${key}'`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.prune(now);
|
|
68
|
+
}
|
|
69
|
+
if (isCallbackQueryUpdate(raw)) {
|
|
70
|
+
await this.handleCallbackQuery(raw);
|
|
71
|
+
if (this.options.dedupEnabled)
|
|
72
|
+
this.seen.set(key, this.now());
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await this.handleMessage(raw);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (!(error instanceof TelegramInboundValidationError))
|
|
80
|
+
throw error;
|
|
81
|
+
// Invalid identities must not enter the contract or block the polling
|
|
82
|
+
// cursor forever. This is intentionally a silent protocol-level drop.
|
|
83
|
+
this.options.ctx.logger.warn('[channel-telegram] dropped invalid inbound update');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (this.options.dedupEnabled)
|
|
87
|
+
this.seen.set(key, this.now());
|
|
88
|
+
}
|
|
89
|
+
/** Map + ACK + emit a callback_query interaction. */
|
|
90
|
+
async handleCallbackQuery(raw) {
|
|
91
|
+
const event = mapCallbackQuery(raw, this.options.meta);
|
|
92
|
+
// Immediate best-effort ACK (clears the button spinner). It must never
|
|
93
|
+
// block on agent resolution, so any failure is logged, not propagated to
|
|
94
|
+
// the emit.
|
|
95
|
+
if (this.options.ackCallback) {
|
|
96
|
+
void this.options.ackCallback(event.interactionId).catch((error) => {
|
|
97
|
+
this.options.ctx.logger.warn('[channel-telegram] answerCallbackQuery failed', error instanceof Error ? error.message : error);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
this.options.ctx.logger.info(`[channel-telegram] inbound interaction ${event.interactionId} from ${event.sender.id} in ${event.conversation.id}`, { action: event.action.slice(0, 80) });
|
|
101
|
+
await this.options.ctx.emit(event);
|
|
102
|
+
}
|
|
103
|
+
/** Map + hydrate + emit a message update. */
|
|
104
|
+
async handleMessage(raw) {
|
|
105
|
+
const event = mapInbound(raw, this.options.meta);
|
|
106
|
+
if (this.options.files) {
|
|
107
|
+
await hydrateTelegramParts(event.message.content, this.options.files, {
|
|
108
|
+
maxBytes: this.options.maxDownloadBytes ?? 20 * 1024 * 1024,
|
|
109
|
+
signal: this.options.ctx.signal,
|
|
110
|
+
logger: this.options.ctx.logger,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Inbound message log (debug diagnostics): visible in web:debug with
|
|
114
|
+
// DSH_CHANNELS_DEBUG=1, shows mapped parts incl. hydration result.
|
|
115
|
+
this.options.ctx.logger.info(`[channel-telegram] inbound message ${event.message.id} from ${event.sender.id} in ${event.conversation.id}`, { parts: summarizeParts(event.message.content) });
|
|
116
|
+
await this.options.ctx.emit(event);
|
|
117
|
+
}
|
|
118
|
+
prune(now) {
|
|
119
|
+
for (const [key, ts] of this.seen) {
|
|
120
|
+
if (now - ts >= this.options.dedupWindowMs)
|
|
121
|
+
this.seen.delete(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=inbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @krischoichoi/channel-telegram — Telegram Bot API channel adapter for DeepSeek
|
|
3
|
+
* Harness.
|
|
4
|
+
*
|
|
5
|
+
* A fifth official channel built on the same Channel Contract as Weixin / QQ /
|
|
6
|
+
* DingTalk / Lark. The Bot API token is resolved through `ctx.credentials`
|
|
7
|
+
* (`tokenRef`, default `TELEGRAM_BOT_TOKEN`) — the secret value never lives in
|
|
8
|
+
* profile config, logs, or fixtures.
|
|
9
|
+
*
|
|
10
|
+
* Lifecycle: when the Channel Control Plane (`ctx.channelControl`) is present,
|
|
11
|
+
* apply() registers a `ChannelDefinition` ('telegram'); the control plane
|
|
12
|
+
* decides when to instantiate/mount the adapter (headless auto-start). When it
|
|
13
|
+
* is absent (standalone / older harness), apply() falls back to resolving the
|
|
14
|
+
* token credential and mounting directly — never throwing when the channel is
|
|
15
|
+
* merely unconfigured.
|
|
16
|
+
*
|
|
17
|
+
* Streaming is `edit`: one message is sent and then edited in place with
|
|
18
|
+
* `editMessageText` as full-text previews arrive. Set
|
|
19
|
+
* `config.streaming.enabled: false` to force the buffered send-once strategy.
|
|
20
|
+
*/
|
|
21
|
+
import { type Context } from '@deepseek-ai/cordis';
|
|
22
|
+
import { Config, TELEGRAM_BOT_TOKEN_REF } from './config.js';
|
|
23
|
+
import type { TelegramConfig, TelegramConfigLive } from './config.js';
|
|
24
|
+
import { type TelegramAdapterDeps } from './adapter.js';
|
|
25
|
+
export declare const name = "channel-telegram";
|
|
26
|
+
export declare const inject: string[];
|
|
27
|
+
export { Config, TELEGRAM_BOT_TOKEN_REF };
|
|
28
|
+
export { TelegramAdapter, type TelegramAdapterDeps } from './adapter.js';
|
|
29
|
+
export { createTelegramDefinition, type TelegramCredentialSeam } from './definition.js';
|
|
30
|
+
export { InboundProcessor } from './inbound.js';
|
|
31
|
+
export { hydrateTelegramParts, type TelegramFileResolver, type TelegramMediaHydratorOptions } from './media-hydrator.js';
|
|
32
|
+
export { OutboundSender, actionsToReplyMarkup } from './outbound.js';
|
|
33
|
+
export { TelegramStreamingReply, makePreview } from './streaming-reply.js';
|
|
34
|
+
export { TelegramRichStreamingReply } from './rich-streaming-reply.js';
|
|
35
|
+
export { TelegramApiError, classifyTelegramError, type TelegramErrorKind, type TelegramErrorParameters, } from './api-error.js';
|
|
36
|
+
export { RICH_MESSAGE_MAX_UTF8, RICH_MESSAGE_MAX_BLOCKS, RICH_MESSAGE_MAX_NESTING, RICH_MESSAGE_MAX_MEDIA, RICH_MESSAGE_MAX_TABLE_COLUMNS, REGULAR_MESSAGE_MAX, CAPTION_MAX, type InputRichMessage, type TelegramInlineKeyboardButton, type TelegramInlineKeyboardMarkup, type TelegramInlineKeyboardRow, type TelegramReplyMarkup, type AnswerCallbackQueryParams, } from './rich-message.js';
|
|
37
|
+
export * as render from './render/index.js';
|
|
38
|
+
export { renderMessage, sendWithFallback, resolveMode, isFormattingFailure, type RenderMode, type RenderOptions, type RenderPlan, } from './render/index.js';
|
|
39
|
+
export type { TelegramFormattingConfig, TelegramConfigLive } from './config.js';
|
|
40
|
+
export { mapInbound, mapCallbackQuery, isCallbackQueryUpdate, dedupKey, simpleHash, type TelegramInboundMeta } from './mapper.js';
|
|
41
|
+
export { HttpTelegramUpstream, type TelegramUpstream, type TelegramBotUser, type TelegramMedia, type TelegramSendOptions, type TelegramFormatOptions, type TelegramSentMessage, type TelegramFileInfo, type TelegramDownloadedFile, } from './upstream.js';
|
|
42
|
+
export { FetchTransport, type HttpTransport, type HttpRequestInit, type HttpBinaryResponse, } from './transport.js';
|
|
43
|
+
export { manifest, type TelegramManifest } from './manifest.js';
|
|
44
|
+
/**
|
|
45
|
+
* Entry plugin apply. Accepts both the activation-time live shape (volatile
|
|
46
|
+
* fields resolve to `Volatile<T>` handles under Harness 0.2.0) and a plain
|
|
47
|
+
* snapshot (tests / programmatic mounting); plain fields pass through
|
|
48
|
+
* `resolveVolatileConfig` untouched. The shape is validated by Schemastery at
|
|
49
|
+
* the Loader boundary via the attached `Config` schema.
|
|
50
|
+
*/
|
|
51
|
+
export declare function apply(ctx: Context, rawConfig: TelegramConfig | TelegramConfigLive, deps?: TelegramAdapterDeps): void;
|
|
52
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGzE,eAAO,MAAM,IAAI,qBAAqB,CAAC;AACvC,eAAO,MAAM,MAAM,EAAE,MAAM,EAAgC,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,KAAK,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,KAAK,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AACzH,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,8BAA8B,EAC9B,mBAAmB,EACnB,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClI,OAAO,EACL,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAoChE;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,cAAc,GAAG,kBAAkB,EAC9C,IAAI,GAAE,mBAAwB,GAC7B,IAAI,CAsDN"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @krischoichoi/channel-telegram — Telegram Bot API channel adapter for DeepSeek
|
|
3
|
+
* Harness.
|
|
4
|
+
*
|
|
5
|
+
* A fifth official channel built on the same Channel Contract as Weixin / QQ /
|
|
6
|
+
* DingTalk / Lark. The Bot API token is resolved through `ctx.credentials`
|
|
7
|
+
* (`tokenRef`, default `TELEGRAM_BOT_TOKEN`) — the secret value never lives in
|
|
8
|
+
* profile config, logs, or fixtures.
|
|
9
|
+
*
|
|
10
|
+
* Lifecycle: when the Channel Control Plane (`ctx.channelControl`) is present,
|
|
11
|
+
* apply() registers a `ChannelDefinition` ('telegram'); the control plane
|
|
12
|
+
* decides when to instantiate/mount the adapter (headless auto-start). When it
|
|
13
|
+
* is absent (standalone / older harness), apply() falls back to resolving the
|
|
14
|
+
* token credential and mounting directly — never throwing when the channel is
|
|
15
|
+
* merely unconfigured.
|
|
16
|
+
*
|
|
17
|
+
* Streaming is `edit`: one message is sent and then edited in place with
|
|
18
|
+
* `editMessageText` as full-text previews arrive. Set
|
|
19
|
+
* `config.streaming.enabled: false` to force the buffered send-once strategy.
|
|
20
|
+
*/
|
|
21
|
+
import {} from '@deepseek-ai/cordis';
|
|
22
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
23
|
+
import { mountChannelAdapter, resolveVolatileConfig, resolveVolatileValue } from '@krischoichoi/channel-core';
|
|
24
|
+
import { Config, TELEGRAM_BOT_TOKEN_REF } from './config.js';
|
|
25
|
+
import { TelegramAdapter } from './adapter.js';
|
|
26
|
+
import { createTelegramDefinition } from './definition.js';
|
|
27
|
+
export const name = 'channel-telegram';
|
|
28
|
+
export const inject = ['channels', 'credentials'];
|
|
29
|
+
export { Config, TELEGRAM_BOT_TOKEN_REF };
|
|
30
|
+
export { TelegramAdapter } from './adapter.js';
|
|
31
|
+
export { createTelegramDefinition } from './definition.js';
|
|
32
|
+
export { InboundProcessor } from './inbound.js';
|
|
33
|
+
export { hydrateTelegramParts } from './media-hydrator.js';
|
|
34
|
+
export { OutboundSender, actionsToReplyMarkup } from './outbound.js';
|
|
35
|
+
export { TelegramStreamingReply, makePreview } from './streaming-reply.js';
|
|
36
|
+
export { TelegramRichStreamingReply } from './rich-streaming-reply.js';
|
|
37
|
+
export { TelegramApiError, classifyTelegramError, } from './api-error.js';
|
|
38
|
+
export { RICH_MESSAGE_MAX_UTF8, RICH_MESSAGE_MAX_BLOCKS, RICH_MESSAGE_MAX_NESTING, RICH_MESSAGE_MAX_MEDIA, RICH_MESSAGE_MAX_TABLE_COLUMNS, REGULAR_MESSAGE_MAX, CAPTION_MAX, } from './rich-message.js';
|
|
39
|
+
export * as render from './render/index.js';
|
|
40
|
+
export { renderMessage, sendWithFallback, resolveMode, isFormattingFailure, } from './render/index.js';
|
|
41
|
+
export { mapInbound, mapCallbackQuery, isCallbackQueryUpdate, dedupKey, simpleHash } from './mapper.js';
|
|
42
|
+
export { HttpTelegramUpstream, } from './upstream.js';
|
|
43
|
+
export { FetchTransport, } from './transport.js';
|
|
44
|
+
export { manifest } from './manifest.js';
|
|
45
|
+
/**
|
|
46
|
+
* One-time legacy plaintext `config.token` -> credential reference migration.
|
|
47
|
+
* The value is written to the credentials seam once, then the plaintext is
|
|
48
|
+
* stripped from the in-memory config. The secret value is never logged.
|
|
49
|
+
*/
|
|
50
|
+
function migrateLegacyToken(ctx, config) {
|
|
51
|
+
const legacy = config.token;
|
|
52
|
+
if (typeof legacy !== 'string' || legacy.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
const ref = config.tokenRef ?? TELEGRAM_BOT_TOKEN_REF;
|
|
55
|
+
void ctx.credentials
|
|
56
|
+
.set(credentialRef(ref), legacy)
|
|
57
|
+
.then(() => {
|
|
58
|
+
ctx.logger('channel-telegram').info(`[channel-telegram] legacy plaintext token migrated into credentials ref "${ref}"`);
|
|
59
|
+
})
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
ctx.logger('channel-telegram').warn('[channel-telegram] legacy plaintext token migration failed', error);
|
|
62
|
+
});
|
|
63
|
+
delete config.token;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Entry plugin apply. Accepts both the activation-time live shape (volatile
|
|
67
|
+
* fields resolve to `Volatile<T>` handles under Harness 0.2.0) and a plain
|
|
68
|
+
* snapshot (tests / programmatic mounting); plain fields pass through
|
|
69
|
+
* `resolveVolatileConfig` untouched. The shape is validated by Schemastery at
|
|
70
|
+
* the Loader boundary via the attached `Config` schema.
|
|
71
|
+
*/
|
|
72
|
+
export function apply(ctx, rawConfig, deps = {}) {
|
|
73
|
+
const config = resolveVolatileConfig(rawConfig);
|
|
74
|
+
const credentialsCtx = ctx;
|
|
75
|
+
migrateLegacyToken(credentialsCtx, config);
|
|
76
|
+
const ref = config.tokenRef ?? TELEGRAM_BOT_TOKEN_REF;
|
|
77
|
+
const control = ctx.get('channelControl');
|
|
78
|
+
if (control) {
|
|
79
|
+
// Control plane present (doc §25/§27): register the definition EVEN when
|
|
80
|
+
// disabled — the plane owns adapter instantiation + headless auto-start,
|
|
81
|
+
// and a disabled definition must stay visible so the Web control plane can
|
|
82
|
+
// re-enable it later (doc §19/§20).
|
|
83
|
+
const settings = ctx.get('settings');
|
|
84
|
+
control.definitions.register(createTelegramDefinition({
|
|
85
|
+
config,
|
|
86
|
+
deps,
|
|
87
|
+
credentials: {
|
|
88
|
+
resolve: (name) => credentialsCtx.credentials.resolve(credentialRef(name)),
|
|
89
|
+
describe: (name) => credentialsCtx.credentials.describe(credentialRef(name)),
|
|
90
|
+
set: (name, value) => credentialsCtx.credentials.set(credentialRef(name), value),
|
|
91
|
+
},
|
|
92
|
+
persistEnabled: (enabled) => settings?.update('channels-telegram', { enabled }) ?? Promise.resolve(),
|
|
93
|
+
}));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Legacy fallback (standalone, no control plane): mount directly. There is no
|
|
97
|
+
// directory/control surface to re-enable a disabled channel, so the config
|
|
98
|
+
// `enabled` gate still applies (doc §20). Unconfigured token must NOT throw
|
|
99
|
+
// (doc §25) — log a warning and stay idle.
|
|
100
|
+
if (!resolveVolatileValue(config.enabled))
|
|
101
|
+
return;
|
|
102
|
+
ctx.effect(async () => {
|
|
103
|
+
const token = deps.token ?? (await credentialsCtx.credentials.resolve(credentialRef(ref)))?.value;
|
|
104
|
+
if (!token) {
|
|
105
|
+
ctx.logger('channel-telegram').warn(`[channel-telegram] telegram credential "${ref}" is not configured; adapter not mounted`);
|
|
106
|
+
return () => { };
|
|
107
|
+
}
|
|
108
|
+
mountChannelAdapter(ctx, new TelegramAdapter(config, { ...deps, token }), (signal) => ctx.channels.createAdapterContext({ channelId: 'telegram', signal }));
|
|
109
|
+
// The mount owns the adapter lifecycle; this outer effect only scopes the
|
|
110
|
+
// async credential resolution, so its disposer is a no-op.
|
|
111
|
+
return () => { };
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAgB,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAE9G,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,eAAe,EAA4B,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAA+B,MAAM,iBAAiB,CAAC;AAExF,MAAM,CAAC,MAAM,IAAI,GAAG,kBAAkB,CAAC;AACvC,MAAM,CAAC,MAAM,MAAM,GAAa,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;AAC1C,OAAO,EAAE,eAAe,EAA4B,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAA+B,MAAM,iBAAiB,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAgE,MAAM,qBAAqB,CAAC;AACzH,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,qBAAqB,GAGtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,8BAA8B,EAC9B,mBAAmB,EACnB,WAAW,GAOZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,mBAAmB,GAIpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,QAAQ,EAAE,UAAU,EAA4B,MAAM,aAAa,CAAC;AAClI,OAAO,EACL,oBAAoB,GASrB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,cAAc,GAIf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAyB,MAAM,eAAe,CAAC;AAgBhE;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,GAA2B,EAAE,MAAsB;IAC7E,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,IAAI,sBAAsB,CAAC;IACtD,KAAK,GAAG,CAAC,WAAW;SACjB,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;SAC/B,IAAI,CAAC,GAAG,EAAE;QACT,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,4EAA4E,GAAG,GAAG,CAAC,CAAC;IAC1H,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACf,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,4DAA4D,EAAE,KAAK,CAAC,CAAC;IAC3G,CAAC,CAAC,CAAC;IACL,OAAQ,MAA6B,CAAC,KAAK,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CACnB,GAAY,EACZ,SAA8C,EAC9C,OAA4B,EAAE;IAE9B,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAmB,CAAC;IAClE,MAAM,cAAc,GAAG,GAA6B,CAAC;IACrD,kBAAkB,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,IAAI,sBAAsB,CAAC;IAEtD,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAE3B,CAAC;IAEd,IAAI,OAAO,EAAE,CAAC;QACZ,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,oCAAoC;QACpC,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,UAAU,CAA8B,CAAC;QAClE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAC1B,wBAAwB,CAAC;YACvB,MAAM;YACN,IAAI;YACJ,WAAW,EAAE;gBACX,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAC1E,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBAC5E,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,WAAW,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;aACjF;YACD,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE;SACrG,CAAC,CACH,CAAC;QACF,OAAO;IACT,CAAC;IAED,8EAA8E;IAC9E,2EAA2E;IAC3E,4EAA4E;IAC5E,2CAA2C;IAC3C,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,OAAO;IAClD,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;QAClG,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CACjC,2CAA2C,GAAG,0CAA0C,CACzF,CAAC;YACF,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;QAClB,CAAC;QAED,mBAAmB,CACjB,GAAG,EACH,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,EAC/C,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CACjF,CAAC;QACF,0EAA0E;QAC1E,2DAA2D;QAC3D,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TelegramUpstreamManifest {
|
|
2
|
+
reference: string;
|
|
3
|
+
testedVersion: string;
|
|
4
|
+
versionRange: string;
|
|
5
|
+
strategy: 'source';
|
|
6
|
+
}
|
|
7
|
+
export interface TelegramManifest {
|
|
8
|
+
id: 'telegram';
|
|
9
|
+
adapterVersion: string;
|
|
10
|
+
upstream: TelegramUpstreamManifest;
|
|
11
|
+
sdk: undefined;
|
|
12
|
+
status: 'experimental';
|
|
13
|
+
}
|
|
14
|
+
/** M5 manifest: direct HTTP protocol, no SDK. */
|
|
15
|
+
export declare const manifest: TelegramManifest;
|
|
16
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,UAAU,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,wBAAwB,CAAC;IACnC,GAAG,EAAE,SAAS,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,iDAAiD;AACjD,eAAO,MAAM,QAAQ,EAAE,gBAWtB,CAAC"}
|
package/lib/manifest.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram upstream compatibility manifest (M4 pattern).
|
|
3
|
+
*
|
|
4
|
+
* Records the upstream reference and tested version so `channels doctor` and
|
|
5
|
+
* the upgrade pipeline can govern compatibility without re-verifying by hand.
|
|
6
|
+
*
|
|
7
|
+
* Strategy choice: 'source' — the adapter speaks the Telegram Bot API HTTP
|
|
8
|
+
* protocol directly (`/bot<token>/...`), so no platform SDK is consumed.
|
|
9
|
+
* Offline contract and fixture coverage is not a live-platform verification,
|
|
10
|
+
* so the adapter remains experimental until the Telegram live gate passes.
|
|
11
|
+
*/
|
|
12
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
13
|
+
/** M5 manifest: direct HTTP protocol, no SDK. */
|
|
14
|
+
export const manifest = {
|
|
15
|
+
id: 'telegram',
|
|
16
|
+
adapterVersion: pkg.version,
|
|
17
|
+
upstream: {
|
|
18
|
+
reference: 'Telegram Bot API (https://core.telegram.org/bots/api)',
|
|
19
|
+
testedVersion: '10.2',
|
|
20
|
+
versionRange: '>=10.2',
|
|
21
|
+
strategy: 'source',
|
|
22
|
+
},
|
|
23
|
+
sdk: undefined,
|
|
24
|
+
status: 'experimental',
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAiBxD,iDAAiD;AACjD,MAAM,CAAC,MAAM,QAAQ,GAAqB;IACxC,EAAE,EAAE,UAAU;IACd,cAAc,EAAE,GAAG,CAAC,OAAO;IAC3B,QAAQ,EAAE;QACR,SAAS,EAAE,uDAAuD;QAClE,aAAa,EAAE,MAAM;QACrB,YAAY,EAAE,QAAQ;QACtB,QAAQ,EAAE,QAAQ;KACnB;IACD,GAAG,EAAE,SAAS;IACd,MAAM,EAAE,cAAc;CACvB,CAAC"}
|
package/lib/mapper.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure payload mapping (no I/O): Telegram Bot API updates → Channel Contract.
|
|
3
|
+
*
|
|
4
|
+
* Raw updates only ever ride along in `event.raw` for debugging — core and
|
|
5
|
+
* the harness bridge never depend on their shape (red line 6).
|
|
6
|
+
*
|
|
7
|
+
* Telegram update shape (Bot API 10.2):
|
|
8
|
+
* ```json
|
|
9
|
+
* { "update_id": 123,
|
|
10
|
+
* "message": { "message_id": 456, "date": 1700000000,
|
|
11
|
+
* "chat": { "id": 789, "type": "private" },
|
|
12
|
+
* "from": { "id": 111, "first_name": "Alice" },
|
|
13
|
+
* "text": "hello" } }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `chat.type` 'private' → dm conversation; 'group'/'supergroup' → group
|
|
17
|
+
* conversation keyed by the chat id. All ids are numeric on the wire and are
|
|
18
|
+
* stringified into the branded contract identities. The bot token is never
|
|
19
|
+
* part of any mapped event.
|
|
20
|
+
*/
|
|
21
|
+
import type { AccountId, ChannelId, MessageReceived } from '@krischoichoi/channel-core';
|
|
22
|
+
import type { InteractionReceived } from '@krischoichoi/channel-core';
|
|
23
|
+
export interface TelegramInboundMeta {
|
|
24
|
+
channel: ChannelId;
|
|
25
|
+
accountId: AccountId;
|
|
26
|
+
/** Trusted bot identity resolved from getMe before polling starts. */
|
|
27
|
+
bot?: {
|
|
28
|
+
id: number;
|
|
29
|
+
username?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** An untrusted Telegram update did not satisfy the canonical identity schema. */
|
|
33
|
+
export declare class TelegramInboundValidationError extends Error {
|
|
34
|
+
constructor(message: string);
|
|
35
|
+
}
|
|
36
|
+
/** Stable hash for ids when the platform omits them. */
|
|
37
|
+
export declare function simpleHash(input: string): string;
|
|
38
|
+
/** Map one raw Telegram update into the stable channel event shape. */
|
|
39
|
+
export declare function mapInbound(raw: unknown, meta: TelegramInboundMeta): MessageReceived;
|
|
40
|
+
/** Dedup identity for raw updates (update_id, then message_id). */
|
|
41
|
+
export declare function dedupKey(raw: unknown): string;
|
|
42
|
+
/** Whether a raw update carries a `callback_query` (message-bearing). */
|
|
43
|
+
export declare function isCallbackQueryUpdate(raw: unknown): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Map a validated `callback_query` update to `InteractionReceived`. Always run
|
|
46
|
+
* `isCallbackQueryUpdate` first; this function re-parses defensively and throws
|
|
47
|
+
* a clear error for a malformed tolerance so the adapter fails closed rather
|
|
48
|
+
* than emitting a fabricated interaction.
|
|
49
|
+
*/
|
|
50
|
+
export declare function mapCallbackQuery(raw: unknown, meta: TelegramInboundMeta): InteractionReceived;
|
|
51
|
+
//# sourceMappingURL=mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mapper.d.ts","sourceRoot":"","sources":["../src/mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EAIT,eAAe,EAEhB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAItE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;IACrB,sEAAsE;IACtE,GAAG,CAAC,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,kFAAkF;AAClF,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,OAAO,EAAE,MAAM;CAI5B;AAqGD,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,eAAe,CA4CnF;AAoGD,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAS7C;AAwBD,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,CA4B7F"}
|