@nubemclaw/channel-telegram 1.2.2

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.
@@ -0,0 +1,59 @@
1
+ import { type Channel } from "@nubemclaw/channel-sdk";
2
+ import { Bot } from "grammy";
3
+ import type { TelegramChannelConfig } from "./config.js";
4
+ import { type TelegramTransport } from "./transport.js";
5
+ /**
6
+ * Telegram channel implementation (Phase 13, ADR-0020 §1 — library
7
+ * route). Wraps **grammy** for the Bot API + long-polling loop, and
8
+ * `@grammyjs/transformer-throttler` for outbound rate-limit. The
9
+ * channel does NOT use the SDK's `PollingTransport` because grammy
10
+ * already owns the polling lifecycle (via `bot.start()`).
11
+ *
12
+ * Precedent: OpenClaw `extensions/telegram/` uses the same stack
13
+ * (`grammy@1.42.0` + `@grammyjs/transformer-throttler@1.2.1`); pinned
14
+ * to the same versions to share the upstream rodaje. Patterns adapted
15
+ * from OpenClaw `bot.runtime.ts:1-4` and `account-throttler.ts:1-21`.
16
+ *
17
+ * Three behaviours the channel guarantees (unchanged from the original
18
+ * raw implementation):
19
+ *
20
+ * 1. **Inbound is private-chat, text-only, allowlisted.** Updates
21
+ * from groups (chat.type !== "private"), media-only messages
22
+ * (no `text`), or senders not in `allowedUsers` are silently
23
+ * dropped with a debug log. F13's scope (ADR-0020 §8) is
24
+ * private-chat text; any other shape just doesn't reach the
25
+ * agent loop. Telegram receives `allowed_updates: ["message"]`
26
+ * so non-message updates never reach us in the first place.
27
+ *
28
+ * 2. **Outbound respects Telegram rate limits via apiThrottler.**
29
+ * `@grammyjs/transformer-throttler` is installed on the bot's
30
+ * api config — it honors the `retry_after` Telegram returns on
31
+ * 429 automatically (no manual bucket needed; OpenClaw's
32
+ * `account-throttler.ts` confirms this pattern).
33
+ *
34
+ * 3. **Mode transition is clean.** Polling mode calls
35
+ * `bot.api.deleteWebhook()` on start (Telegram rejects with 409
36
+ * if `getUpdates` runs while a webhook is registered). Webhook
37
+ * mode calls `bot.api.setWebhook(...)` with the secret token we
38
+ * verify against.
39
+ */
40
+ export interface CreateTelegramChannelOptions {
41
+ readonly token: string;
42
+ readonly config: TelegramChannelConfig;
43
+ /**
44
+ * Test seam: override the Bot constructor (the tests substitute a
45
+ * thin fake that records calls and serves canned `getMe` /
46
+ * `getUpdates` responses). Default is grammy's `Bot` configured
47
+ * with the operational transport (see `createTelegramTransport`).
48
+ */
49
+ readonly botFactory?: (token: string, fetcher: typeof globalThis.fetch | undefined) => Bot;
50
+ /**
51
+ * Test seam: override the operational transport. Tests pass
52
+ * `undefined` to let grammy use its default `globalThis.fetch`
53
+ * (which under Vitest's environment is fine — production paths get
54
+ * the hardened transport).
55
+ */
56
+ readonly transport?: TelegramTransport | null;
57
+ }
58
+ export declare const createTelegramChannel: (opts: CreateTelegramChannelOptions) => Channel;
59
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,OAAO,EAQb,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,GAAG,EAAgB,MAAM,QAAQ,CAAC;AAG3C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,UAAU,CAAC,KAAK,GAAG,SAAS,KAAK,GAAG,CAAC;IAC3F;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC/C;AAkBD,eAAO,MAAM,qBAAqB,GAAI,MAAM,4BAA4B,KAAG,OA4P1E,CAAC"}
@@ -0,0 +1,246 @@
1
+ import { apiThrottler } from "@grammyjs/transformer-throttler";
2
+ import { createAllowlist, createWebhookTransport, webhookVerifyOk, } from "@nubemclaw/channel-sdk";
3
+ import { err, nubemClawError } from "@nubemclaw/core";
4
+ import { Bot } from "grammy";
5
+ import { TelegramUpdateSchema } from "./api/schemas.js";
6
+ import { createTelegramTransport } from "./transport.js";
7
+ const CHANNEL_ID = "telegram";
8
+ const ALLOWED_UPDATES = ["message"];
9
+ const CAPABILITIES = {
10
+ transports: ["polling", "webhook"],
11
+ mediaTypes: ["text"],
12
+ supportsGroups: false,
13
+ rateLimits: {
14
+ // apiThrottler honors Telegram's documented limits + retry_after
15
+ // automatically — these values stay in capabilities for operator
16
+ // visibility, not for enforcement.
17
+ globalPerSec: 30,
18
+ perTargetPerMin: 20,
19
+ },
20
+ };
21
+ export const createTelegramChannel = (opts) => {
22
+ let deps = null;
23
+ let bot = null;
24
+ let pollingPromise = null;
25
+ let webhookReg = null;
26
+ // The operational transport owns its undici Agents; the channel
27
+ // owns this reference so `stop()` can release dispatchers cleanly.
28
+ let ownedTransport = null;
29
+ const allowlist = createAllowlist(opts.config.allowedUsers);
30
+ const requireDeps = () => {
31
+ if (deps === null)
32
+ throw new Error("telegram channel: not initialized — call init() first");
33
+ return deps;
34
+ };
35
+ const requireBot = () => {
36
+ if (bot === null)
37
+ throw new Error("telegram channel: not initialized — call init() first");
38
+ return bot;
39
+ };
40
+ /**
41
+ * Normalize a grammy `Context` (or a raw `TelegramUpdate` from the
42
+ * webhook path) into the SDK's `ChannelInboundEvent`. Returns
43
+ * `undefined` for any update F13 intentionally ignores. The caller
44
+ * logs the drop reason at debug level — no user-facing log at info
45
+ * because a chatty rejected user would spam the logs.
46
+ */
47
+ const normalize = (update, receivedAt) => {
48
+ const msg = update.message;
49
+ if (msg === undefined)
50
+ return undefined;
51
+ if (msg.chat.type !== "private") {
52
+ requireDeps().logger.debug({ channel: CHANNEL_ID, chatType: msg.chat.type }, "telegram inbound from non-private chat — dropping");
53
+ return undefined;
54
+ }
55
+ if (msg.text === undefined || msg.text === "")
56
+ return undefined;
57
+ const sender = msg.from;
58
+ if (sender === undefined)
59
+ return undefined;
60
+ const externalId = String(sender.id);
61
+ if (!allowlist.allows(externalId)) {
62
+ requireDeps().logger.info({ channel: CHANNEL_ID }, "telegram inbound from non-allowed user rejected");
63
+ return undefined;
64
+ }
65
+ return {
66
+ channel: CHANNEL_ID,
67
+ target: { kind: "chat", id: String(msg.chat.id) },
68
+ sender: {
69
+ externalId,
70
+ ...(sender.username !== undefined ? { displayName: sender.username } : {}),
71
+ },
72
+ content: { type: "text", text: msg.text },
73
+ receivedAt,
74
+ raw: update,
75
+ };
76
+ };
77
+ const dispatchUpdate = async (update) => {
78
+ const event = normalize(update, new Date().toISOString());
79
+ if (event === undefined)
80
+ return;
81
+ await requireDeps().onInbound(event);
82
+ };
83
+ return {
84
+ id: CHANNEL_ID,
85
+ capabilities: CAPABILITIES,
86
+ async init(d) {
87
+ deps = d;
88
+ // Operational transport: opts.transport === undefined → build
89
+ // the hardened default (undici Agent + allowH2:false + bounded
90
+ // pool + IPv4 fallback + proxy support). opts.transport === null
91
+ // → caller explicitly disables it (tests letting grammy use the
92
+ // global fetch). Either way, the result is the fetcher passed
93
+ // to grammy's `Bot` constructor.
94
+ if (opts.transport !== null) {
95
+ ownedTransport = opts.transport ?? createTelegramTransport({ logger: d.logger });
96
+ }
97
+ const fetcher = ownedTransport?.fetch;
98
+ const factory = opts.botFactory ??
99
+ ((token, f) => f !== undefined ? new Bot(token, { client: { fetch: f } }) : new Bot(token));
100
+ bot = factory(opts.token, fetcher);
101
+ // Compose the throttler transformer on the api so every outbound
102
+ // call (sendMessage, deleteWebhook, setWebhook, etc.) is rate-
103
+ // limited and respects Telegram's retry_after automatically.
104
+ // Pattern adopted from OpenClaw `account-throttler.ts`.
105
+ bot.api.config.use(apiThrottler());
106
+ // Inbound handler — grammy invokes this for every text message
107
+ // matching `allowed_updates`. We bridge to our normalizer +
108
+ // onInbound callback. The grammy update shape is structurally
109
+ // compatible with our zod-validated `TelegramUpdate`, so we
110
+ // safeParse to keep the boundary explicit.
111
+ bot.on("message", async (ctx) => {
112
+ const parsed = TelegramUpdateSchema.safeParse(ctx.update);
113
+ if (!parsed.success) {
114
+ d.logger.warn({ channel: CHANNEL_ID, issues: parsed.error.issues }, "telegram polled update failed schema validation — dropping");
115
+ return;
116
+ }
117
+ await dispatchUpdate(parsed.data);
118
+ });
119
+ if (opts.config.mode === "webhook") {
120
+ const secretToken = opts.config.webhook?.secretToken ?? "";
121
+ webhookReg = createWebhookTransport({
122
+ channel: CHANNEL_ID,
123
+ logger: d.logger,
124
+ verify: async (req) => {
125
+ const header = req.headers["x-telegram-bot-api-secret-token"];
126
+ const provided = Array.isArray(header) ? header[0] : header;
127
+ if (provided !== secretToken) {
128
+ return err(nubemClawError({
129
+ code: "auth.unauthorized",
130
+ message: "telegram webhook secret token mismatch",
131
+ }));
132
+ }
133
+ return webhookVerifyOk();
134
+ },
135
+ handle: async (body) => {
136
+ const parsed = TelegramUpdateSchema.safeParse(body);
137
+ if (!parsed.success) {
138
+ d.logger.warn({ channel: CHANNEL_ID, issues: parsed.error.issues }, "telegram webhook body failed schema validation — dropping");
139
+ return;
140
+ }
141
+ await dispatchUpdate(parsed.data);
142
+ },
143
+ });
144
+ }
145
+ },
146
+ async start() {
147
+ const b = requireBot();
148
+ const d = requireDeps();
149
+ if (opts.config.mode === "polling") {
150
+ // Drop any registered webhook so getUpdates does not 409.
151
+ try {
152
+ await b.api.deleteWebhook({ drop_pending_updates: false });
153
+ }
154
+ catch (err) {
155
+ d.logger.warn({ channel: CHANNEL_ID, err }, "telegram: deleteWebhook on start failed (continuing into polling anyway)");
156
+ }
157
+ // `bot.start()` is the polling loop — the returned promise
158
+ // resolves when the loop terminates cleanly (someone called
159
+ // `bot.stop()`) and rejects if it dies mid-flight (network
160
+ // drop persisting beyond grammy's internal retries, bot
161
+ // kicked, persistent 5xx). The channel propagates this
162
+ // promise upward so the `ChannelManager` can tap it via
163
+ // `.then/.catch/.finally` and trigger restart on mid-flight
164
+ // failure. See `Channel.start()` contract in
165
+ // `@nubemclaw/channel-sdk/types.ts`.
166
+ pollingPromise = b.start({
167
+ allowed_updates: [...ALLOWED_UPDATES],
168
+ onStart: (info) => {
169
+ d.logger.info({ channel: CHANNEL_ID, botUsername: info.username }, "telegram polling started");
170
+ },
171
+ });
172
+ // Return the polling promise so the manager observes the full
173
+ // lifecycle. `stop()` calls `bot.stop()` which resolves this
174
+ // promise cleanly; `bot.stop()` is idempotent so the local
175
+ // `pollingPromise` reference is retained but not re-awaited.
176
+ return pollingPromise;
177
+ }
178
+ else {
179
+ const webhookCfg = opts.config.webhook;
180
+ if (webhookCfg === undefined) {
181
+ throw new Error("telegram channel: webhook mode requires webhook config");
182
+ }
183
+ await b.api.setWebhook(webhookCfg.publicUrl, {
184
+ secret_token: webhookCfg.secretToken,
185
+ allowed_updates: [...ALLOWED_UPDATES],
186
+ drop_pending_updates: false,
187
+ });
188
+ }
189
+ },
190
+ async stop() {
191
+ if (bot === null)
192
+ return;
193
+ // grammy.Bot.stop() unblocks an in-flight getUpdates and lets
194
+ // bot.start() resolve cleanly. Safe to call when no polling
195
+ // session is active — it's a no-op then.
196
+ try {
197
+ await bot.stop();
198
+ }
199
+ catch (e) {
200
+ requireDeps().logger.warn({ channel: CHANNEL_ID, err: e }, "telegram bot.stop() raised (continuing teardown)");
201
+ }
202
+ if (pollingPromise !== null) {
203
+ try {
204
+ await pollingPromise;
205
+ }
206
+ catch {
207
+ // bot.start() rejects when stop interrupts it; swallow.
208
+ }
209
+ pollingPromise = null;
210
+ }
211
+ // Release the undici dispatchers owned by the operational
212
+ // transport. Without this, keep-alive sockets to
213
+ // api.telegram.org stay open indefinitely (the openclaw#68128
214
+ // failure mode the transport hardening was originally written
215
+ // to fix). Idempotent — safe to call when there's no transport.
216
+ if (ownedTransport !== null) {
217
+ await ownedTransport.close();
218
+ ownedTransport = null;
219
+ }
220
+ // Webhook mode: we deliberately do NOT call deleteWebhook on
221
+ // stop because the operator may run a short-lived process
222
+ // (deploy, restart) and dropping the webhook each cycle would
223
+ // make Telegram drop in-flight events.
224
+ },
225
+ async send(target, message) {
226
+ if (target.kind !== "chat") {
227
+ throw new Error(`telegram channel: unsupported target kind '${target.kind}'`);
228
+ }
229
+ if (message.type !== "text") {
230
+ throw new Error(`telegram channel: unsupported message type '${message.type}'`);
231
+ }
232
+ const chatId = Number.parseInt(target.id, 10);
233
+ if (!Number.isFinite(chatId)) {
234
+ throw new Error(`telegram channel: invalid chat id '${target.id}'`);
235
+ }
236
+ // apiThrottler is composed on bot.api.config — sendMessage goes
237
+ // through it automatically and honors Telegram's retry_after on
238
+ // 429 without manual bucket management.
239
+ await requireBot().api.sendMessage(chatId, message.text);
240
+ },
241
+ webhookRegistration() {
242
+ return webhookReg ?? undefined;
243
+ },
244
+ };
245
+ };
246
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Per-channel config schema for Telegram (Phase 13, ADR-0020 §7
4
+ * identity + allowlist).
5
+ *
6
+ * • `allowedUsers` — exact-match deny-default allowlist of
7
+ * `telegram_user_id` values (as strings — Telegram IDs are numbers
8
+ * but config files round-trip JSON cleanly with strings, and the
9
+ * SDK `createAllowlist` expects strings anyway).
10
+ * • `mode` — polling or webhook. ADR-0020 §1 keeps polling
11
+ * as the default because it works behind NAT without a public URL;
12
+ * the operator opts into webhook by setting `mode: "webhook"` and
13
+ * supplying `webhook.publicUrl` + `webhook.secretToken`.
14
+ * • `polling.timeoutSec` — Telegram `getUpdates(timeout=N)` long-poll
15
+ * duration. Default 50 (Telegram's recommended ceiling is 50).
16
+ *
17
+ * `.strict()` so a typo (`allowedUSers`) fails loud at boot.
18
+ */
19
+ export declare const TelegramPollingConfigSchema: z.ZodObject<{
20
+ timeoutSec: z.ZodDefault<z.ZodNumber>;
21
+ }, "strict", z.ZodTypeAny, {
22
+ timeoutSec: number;
23
+ }, {
24
+ timeoutSec?: number | undefined;
25
+ }>;
26
+ export type TelegramPollingConfig = z.infer<typeof TelegramPollingConfigSchema>;
27
+ export declare const TelegramWebhookConfigSchema: z.ZodObject<{
28
+ publicUrl: z.ZodString;
29
+ secretToken: z.ZodString;
30
+ }, "strict", z.ZodTypeAny, {
31
+ publicUrl: string;
32
+ secretToken: string;
33
+ }, {
34
+ publicUrl: string;
35
+ secretToken: string;
36
+ }>;
37
+ export type TelegramWebhookConfig = z.infer<typeof TelegramWebhookConfigSchema>;
38
+ export declare const TelegramChannelConfigSchema: z.ZodEffects<z.ZodObject<{
39
+ allowedUsers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
40
+ mode: z.ZodDefault<z.ZodEnum<["polling", "webhook"]>>;
41
+ polling: z.ZodDefault<z.ZodObject<{
42
+ timeoutSec: z.ZodDefault<z.ZodNumber>;
43
+ }, "strict", z.ZodTypeAny, {
44
+ timeoutSec: number;
45
+ }, {
46
+ timeoutSec?: number | undefined;
47
+ }>>;
48
+ webhook: z.ZodOptional<z.ZodObject<{
49
+ publicUrl: z.ZodString;
50
+ secretToken: z.ZodString;
51
+ }, "strict", z.ZodTypeAny, {
52
+ publicUrl: string;
53
+ secretToken: string;
54
+ }, {
55
+ publicUrl: string;
56
+ secretToken: string;
57
+ }>>;
58
+ }, "strict", z.ZodTypeAny, {
59
+ allowedUsers: string[];
60
+ polling: {
61
+ timeoutSec: number;
62
+ };
63
+ mode: "polling" | "webhook";
64
+ webhook?: {
65
+ publicUrl: string;
66
+ secretToken: string;
67
+ } | undefined;
68
+ }, {
69
+ allowedUsers?: string[] | undefined;
70
+ polling?: {
71
+ timeoutSec?: number | undefined;
72
+ } | undefined;
73
+ webhook?: {
74
+ publicUrl: string;
75
+ secretToken: string;
76
+ } | undefined;
77
+ mode?: "polling" | "webhook" | undefined;
78
+ }>, {
79
+ allowedUsers: string[];
80
+ polling: {
81
+ timeoutSec: number;
82
+ };
83
+ mode: "polling" | "webhook";
84
+ webhook?: {
85
+ publicUrl: string;
86
+ secretToken: string;
87
+ } | undefined;
88
+ }, {
89
+ allowedUsers?: string[] | undefined;
90
+ polling?: {
91
+ timeoutSec?: number | undefined;
92
+ } | undefined;
93
+ webhook?: {
94
+ publicUrl: string;
95
+ secretToken: string;
96
+ } | undefined;
97
+ mode?: "polling" | "webhook" | undefined;
98
+ }>;
99
+ export type TelegramChannelConfig = z.infer<typeof TelegramChannelConfigSchema>;
100
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,2BAA2B;;;;;;EAI7B,CAAC;AACZ,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,2BAA2B;;;;;;;;;EAK7B,CAAC;AACZ,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBpC,CAAC;AACL,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Per-channel config schema for Telegram (Phase 13, ADR-0020 §7
4
+ * identity + allowlist).
5
+ *
6
+ * • `allowedUsers` — exact-match deny-default allowlist of
7
+ * `telegram_user_id` values (as strings — Telegram IDs are numbers
8
+ * but config files round-trip JSON cleanly with strings, and the
9
+ * SDK `createAllowlist` expects strings anyway).
10
+ * • `mode` — polling or webhook. ADR-0020 §1 keeps polling
11
+ * as the default because it works behind NAT without a public URL;
12
+ * the operator opts into webhook by setting `mode: "webhook"` and
13
+ * supplying `webhook.publicUrl` + `webhook.secretToken`.
14
+ * • `polling.timeoutSec` — Telegram `getUpdates(timeout=N)` long-poll
15
+ * duration. Default 50 (Telegram's recommended ceiling is 50).
16
+ *
17
+ * `.strict()` so a typo (`allowedUSers`) fails loud at boot.
18
+ */
19
+ export const TelegramPollingConfigSchema = z
20
+ .object({
21
+ timeoutSec: z.number().int().min(1).max(50).default(50),
22
+ })
23
+ .strict();
24
+ export const TelegramWebhookConfigSchema = z
25
+ .object({
26
+ publicUrl: z.string().url(),
27
+ secretToken: z.string().min(1).max(256),
28
+ })
29
+ .strict();
30
+ export const TelegramChannelConfigSchema = z
31
+ .object({
32
+ allowedUsers: z.array(z.string().min(1)).default([]),
33
+ mode: z.enum(["polling", "webhook"]).default("polling"),
34
+ polling: TelegramPollingConfigSchema.default({ timeoutSec: 50 }),
35
+ webhook: TelegramWebhookConfigSchema.optional(),
36
+ })
37
+ .strict()
38
+ .superRefine((cfg, ctx) => {
39
+ if (cfg.mode === "webhook" && cfg.webhook === undefined) {
40
+ ctx.addIssue({
41
+ code: z.ZodIssueCode.custom,
42
+ path: ["webhook"],
43
+ message: "webhook config is required when mode is 'webhook'",
44
+ });
45
+ }
46
+ });
47
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC;KACzC,MAAM,CAAC;IACN,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACxD,CAAC;KACD,MAAM,EAAE,CAAC;AAGZ,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC;KACzC,MAAM,CAAC;IACN,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC3B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACxC,CAAC;KACD,MAAM,EAAE,CAAC;AAGZ,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC;KACzC,MAAM,CAAC;IACN,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACpD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IACvD,OAAO,EAAE,2BAA2B,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAChE,OAAO,EAAE,2BAA2B,CAAC,QAAQ,EAAE;CAChD,CAAC;KACD,MAAM,EAAE;KACR,WAAW,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IACxB,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACxD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,IAAI,EAAE,CAAC,SAAS,CAAC;YACjB,OAAO,EAAE,mDAAmD;SAC7D,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare const PACKAGE_NAME: "@nubemclaw/channel-telegram";
2
+ export declare const PACKAGE_VERSION: string;
3
+ export * from "./api/schemas.js";
4
+ export * from "./channel.js";
5
+ export * from "./config.js";
6
+ export * from "./setup.js";
7
+ export * from "./transport.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,EAAG,6BAAsC,CAAC;AACnE,eAAO,MAAM,eAAe,EAAE,MAAoB,CAAC;AAEnD,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import pkg from "../package.json" with { type: "json" };
2
+ export const PACKAGE_NAME = "@nubemclaw/channel-telegram";
3
+ export const PACKAGE_VERSION = pkg.version;
4
+ export * from "./api/schemas.js";
5
+ export * from "./channel.js";
6
+ export * from "./config.js";
7
+ export * from "./setup.js";
8
+ export * from "./transport.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAExD,MAAM,CAAC,MAAM,YAAY,GAAG,6BAAsC,CAAC;AACnE,MAAM,CAAC,MAAM,eAAe,GAAW,GAAG,CAAC,OAAO,CAAC;AAEnD,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type ChannelLogger, type ChannelSetup } from "@nubemclaw/channel-sdk";
2
+ import { Bot } from "grammy";
3
+ import { z } from "zod";
4
+ export declare const TelegramCredentialsSchema: z.ZodObject<{
5
+ botToken: z.ZodString;
6
+ }, "strict", z.ZodTypeAny, {
7
+ botToken: string;
8
+ }, {
9
+ botToken: string;
10
+ }>;
11
+ export type TelegramCredentials = z.infer<typeof TelegramCredentialsSchema>;
12
+ export interface CreateTelegramSetupOptions {
13
+ readonly logger: ChannelLogger;
14
+ /**
15
+ * Test seam: override the Bot constructor. Production callers
16
+ * always use grammy's `Bot`; tests inject a fake whose `api.getMe()`
17
+ * is scriptable.
18
+ */
19
+ readonly botFactory?: (token: string) => Pick<Bot, "api">;
20
+ /** Test seam: override env vars for credential path resolution. */
21
+ readonly envVars?: NodeJS.ProcessEnv;
22
+ }
23
+ export declare const createTelegramSetup: (opts: CreateTelegramSetupOptions) => ChannelSetup<TelegramCredentials>;
24
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,YAAY,EAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,GAAG,EAA0B,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmCxB,eAAO,MAAM,yBAAyB;;;;;;EAM3B,CAAC;AACZ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1D,mEAAmE;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACtC;AAED,eAAO,MAAM,mBAAmB,GAC9B,MAAM,0BAA0B,KAC/B,YAAY,CAAC,mBAAmB,CAuEjC,CAAC"}
package/dist/setup.js ADDED
@@ -0,0 +1,104 @@
1
+ import { err, nubemClawError, ok } from "@nubemclaw/core";
2
+ import { resolveCredentialPaths, writeChannelCredential, } from "@nubemclaw/channel-sdk";
3
+ import { Bot, GrammyError, HttpError } from "grammy";
4
+ import { z } from "zod";
5
+ /**
6
+ * `TelegramSetup` — guided credential capture for the Telegram channel
7
+ * (Phase 13, ADR-0020 §2).
8
+ *
9
+ * Wizard transcript:
10
+ *
11
+ * 1. `secret` — bot token, format `<digits>:<token>` (BotFather).
12
+ *
13
+ * Schema (`credentialSchema`):
14
+ *
15
+ * - `botToken: string` matching `/^\d+:[A-Za-z0-9_-]+$/`.
16
+ *
17
+ * Validate (`validate`):
18
+ *
19
+ * - Instantiates a transient grammy `Bot` and calls `bot.api.getMe()`.
20
+ * grammy maps the Bot API failure modes to typed errors:
21
+ * • `GrammyError` with `error_code: 401` → invalid token.
22
+ * • `HttpError` → transport/network failure.
23
+ * - We translate both into `channel.setup_validation_failed` so the
24
+ * operator sees a clean message; the orchestrator does NOT persist.
25
+ *
26
+ * Persist (`persist`):
27
+ *
28
+ * - Atomic 0o600 write to
29
+ * `~/.nubemclaw-dev/credentials/telegram-bot-token` via
30
+ * `writeChannelCredential` (SDK shared helper).
31
+ *
32
+ * Cancellation at any step is converted by `runChannelSetup` to
33
+ * `channel.setup_cancelled` — see ADR-0020 §2.
34
+ */
35
+ const TELEGRAM_BOT_TOKEN_REGEX = /^\d+:[A-Za-z0-9_-]+$/;
36
+ export const TelegramCredentialsSchema = z
37
+ .object({
38
+ botToken: z.string().regex(TELEGRAM_BOT_TOKEN_REGEX, {
39
+ message: "expected BotFather format: <digits>:<token>",
40
+ }),
41
+ })
42
+ .strict();
43
+ export const createTelegramSetup = (opts) => ({
44
+ channel: "telegram",
45
+ credentialSchema: TelegramCredentialsSchema,
46
+ async promptOperator(wizard) {
47
+ const token = await wizard.secret({
48
+ message: "Telegram bot token (BotFather format: 123456:ABC...)",
49
+ validate: (value) => {
50
+ if (value === undefined || value.trim() === "")
51
+ return "required";
52
+ if (!TELEGRAM_BOT_TOKEN_REGEX.test(value.trim())) {
53
+ return "expected BotFather format: <digits>:<token>";
54
+ }
55
+ return undefined;
56
+ },
57
+ });
58
+ if (!token.ok)
59
+ return token;
60
+ return ok({ botToken: token.value.trim() });
61
+ },
62
+ async validate(creds) {
63
+ const factory = opts.botFactory ?? ((token) => new Bot(token));
64
+ const bot = factory(creds.botToken);
65
+ try {
66
+ const me = await bot.api.getMe();
67
+ if (me.is_bot !== true) {
68
+ return err(nubemClawError({
69
+ code: "channel.setup_validation_failed",
70
+ message: "telegram getMe returned a non-bot identity — token is not a bot token",
71
+ }));
72
+ }
73
+ opts.logger.info({ username: me.username, id: me.id }, "telegram bot validated");
74
+ return ok(undefined);
75
+ }
76
+ catch (cause) {
77
+ if (cause instanceof GrammyError) {
78
+ return err(nubemClawError({
79
+ code: "channel.setup_validation_failed",
80
+ message: `telegram getMe rejected the token: ${cause.description}`,
81
+ meta: { error_code: cause.error_code },
82
+ cause,
83
+ }));
84
+ }
85
+ if (cause instanceof HttpError) {
86
+ return err(nubemClawError({
87
+ code: "channel.setup_validation_failed",
88
+ message: `telegram getMe could not reach the Bot API: ${cause.message}`,
89
+ cause,
90
+ }));
91
+ }
92
+ return err(nubemClawError({
93
+ code: "channel.setup_validation_failed",
94
+ message: `telegram getMe failed: ${String(cause)}`,
95
+ cause,
96
+ }));
97
+ }
98
+ },
99
+ async persist(creds) {
100
+ const paths = resolveCredentialPaths({ channel: "telegram", role: "bot-token" }, opts.envVars ?? process.env);
101
+ await writeChannelCredential(creds.botToken, paths);
102
+ },
103
+ });
104
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}