nonotify 0.1.15 → 0.2.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/CHANGELOG.md +6 -0
- package/dist/cli.js +170 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/notifier.d.ts +19 -0
- package/dist/notifier.js +94 -1
- package/dist/telegram.d.ts +24 -1
- package/dist/telegram.js +293 -31
- package/package.json +1 -1
- package/src/cli.ts +224 -4
- package/src/index.ts +4 -0
- package/src/notifier.ts +155 -1
- package/src/telegram.ts +451 -48
package/src/telegram.ts
CHANGED
|
@@ -9,18 +9,33 @@ type TelegramApiResponse<T> =
|
|
|
9
9
|
description: string;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
type TelegramChat = {
|
|
13
|
+
id: number;
|
|
14
|
+
username?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type TelegramMessage = {
|
|
18
|
+
message_id: number;
|
|
19
|
+
from?: {
|
|
20
|
+
username?: string;
|
|
21
|
+
};
|
|
22
|
+
chat?: TelegramChat;
|
|
23
|
+
text?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type TelegramCallbackQuery = {
|
|
27
|
+
id: string;
|
|
28
|
+
from?: {
|
|
29
|
+
username?: string;
|
|
30
|
+
};
|
|
31
|
+
message?: TelegramMessage;
|
|
32
|
+
data?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
12
35
|
type TelegramUpdate = {
|
|
13
36
|
update_id: number;
|
|
14
|
-
message?:
|
|
15
|
-
|
|
16
|
-
username?: string;
|
|
17
|
-
};
|
|
18
|
-
chat?: {
|
|
19
|
-
id: number;
|
|
20
|
-
username?: string;
|
|
21
|
-
};
|
|
22
|
-
text?: string;
|
|
23
|
-
};
|
|
37
|
+
message?: TelegramMessage;
|
|
38
|
+
callback_query?: TelegramCallbackQuery;
|
|
24
39
|
};
|
|
25
40
|
|
|
26
41
|
export type TelegramConnection = {
|
|
@@ -28,29 +43,76 @@ export type TelegramConnection = {
|
|
|
28
43
|
username: string | null;
|
|
29
44
|
};
|
|
30
45
|
|
|
46
|
+
export type TelegramInlineOption = {
|
|
47
|
+
label: string;
|
|
48
|
+
callbackData: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type TelegramChoiceMessage = {
|
|
52
|
+
messageId: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type TelegramCallbackSelection = {
|
|
56
|
+
callbackQueryId: string;
|
|
57
|
+
data: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type TelegramWaitOptions = {
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type TelegramWaiter<T> = {
|
|
66
|
+
match: (update: TelegramUpdate) => T | undefined;
|
|
67
|
+
resolve: (value: T) => void;
|
|
68
|
+
reject: (error: unknown) => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const allowedUpdateTypes = ["message", "callback_query"];
|
|
72
|
+
const updateStreams = new Map<string, TelegramUpdateStream>();
|
|
73
|
+
const shortPollIntervalMs = 1_000;
|
|
74
|
+
|
|
31
75
|
async function telegramRequest<T>(
|
|
32
76
|
botToken: string,
|
|
33
77
|
method: string,
|
|
34
|
-
payload: Record<string, unknown
|
|
78
|
+
payload: Record<string, unknown>,
|
|
79
|
+
signal?: AbortSignal
|
|
35
80
|
): Promise<T> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
81
|
+
let response: Response;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
response = await fetch(
|
|
85
|
+
`https://api.telegram.org/bot${botToken}/${method}`,
|
|
86
|
+
{
|
|
87
|
+
method: "POST",
|
|
88
|
+
signal,
|
|
89
|
+
headers: {
|
|
90
|
+
"content-type": "application/json",
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify(payload),
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(formatTelegramFetchError(method, error));
|
|
97
|
+
}
|
|
46
98
|
|
|
47
99
|
if (!response.ok) {
|
|
48
|
-
throw new Error(`Telegram
|
|
100
|
+
throw new Error(`Telegram ${method} failed with HTTP ${response.status}.`);
|
|
49
101
|
}
|
|
50
102
|
|
|
51
103
|
const json = (await response.json()) as TelegramApiResponse<T>;
|
|
52
104
|
|
|
53
105
|
if (!json.ok) {
|
|
106
|
+
if (
|
|
107
|
+
method === "getUpdates" &&
|
|
108
|
+
/webhook/i.test(json.description) &&
|
|
109
|
+
/active|set/i.test(json.description)
|
|
110
|
+
) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"This Telegram bot has an active webhook. `nnt ask` requires getUpdates, so disable the webhook or use a separate bot token for nonotify."
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
54
116
|
throw new Error(json.description);
|
|
55
117
|
}
|
|
56
118
|
|
|
@@ -63,7 +125,7 @@ export async function getLatestUpdateOffset(botToken: string): Promise<number> {
|
|
|
63
125
|
"getUpdates",
|
|
64
126
|
{
|
|
65
127
|
timeout: 0,
|
|
66
|
-
allowed_updates:
|
|
128
|
+
allowed_updates: allowedUpdateTypes,
|
|
67
129
|
}
|
|
68
130
|
);
|
|
69
131
|
|
|
@@ -81,30 +143,18 @@ export async function getLatestUpdateOffset(botToken: string): Promise<number> {
|
|
|
81
143
|
export async function waitForChatId(
|
|
82
144
|
botToken: string,
|
|
83
145
|
offset: number,
|
|
84
|
-
timeoutSeconds = 120
|
|
146
|
+
timeoutSeconds = 120,
|
|
147
|
+
signal?: AbortSignal
|
|
85
148
|
): Promise<TelegramConnection> {
|
|
86
|
-
const
|
|
87
|
-
let currentOffset = offset;
|
|
88
|
-
|
|
89
|
-
while ((Date.now() - startedAt) / 1000 < timeoutSeconds) {
|
|
90
|
-
const remainingSeconds =
|
|
91
|
-
timeoutSeconds - Math.floor((Date.now() - startedAt) / 1000);
|
|
92
|
-
const pollTimeout = Math.max(1, Math.min(25, remainingSeconds));
|
|
93
|
-
|
|
94
|
-
const updates = await telegramRequest<TelegramUpdate[]>(
|
|
95
|
-
botToken,
|
|
96
|
-
"getUpdates",
|
|
97
|
-
{
|
|
98
|
-
offset: currentOffset,
|
|
99
|
-
timeout: pollTimeout,
|
|
100
|
-
allowed_updates: ["message"],
|
|
101
|
-
}
|
|
102
|
-
);
|
|
149
|
+
const stream = getTelegramUpdateStream(botToken, offset);
|
|
103
150
|
|
|
104
|
-
|
|
105
|
-
|
|
151
|
+
try {
|
|
152
|
+
return await stream.waitFor(
|
|
153
|
+
(update) => {
|
|
154
|
+
if (update.message?.chat?.id === undefined) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
106
157
|
|
|
107
|
-
if (update.message?.chat?.id !== undefined) {
|
|
108
158
|
return {
|
|
109
159
|
chatId: String(update.message.chat.id),
|
|
110
160
|
username:
|
|
@@ -112,13 +162,21 @@ export async function waitForChatId(
|
|
|
112
162
|
update.message.chat.username ??
|
|
113
163
|
null,
|
|
114
164
|
};
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
168
|
+
signal,
|
|
115
169
|
}
|
|
170
|
+
);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof Error && error.message === "Timed out waiting") {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"Timed out waiting for Telegram message. Send a message to your bot and try again."
|
|
175
|
+
);
|
|
116
176
|
}
|
|
117
|
-
}
|
|
118
177
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
122
180
|
}
|
|
123
181
|
|
|
124
182
|
export async function sendTelegramMessage(
|
|
@@ -131,3 +189,348 @@ export async function sendTelegramMessage(
|
|
|
131
189
|
text,
|
|
132
190
|
});
|
|
133
191
|
}
|
|
192
|
+
|
|
193
|
+
export async function sendTelegramChoiceMessage(
|
|
194
|
+
botToken: string,
|
|
195
|
+
chatId: string,
|
|
196
|
+
text: string,
|
|
197
|
+
options: readonly TelegramInlineOption[]
|
|
198
|
+
): Promise<TelegramChoiceMessage> {
|
|
199
|
+
const message = await telegramRequest<TelegramMessage>(
|
|
200
|
+
botToken,
|
|
201
|
+
"sendMessage",
|
|
202
|
+
{
|
|
203
|
+
chat_id: chatId,
|
|
204
|
+
text,
|
|
205
|
+
reply_markup: {
|
|
206
|
+
inline_keyboard: options.map((option) => [
|
|
207
|
+
{
|
|
208
|
+
text: option.label,
|
|
209
|
+
callback_data: option.callbackData,
|
|
210
|
+
},
|
|
211
|
+
]),
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
messageId: message.message_id,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function waitForTelegramCallback(
|
|
222
|
+
botToken: string,
|
|
223
|
+
input: {
|
|
224
|
+
chatId: string;
|
|
225
|
+
messageId: number;
|
|
226
|
+
callbackData: readonly string[];
|
|
227
|
+
offset?: number;
|
|
228
|
+
timeoutMs?: number;
|
|
229
|
+
signal?: AbortSignal;
|
|
230
|
+
}
|
|
231
|
+
): Promise<TelegramCallbackSelection> {
|
|
232
|
+
const stream = getTelegramUpdateStream(botToken, input.offset);
|
|
233
|
+
const allowedCallbackData = new Set(input.callbackData);
|
|
234
|
+
|
|
235
|
+
return stream.waitFor(
|
|
236
|
+
(update) => {
|
|
237
|
+
const callback = update.callback_query;
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
!callback?.message ||
|
|
241
|
+
String(callback.message.chat?.id) !== input.chatId ||
|
|
242
|
+
callback.message.message_id !== input.messageId ||
|
|
243
|
+
typeof callback.data !== "string" ||
|
|
244
|
+
!allowedCallbackData.has(callback.data)
|
|
245
|
+
) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
callbackQueryId: callback.id,
|
|
251
|
+
data: callback.data,
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
timeoutMs: input.timeoutMs,
|
|
256
|
+
signal: input.signal,
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function answerTelegramCallbackQuery(
|
|
262
|
+
botToken: string,
|
|
263
|
+
callbackQueryId: string
|
|
264
|
+
): Promise<void> {
|
|
265
|
+
await telegramRequest(botToken, "answerCallbackQuery", {
|
|
266
|
+
callback_query_id: callbackQueryId,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function clearTelegramInlineKeyboard(
|
|
271
|
+
botToken: string,
|
|
272
|
+
chatId: string,
|
|
273
|
+
messageId: number
|
|
274
|
+
): Promise<void> {
|
|
275
|
+
try {
|
|
276
|
+
await telegramRequest(botToken, "editMessageReplyMarkup", {
|
|
277
|
+
chat_id: chatId,
|
|
278
|
+
message_id: messageId,
|
|
279
|
+
reply_markup: {
|
|
280
|
+
inline_keyboard: [],
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (
|
|
285
|
+
error instanceof Error &&
|
|
286
|
+
/message is not modified/i.test(error.message)
|
|
287
|
+
) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function markTelegramSelectedOption(
|
|
296
|
+
botToken: string,
|
|
297
|
+
chatId: string,
|
|
298
|
+
messageId: number,
|
|
299
|
+
options: readonly TelegramInlineOption[],
|
|
300
|
+
selectedCallbackData: string
|
|
301
|
+
): Promise<void> {
|
|
302
|
+
try {
|
|
303
|
+
await telegramRequest(botToken, "editMessageReplyMarkup", {
|
|
304
|
+
chat_id: chatId,
|
|
305
|
+
message_id: messageId,
|
|
306
|
+
reply_markup: {
|
|
307
|
+
inline_keyboard: options.map((option) => [
|
|
308
|
+
{
|
|
309
|
+
text:
|
|
310
|
+
option.callbackData === selectedCallbackData
|
|
311
|
+
? `✅ ${option.label}`
|
|
312
|
+
: option.label,
|
|
313
|
+
callback_data: option.callbackData,
|
|
314
|
+
},
|
|
315
|
+
]),
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (
|
|
320
|
+
error instanceof Error &&
|
|
321
|
+
/message is not modified/i.test(error.message)
|
|
322
|
+
) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function getTelegramUpdateStream(
|
|
331
|
+
botToken: string,
|
|
332
|
+
offset = 0
|
|
333
|
+
): TelegramUpdateStream {
|
|
334
|
+
const existing = updateStreams.get(botToken);
|
|
335
|
+
|
|
336
|
+
if (existing) {
|
|
337
|
+
existing.setMinimumOffset(offset);
|
|
338
|
+
return existing;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const stream = new TelegramUpdateStream(botToken, offset);
|
|
342
|
+
updateStreams.set(botToken, stream);
|
|
343
|
+
return stream;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class TelegramUpdateStream {
|
|
347
|
+
private currentOffset: number;
|
|
348
|
+
private readonly waiters = new Set<TelegramWaiter<unknown>>();
|
|
349
|
+
private pollPromise: Promise<void> | null = null;
|
|
350
|
+
private pollAbortController: AbortController | null = null;
|
|
351
|
+
|
|
352
|
+
constructor(private readonly botToken: string, initialOffset: number) {
|
|
353
|
+
this.currentOffset = initialOffset;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
setMinimumOffset(offset: number): void {
|
|
357
|
+
this.currentOffset = Math.max(this.currentOffset, offset);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async waitFor<T>(
|
|
361
|
+
match: (update: TelegramUpdate) => T | undefined,
|
|
362
|
+
options: TelegramWaitOptions = {}
|
|
363
|
+
): Promise<T> {
|
|
364
|
+
if (options.signal?.aborted) {
|
|
365
|
+
throw options.signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return new Promise<T>((resolve, reject) => {
|
|
369
|
+
const abortListener = () => {
|
|
370
|
+
unregister();
|
|
371
|
+
reject(
|
|
372
|
+
options.signal?.reason ?? new DOMException("Aborted", "AbortError")
|
|
373
|
+
);
|
|
374
|
+
};
|
|
375
|
+
const timeout =
|
|
376
|
+
typeof options.timeoutMs === "number"
|
|
377
|
+
? setTimeout(() => {
|
|
378
|
+
unregister();
|
|
379
|
+
reject(new Error("Timed out waiting"));
|
|
380
|
+
}, options.timeoutMs)
|
|
381
|
+
: null;
|
|
382
|
+
|
|
383
|
+
const waiter: TelegramWaiter<T> = {
|
|
384
|
+
match,
|
|
385
|
+
resolve: (value) => {
|
|
386
|
+
unregister();
|
|
387
|
+
resolve(value);
|
|
388
|
+
},
|
|
389
|
+
reject: (error) => {
|
|
390
|
+
unregister();
|
|
391
|
+
reject(error);
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const unregister = () => {
|
|
396
|
+
if (timeout) {
|
|
397
|
+
clearTimeout(timeout);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
options.signal?.removeEventListener("abort", abortListener);
|
|
401
|
+
this.waiters.delete(waiter as TelegramWaiter<unknown>);
|
|
402
|
+
|
|
403
|
+
if (this.waiters.size === 0) {
|
|
404
|
+
this.pollAbortController?.abort();
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
this.waiters.add(waiter as TelegramWaiter<unknown>);
|
|
409
|
+
options.signal?.addEventListener("abort", abortListener, { once: true });
|
|
410
|
+
this.ensurePolling();
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private ensurePolling(): void {
|
|
415
|
+
if (this.pollPromise) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
this.pollPromise = this.pollLoop().finally(() => {
|
|
420
|
+
this.pollPromise = null;
|
|
421
|
+
|
|
422
|
+
if (this.waiters.size === 0) {
|
|
423
|
+
updateStreams.delete(this.botToken);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private async pollLoop(): Promise<void> {
|
|
429
|
+
while (this.waiters.size > 0) {
|
|
430
|
+
this.pollAbortController = new AbortController();
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
const updates = await telegramRequest<TelegramUpdate[]>(
|
|
434
|
+
this.botToken,
|
|
435
|
+
"getUpdates",
|
|
436
|
+
{
|
|
437
|
+
offset: this.currentOffset,
|
|
438
|
+
timeout: 0,
|
|
439
|
+
allowed_updates: allowedUpdateTypes,
|
|
440
|
+
},
|
|
441
|
+
this.pollAbortController.signal
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
for (const update of updates) {
|
|
445
|
+
this.currentOffset = Math.max(
|
|
446
|
+
this.currentOffset,
|
|
447
|
+
update.update_id + 1
|
|
448
|
+
);
|
|
449
|
+
this.dispatch(update);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (updates.length === 0 && this.waiters.size > 0) {
|
|
453
|
+
await delay(shortPollIntervalMs, this.pollAbortController.signal);
|
|
454
|
+
}
|
|
455
|
+
} catch (error) {
|
|
456
|
+
if (isAbortError(error) && this.waiters.size === 0) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.rejectAll(error);
|
|
461
|
+
return;
|
|
462
|
+
} finally {
|
|
463
|
+
this.pollAbortController = null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
private dispatch(update: TelegramUpdate): void {
|
|
469
|
+
for (const waiter of Array.from(this.waiters)) {
|
|
470
|
+
try {
|
|
471
|
+
const result = waiter.match(update);
|
|
472
|
+
|
|
473
|
+
if (result !== undefined) {
|
|
474
|
+
waiter.resolve(result);
|
|
475
|
+
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
waiter.reject(error);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private rejectAll(error: unknown): void {
|
|
483
|
+
for (const waiter of Array.from(this.waiters)) {
|
|
484
|
+
waiter.reject(error);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function isAbortError(error: unknown): boolean {
|
|
490
|
+
return error instanceof DOMException
|
|
491
|
+
? error.name === "AbortError"
|
|
492
|
+
: error instanceof Error && error.name === "AbortError";
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
496
|
+
if (signal?.aborted) {
|
|
497
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
await new Promise<void>((resolve, reject) => {
|
|
501
|
+
const onAbort = () => {
|
|
502
|
+
clearTimeout(timeout);
|
|
503
|
+
signal?.removeEventListener("abort", onAbort);
|
|
504
|
+
const abortReason =
|
|
505
|
+
signal?.reason ?? new DOMException("Aborted", "AbortError");
|
|
506
|
+
reject(abortReason);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const timeout = setTimeout(() => {
|
|
510
|
+
signal?.removeEventListener("abort", onAbort);
|
|
511
|
+
resolve();
|
|
512
|
+
}, ms);
|
|
513
|
+
|
|
514
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function formatTelegramFetchError(method: string, error: unknown): string {
|
|
519
|
+
if (!(error instanceof Error)) {
|
|
520
|
+
return `Telegram ${method} request failed: ${String(error)}`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const causeMessage =
|
|
524
|
+
typeof error.cause === "object" &&
|
|
525
|
+
error.cause !== null &&
|
|
526
|
+
"message" in error.cause &&
|
|
527
|
+
typeof error.cause.message === "string"
|
|
528
|
+
? error.cause.message
|
|
529
|
+
: null;
|
|
530
|
+
|
|
531
|
+
if (causeMessage) {
|
|
532
|
+
return `Telegram ${method} request failed: ${error.message} (${causeMessage})`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return `Telegram ${method} request failed: ${error.message}`;
|
|
536
|
+
}
|