@jant/core 0.5.4 → 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/bin/commands/telegram/register-webhooks.js +93 -0
- package/dist/{app-BtNdUAqz.js → app-BIkkbVQk.js} +2252 -383
- package/dist/app-Bcr5_wZI.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-Bo7sKkAQ.js +274 -0
- package/dist/client/_assets/client-QHRvzZwk.css +2 -0
- package/dist/client/_assets/{client-auth-DJ_5wx9N.js → client-auth-D1jDQgbH.js} +81 -81
- package/dist/{env-CgaH9Mut.js → env-C7e2Nlnt.js} +30 -1
- package/dist/{export-CR9Megtb.js → export-Bbn86HmS.js} +1 -1
- package/dist/{github-sync-DYZq9rQp.js → github-sync-CBQPRZ8H.js} +1 -1
- package/dist/{github-sync-8Vv06aCr.js → github-sync-dXsiZa_e.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/node.js +61 -5
- package/package.json +2 -1
- package/src/__tests__/helpers/app.ts +15 -2
- package/src/app.tsx +3 -0
- package/src/client/thread-context.ts +146 -2
- package/src/client/tiptap/__tests__/link-toolbar.test.ts +1 -1
- package/src/client/tiptap/bubble-menu.ts +1 -16
- package/src/client/tiptap/extensions.ts +2 -6
- package/src/client/tiptap/link-toolbar.ts +0 -21
- package/src/client/tiptap/toolbar-mode.ts +0 -43
- package/src/db/migrations/0022_old_gressill.sql +24 -0
- package/src/db/migrations/0023_broad_terror.sql +20 -0
- package/src/db/migrations/0024_red_the_twelve.sql +3 -0
- package/src/db/migrations/0025_exotic_wendell_rand.sql +1 -0
- package/src/db/migrations/meta/0022_snapshot.json +2267 -0
- package/src/db/migrations/meta/0023_snapshot.json +2396 -0
- package/src/db/migrations/meta/0024_snapshot.json +2417 -0
- package/src/db/migrations/meta/0025_snapshot.json +2424 -0
- package/src/db/migrations/meta/_journal.json +28 -0
- package/src/db/migrations/pg/0020_bizarre_smasher.sql +24 -0
- package/src/db/migrations/pg/0021_sharp_puppet_master.sql +20 -0
- package/src/db/migrations/pg/0022_blushing_blue_shield.sql +3 -0
- package/src/db/migrations/pg/0023_organic_zemo.sql +1 -0
- package/src/db/migrations/pg/meta/0020_snapshot.json +2904 -0
- package/src/db/migrations/pg/meta/0021_snapshot.json +3060 -0
- package/src/db/migrations/pg/meta/0022_snapshot.json +3078 -0
- package/src/db/migrations/pg/meta/0023_snapshot.json +3084 -0
- package/src/db/migrations/pg/meta/_journal.json +28 -0
- package/src/db/pg/schema.ts +82 -0
- package/src/db/schema.ts +90 -0
- package/src/i18n/coverage.generated.ts +2 -2
- package/src/i18n/locales/public/en.po +8 -0
- package/src/i18n/locales/public/zh-Hans.po +8 -0
- package/src/i18n/locales/public/zh-Hant.po +8 -0
- package/src/i18n/locales/settings/en.po +135 -0
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +136 -1
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +136 -1
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/lib/__tests__/image-dimensions.test.ts +314 -0
- package/src/lib/__tests__/telegram-entities.test.ts +180 -0
- package/src/lib/__tests__/telegram-pool-webhooks.test.ts +127 -0
- package/src/lib/env.ts +45 -0
- package/src/lib/ids.ts +3 -0
- package/src/lib/image-dimensions.ts +258 -0
- package/src/lib/telegram-entities.ts +240 -0
- package/src/lib/telegram-pool-webhooks.ts +86 -0
- package/src/lib/telegram-settings-status.tsx +109 -0
- package/src/lib/telegram.ts +363 -0
- package/src/node/runtime.ts +6 -0
- package/src/routes/api/__tests__/telegram.test.ts +612 -0
- package/src/routes/api/telegram.ts +782 -0
- package/src/routes/api/upload-multipart.ts +34 -12
- package/src/routes/api/upload.ts +23 -2
- package/src/routes/dash/settings.tsx +131 -1
- package/src/routes/pages/__tests__/post-page-title.test.ts +70 -0
- package/src/routes/pages/page.tsx +3 -2
- package/src/runtime/cloudflare.ts +20 -9
- package/src/runtime/node.ts +20 -9
- package/src/runtime/site.ts +2 -1
- package/src/services/__tests__/telegram.test.ts +148 -0
- package/src/services/index.ts +9 -0
- package/src/services/telegram.ts +613 -0
- package/src/services/upload-session.ts +39 -12
- package/src/styles/tokens.css +1 -0
- package/src/styles/ui.css +116 -38
- package/src/types/app-context.ts +6 -0
- package/src/types/bindings.ts +3 -0
- package/src/types/config.ts +40 -0
- package/src/ui/dash/settings/SettingsRootContent.tsx +48 -17
- package/src/ui/dash/settings/TelegramContent.tsx +549 -0
- package/src/ui/feed/ThreadPreview.tsx +91 -38
- package/src/ui/feed/__tests__/thread-preview.test.ts +67 -5
- package/src/ui/pages/PostPage.tsx +78 -15
- package/dist/app-DLINgGBd.js +0 -6
- package/dist/client/_assets/client-BErXNT6k.css +0 -2
- package/dist/client/_assets/client-CtAgWT8i.js +0 -274
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Bot API client.
|
|
3
|
+
*
|
|
4
|
+
* Thin `fetch` wrappers around the handful of Bot API methods the Telegram
|
|
5
|
+
* integration needs, plus helpers for working with bot tokens and deep links.
|
|
6
|
+
* All network calls go to `https://api.telegram.org/bot<token>/<method>`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const TELEGRAM_API_BASE = "https://api.telegram.org";
|
|
10
|
+
|
|
11
|
+
/** A Telegram user as it appears in `message.from` / `callback_query.from`. */
|
|
12
|
+
export interface TelegramUser {
|
|
13
|
+
id: number;
|
|
14
|
+
is_bot: boolean;
|
|
15
|
+
first_name: string;
|
|
16
|
+
username?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One styled span within a Telegram message.
|
|
21
|
+
*
|
|
22
|
+
* `offset` and `length` are measured in UTF-16 code units, which matches
|
|
23
|
+
* JavaScript string indexing — `text.slice(offset, offset + length)` returns
|
|
24
|
+
* the entity's visible text directly. See
|
|
25
|
+
* https://core.telegram.org/bots/api#messageentity for the full type list.
|
|
26
|
+
*/
|
|
27
|
+
export interface TelegramMessageEntity {
|
|
28
|
+
type: string;
|
|
29
|
+
offset: number;
|
|
30
|
+
length: number;
|
|
31
|
+
/** Present on `text_link` entities. */
|
|
32
|
+
url?: string;
|
|
33
|
+
/** Present on `pre` entities when the user picked a language. */
|
|
34
|
+
language?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One rendered size of a Telegram photo. */
|
|
38
|
+
export interface TelegramPhotoSize {
|
|
39
|
+
file_id: string;
|
|
40
|
+
file_unique_id: string;
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
file_size?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A Telegram video message attachment. */
|
|
47
|
+
export interface TelegramVideo {
|
|
48
|
+
file_id: string;
|
|
49
|
+
file_unique_id: string;
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
duration: number;
|
|
53
|
+
mime_type?: string;
|
|
54
|
+
file_name?: string;
|
|
55
|
+
file_size?: number;
|
|
56
|
+
/** Auto-generated preview frame; downloadable as a normal PhotoSize. */
|
|
57
|
+
thumbnail?: TelegramPhotoSize;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A Telegram document message attachment (arbitrary file). */
|
|
61
|
+
export interface TelegramDocument {
|
|
62
|
+
file_id: string;
|
|
63
|
+
file_unique_id: string;
|
|
64
|
+
mime_type?: string;
|
|
65
|
+
file_name?: string;
|
|
66
|
+
file_size?: number;
|
|
67
|
+
/** Mime-appropriate preview, when one was generated. */
|
|
68
|
+
thumbnail?: TelegramPhotoSize;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Subset of the Telegram `Message` object the integration consumes. */
|
|
72
|
+
export interface TelegramMessage {
|
|
73
|
+
message_id: number;
|
|
74
|
+
from?: TelegramUser;
|
|
75
|
+
chat: { id: number };
|
|
76
|
+
text?: string;
|
|
77
|
+
entities?: TelegramMessageEntity[];
|
|
78
|
+
/** Album grouping key — present on every item in a multi-attachment send. */
|
|
79
|
+
media_group_id?: string;
|
|
80
|
+
/** Caption supplied with a photo/video/document; same entity grammar as `text`. */
|
|
81
|
+
caption?: string;
|
|
82
|
+
caption_entities?: TelegramMessageEntity[];
|
|
83
|
+
/** Ordered list of rendered sizes; the last entry is the highest resolution. */
|
|
84
|
+
photo?: TelegramPhotoSize[];
|
|
85
|
+
video?: TelegramVideo;
|
|
86
|
+
document?: TelegramDocument;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Subset of the Telegram `CallbackQuery` object the integration consumes. */
|
|
90
|
+
export interface TelegramCallbackQuery {
|
|
91
|
+
id: string;
|
|
92
|
+
from: TelegramUser;
|
|
93
|
+
message?: TelegramMessage;
|
|
94
|
+
data?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Subset of the Telegram `Update` object delivered to the webhook. */
|
|
98
|
+
export interface TelegramUpdate {
|
|
99
|
+
update_id: number;
|
|
100
|
+
message?: TelegramMessage;
|
|
101
|
+
callback_query?: TelegramCallbackQuery;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** An inline keyboard button — either a deep link or a callback action. */
|
|
105
|
+
export interface TelegramInlineButton {
|
|
106
|
+
text: string;
|
|
107
|
+
url?: string;
|
|
108
|
+
callback_data?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface TelegramInlineKeyboard {
|
|
112
|
+
inline_keyboard: TelegramInlineButton[][];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class TelegramApiError extends Error {
|
|
116
|
+
constructor(
|
|
117
|
+
public readonly method: string,
|
|
118
|
+
public readonly description: string,
|
|
119
|
+
) {
|
|
120
|
+
super(`Telegram ${method} failed: ${description}`);
|
|
121
|
+
this.name = "TelegramApiError";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface TelegramApiResponse<T> {
|
|
126
|
+
ok: boolean;
|
|
127
|
+
result?: T;
|
|
128
|
+
description?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function callTelegram<T>(
|
|
132
|
+
token: string,
|
|
133
|
+
method: string,
|
|
134
|
+
body?: Record<string, unknown>,
|
|
135
|
+
): Promise<T> {
|
|
136
|
+
const response = await fetch(`${TELEGRAM_API_BASE}/bot${token}/${method}`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "content-type": "application/json" },
|
|
139
|
+
body: JSON.stringify(body ?? {}),
|
|
140
|
+
});
|
|
141
|
+
const payload = (await response.json()) as TelegramApiResponse<T>;
|
|
142
|
+
if (!payload.ok) {
|
|
143
|
+
throw new TelegramApiError(method, payload.description ?? "unknown error");
|
|
144
|
+
}
|
|
145
|
+
return payload.result as T;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Extracts the numeric bot id from a bot token.
|
|
150
|
+
*
|
|
151
|
+
* A Telegram token is `<bot_id>:<secret>`, so the bot id is intrinsic and
|
|
152
|
+
* stable — no API call required.
|
|
153
|
+
*
|
|
154
|
+
* @param token - Full bot token
|
|
155
|
+
* @returns The bot id, or an empty string when the token is malformed
|
|
156
|
+
* @example
|
|
157
|
+
* parseBotId("123456:ABC-DEF"); // "123456"
|
|
158
|
+
*/
|
|
159
|
+
export function parseBotId(token: string): string {
|
|
160
|
+
const botId = token.split(":")[0]?.trim() ?? "";
|
|
161
|
+
return /^\d+$/.test(botId) ? botId : "";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Builds a `t.me` deep link that pre-fills `/start <code>` for a bot.
|
|
166
|
+
*
|
|
167
|
+
* @param botUsername - Bot username without the leading `@`
|
|
168
|
+
* @param code - Binding code to pass as the `start` parameter
|
|
169
|
+
* @returns The deep link URL
|
|
170
|
+
* @example
|
|
171
|
+
* buildDeepLink("JantBot", "abc123"); // "https://t.me/JantBot?start=abc123"
|
|
172
|
+
*/
|
|
173
|
+
export function buildDeepLink(botUsername: string, code: string): string {
|
|
174
|
+
return `https://t.me/${botUsername}?start=${encodeURIComponent(code)}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Bot identity returned by `getMe`. */
|
|
178
|
+
export interface TelegramBotIdentity {
|
|
179
|
+
id: number;
|
|
180
|
+
username: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Validates a bot token and returns the bot's identity.
|
|
185
|
+
*
|
|
186
|
+
* @param token - Bot token to validate
|
|
187
|
+
* @returns The bot's numeric id and username
|
|
188
|
+
* @throws {TelegramApiError} When the token is invalid
|
|
189
|
+
*/
|
|
190
|
+
export async function getMe(token: string): Promise<TelegramBotIdentity> {
|
|
191
|
+
const result = await callTelegram<TelegramUser>(token, "getMe");
|
|
192
|
+
return { id: result.id, username: result.username ?? "" };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Registers a webhook URL for a bot.
|
|
197
|
+
*
|
|
198
|
+
* @param token - Bot token
|
|
199
|
+
* @param url - Public webhook URL Telegram should POST updates to
|
|
200
|
+
* @param secretToken - Value Telegram echoes back in the
|
|
201
|
+
* `X-Telegram-Bot-Api-Secret-Token` header so the handler can verify the call
|
|
202
|
+
*/
|
|
203
|
+
export async function setWebhook(
|
|
204
|
+
token: string,
|
|
205
|
+
url: string,
|
|
206
|
+
secretToken: string,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
await callTelegram(token, "setWebhook", {
|
|
209
|
+
url,
|
|
210
|
+
secret_token: secretToken,
|
|
211
|
+
allowed_updates: ["message", "callback_query"],
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Removes a bot's webhook.
|
|
217
|
+
*
|
|
218
|
+
* @param token - Bot token
|
|
219
|
+
*/
|
|
220
|
+
export async function deleteWebhook(token: string): Promise<void> {
|
|
221
|
+
await callTelegram(token, "deleteWebhook");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Canonical bot command list. Telegram shows these in the `/` autocomplete
|
|
226
|
+
* popup and on the bot's profile. The list is persisted per-bot on Telegram's
|
|
227
|
+
* servers via `setMyCommands` — we only register commands the bot actually
|
|
228
|
+
* responds to. Anything else the user sends is treated as note content.
|
|
229
|
+
*/
|
|
230
|
+
export const JANT_BOT_COMMANDS: ReadonlyArray<{
|
|
231
|
+
command: string;
|
|
232
|
+
description: string;
|
|
233
|
+
}> = [
|
|
234
|
+
{
|
|
235
|
+
command: "start",
|
|
236
|
+
description: "Connect this chat to a Jant site",
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Registers the bot's command list with Telegram so typing `/` in the chat
|
|
242
|
+
* shows autocomplete suggestions. Idempotent — safe to call on every boot.
|
|
243
|
+
*
|
|
244
|
+
* @param token - Bot token
|
|
245
|
+
* @param commands - Commands to register. Defaults to `JANT_BOT_COMMANDS`.
|
|
246
|
+
*/
|
|
247
|
+
export async function setMyCommands(
|
|
248
|
+
token: string,
|
|
249
|
+
commands: ReadonlyArray<{
|
|
250
|
+
command: string;
|
|
251
|
+
description: string;
|
|
252
|
+
}> = JANT_BOT_COMMANDS,
|
|
253
|
+
): Promise<void> {
|
|
254
|
+
await callTelegram(token, "setMyCommands", {
|
|
255
|
+
commands: commands.map((c) => ({
|
|
256
|
+
command: c.command,
|
|
257
|
+
description: c.description,
|
|
258
|
+
})),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Returns the bot's currently registered webhook URL (empty when none).
|
|
264
|
+
*
|
|
265
|
+
* Lets callers skip a redundant `setWebhook` write when the webhook is
|
|
266
|
+
* already pointed at the right place.
|
|
267
|
+
*
|
|
268
|
+
* @param token - Bot token
|
|
269
|
+
* @returns The current webhook URL, or `""` when no webhook is set
|
|
270
|
+
*/
|
|
271
|
+
export async function getWebhookUrl(token: string): Promise<string> {
|
|
272
|
+
const result = await callTelegram<{ url?: string }>(token, "getWebhookInfo");
|
|
273
|
+
return result.url ?? "";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Sends a text message, optionally with an inline keyboard.
|
|
278
|
+
*
|
|
279
|
+
* @param token - Bot token
|
|
280
|
+
* @param chatId - Target chat id
|
|
281
|
+
* @param text - Message body
|
|
282
|
+
* @param replyMarkup - Optional inline keyboard
|
|
283
|
+
*/
|
|
284
|
+
export async function sendMessage(
|
|
285
|
+
token: string,
|
|
286
|
+
chatId: number,
|
|
287
|
+
text: string,
|
|
288
|
+
replyMarkup?: TelegramInlineKeyboard,
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
await callTelegram(token, "sendMessage", {
|
|
291
|
+
chat_id: chatId,
|
|
292
|
+
text,
|
|
293
|
+
...(replyMarkup ? { reply_markup: replyMarkup } : {}),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Metadata returned by Telegram's `getFile`. The `file_path` is relative and
|
|
299
|
+
* must be appended to `/file/bot<token>/` to download the bytes.
|
|
300
|
+
*/
|
|
301
|
+
export interface TelegramFile {
|
|
302
|
+
file_id: string;
|
|
303
|
+
file_unique_id: string;
|
|
304
|
+
file_size?: number;
|
|
305
|
+
file_path?: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Resolves a `file_id` to its downloadable path and size.
|
|
310
|
+
*
|
|
311
|
+
* Telegram's Bot API supports downloads up to 20 MB; the caller is expected to
|
|
312
|
+
* enforce its own limit using `file_size` before calling `downloadFile`.
|
|
313
|
+
*
|
|
314
|
+
* @param token - Bot token
|
|
315
|
+
* @param fileId - Identifier from a photo/video/document field
|
|
316
|
+
*/
|
|
317
|
+
export async function getFile(
|
|
318
|
+
token: string,
|
|
319
|
+
fileId: string,
|
|
320
|
+
): Promise<TelegramFile> {
|
|
321
|
+
return callTelegram<TelegramFile>(token, "getFile", { file_id: fileId });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Downloads the raw bytes for a `file_path` returned by `getFile`.
|
|
326
|
+
*
|
|
327
|
+
* Returns the underlying `Response` so callers can stream large bodies into
|
|
328
|
+
* storage without first materializing them in memory.
|
|
329
|
+
*
|
|
330
|
+
* @param token - Bot token
|
|
331
|
+
* @param filePath - The `file_path` from `getFile`
|
|
332
|
+
*/
|
|
333
|
+
export async function downloadFile(
|
|
334
|
+
token: string,
|
|
335
|
+
filePath: string,
|
|
336
|
+
): Promise<Response> {
|
|
337
|
+
const response = await fetch(
|
|
338
|
+
`${TELEGRAM_API_BASE}/file/bot${token}/${filePath}`,
|
|
339
|
+
);
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
throw new TelegramApiError(
|
|
342
|
+
"downloadFile",
|
|
343
|
+
`HTTP ${response.status} fetching ${filePath}`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return response;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Acknowledges a callback query so Telegram stops showing a loading state on
|
|
351
|
+
* the tapped inline button.
|
|
352
|
+
*
|
|
353
|
+
* @param token - Bot token
|
|
354
|
+
* @param callbackQueryId - The callback query id to acknowledge
|
|
355
|
+
*/
|
|
356
|
+
export async function answerCallbackQuery(
|
|
357
|
+
token: string,
|
|
358
|
+
callbackQueryId: string,
|
|
359
|
+
): Promise<void> {
|
|
360
|
+
await callTelegram(token, "answerCallbackQuery", {
|
|
361
|
+
callback_query_id: callbackQueryId,
|
|
362
|
+
});
|
|
363
|
+
}
|
package/src/node/runtime.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
resolveHost,
|
|
8
8
|
resolvePort,
|
|
9
9
|
} from "./request-handler.js";
|
|
10
|
+
import { registerTelegramPoolWebhooks } from "../lib/telegram-pool-webhooks.js";
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
applyNodeRuntimeEnvDefaults,
|
|
@@ -47,6 +48,11 @@ export async function start(
|
|
|
47
48
|
didResolve = true;
|
|
48
49
|
server.off("error", onError);
|
|
49
50
|
|
|
51
|
+
// Fire-and-forget: self-register managed-pool Telegram webhooks once
|
|
52
|
+
// the server is listening. Never awaited — it must not delay readiness
|
|
53
|
+
// or fail startup. No-ops unless this is a hosted deployment.
|
|
54
|
+
void registerTelegramPoolWebhooks(env);
|
|
55
|
+
|
|
50
56
|
let closed = false;
|
|
51
57
|
resolvePromise({
|
|
52
58
|
server,
|