telegix 1.1.3-a1 → 1.1.4
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/index.d.ts +44 -0
- package/index.js +7 -1
- package/lib/api.js +34 -18
- package/lib/context.js +1 -1
- package/lib/telegix.js +10 -1
- package/lib/webhook.js +405 -1
- package/package.json +7 -1
package/index.d.ts
CHANGED
|
@@ -442,6 +442,15 @@ export class Telegix extends Composer {
|
|
|
442
442
|
stop(reason?: string): Promise<void>;
|
|
443
443
|
launch(options?: { polling?: boolean | object; webhook?: object; dropPendingUpdates?: boolean }): Promise<User>;
|
|
444
444
|
webhookCallback(path?: string, options?: object): (req: any, res: any, next?: Function) => Promise<void>;
|
|
445
|
+
pagesWebhookHandler(options?: {
|
|
446
|
+
secretToken?: string | ((env: any) => string);
|
|
447
|
+
path?: string;
|
|
448
|
+
waitUntil?: boolean;
|
|
449
|
+
respondOnError?: number;
|
|
450
|
+
enableHealthCheck?: boolean;
|
|
451
|
+
onUpdate?: (update: any, context: any) => Promise<void> | void;
|
|
452
|
+
onError?: (err: Error, context: any) => any;
|
|
453
|
+
}): (context: any, env?: any, ctx?: any) => Promise<Response>;
|
|
445
454
|
}
|
|
446
455
|
|
|
447
456
|
export class MemorySessionStore {
|
|
@@ -656,4 +665,39 @@ export class TelegramError extends TelegixError {
|
|
|
656
665
|
export class NetworkError extends TelegixError {}
|
|
657
666
|
export class PollingError extends TelegixError {}
|
|
658
667
|
|
|
668
|
+
export function createWebhookCallback(
|
|
669
|
+
bot: Telegix,
|
|
670
|
+
path?: string,
|
|
671
|
+
options?: { secretToken?: string }
|
|
672
|
+
): (req: any, res: any, next?: Function) => Promise<void>;
|
|
673
|
+
|
|
674
|
+
export function createPagesWebhookHandler(
|
|
675
|
+
botOrFactory?: Telegix | ((env: any, context?: any) => Telegix),
|
|
676
|
+
options?: {
|
|
677
|
+
secretToken?: string | ((env: any) => string);
|
|
678
|
+
path?: string;
|
|
679
|
+
waitUntil?: boolean;
|
|
680
|
+
respondOnError?: number;
|
|
681
|
+
enableHealthCheck?: boolean;
|
|
682
|
+
onUpdate?: (update: any, context: any) => Promise<void> | void;
|
|
683
|
+
onError?: (err: Error, context: any) => any;
|
|
684
|
+
}
|
|
685
|
+
): (context: any, env?: any, ctx?: any) => Promise<Response>;
|
|
686
|
+
|
|
687
|
+
export function createCloudflareWebhookCallback(
|
|
688
|
+
botOrFactory?: Telegix | ((env: any, context?: any) => Telegix),
|
|
689
|
+
options?: object
|
|
690
|
+
): (context: any, env?: any, ctx?: any) => Promise<Response>;
|
|
691
|
+
|
|
692
|
+
export function handleCloudflareWebhook(
|
|
693
|
+
context: any,
|
|
694
|
+
bot: Telegix,
|
|
695
|
+
options?: object
|
|
696
|
+
): Promise<Response>;
|
|
697
|
+
|
|
698
|
+
export function createPagesSetWebhookHandler(
|
|
699
|
+
botOrFactory?: Telegix | ((env: any) => Telegix),
|
|
700
|
+
options?: { webhookPath?: string; secretToken?: string }
|
|
701
|
+
): (context: any) => Promise<Response>;
|
|
702
|
+
|
|
659
703
|
export default Telegix;
|
package/index.js
CHANGED
|
@@ -11,7 +11,13 @@ export { Composer, compose } from './lib/composer.js';
|
|
|
11
11
|
export { Markup, KeyboardBuilder } from './lib/markup.js';
|
|
12
12
|
export { session, MemorySessionStore, FileSessionStore } from './lib/session.js';
|
|
13
13
|
export { Polling } from './lib/polling.js';
|
|
14
|
-
export {
|
|
14
|
+
export {
|
|
15
|
+
createWebhookCallback,
|
|
16
|
+
createPagesWebhookHandler,
|
|
17
|
+
createCloudflareWebhookCallback,
|
|
18
|
+
handleCloudflareWebhook,
|
|
19
|
+
createPagesSetWebhookHandler,
|
|
20
|
+
} from './lib/webhook.js';
|
|
15
21
|
export { fmt, Format, escapeHtml, escapeMarkdown, html, markdown } from './lib/format.js';
|
|
16
22
|
export { Table, InputRichBlockTable, RichBlockTable } from './lib/table.js';
|
|
17
23
|
export { EphemeralMessageParameters, ReplyParameters, BotCommand } from './lib/ephemeral.js';
|
package/lib/api.js
CHANGED
|
@@ -30,7 +30,13 @@ export function isUploadableFile(value, key = '') {
|
|
|
30
30
|
) {
|
|
31
31
|
return false;
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
value instanceof Blob ||
|
|
35
|
+
value instanceof Uint8Array ||
|
|
36
|
+
(typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(value))
|
|
37
|
+
) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
34
40
|
if (typeof value === 'object') {
|
|
35
41
|
// If it's a LinkPreview or has link preview options properties, it's not a file
|
|
36
42
|
if (
|
|
@@ -57,20 +63,24 @@ export function isUploadableFile(value, key = '') {
|
|
|
57
63
|
*/
|
|
58
64
|
export async function normalizeFileSource(input, defaultFilename = 'file') {
|
|
59
65
|
if (typeof input === 'string') {
|
|
60
|
-
// If it's a file path on local filesystem
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// If it's a file path on local filesystem (Node.js environment)
|
|
67
|
+
try {
|
|
68
|
+
if (fs && typeof fs.existsSync === 'function' && fs.existsSync(input)) {
|
|
69
|
+
const buffer = await fs.promises.readFile(input);
|
|
70
|
+
const filename = (path && typeof path.basename === 'function' && path.basename(input)) || defaultFilename;
|
|
71
|
+
return {
|
|
72
|
+
blob: new Blob([buffer]),
|
|
73
|
+
filename,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Ignored in non-filesystem/edge environments
|
|
68
78
|
}
|
|
69
79
|
// Otherwise it's a file_id or URL string
|
|
70
80
|
return { blob: input };
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
|
|
83
|
+
if ((typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(input)) || input instanceof Uint8Array) {
|
|
74
84
|
return {
|
|
75
85
|
blob: new Blob([input]),
|
|
76
86
|
filename: defaultFilename,
|
|
@@ -93,16 +103,20 @@ export async function normalizeFileSource(input, defaultFilename = 'file') {
|
|
|
93
103
|
|
|
94
104
|
if (input.source) {
|
|
95
105
|
if (typeof input.source === 'string') {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
try {
|
|
107
|
+
if (fs && typeof fs.existsSync === 'function' && fs.existsSync(input.source)) {
|
|
108
|
+
const buffer = await fs.promises.readFile(input.source);
|
|
109
|
+
return {
|
|
110
|
+
blob: new Blob([buffer]),
|
|
111
|
+
filename: input.filename || (path && typeof path.basename === 'function' && path.basename(input.source)) || defaultFilename,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
// Ignored
|
|
102
116
|
}
|
|
103
117
|
return { blob: input.source };
|
|
104
118
|
}
|
|
105
|
-
if (Buffer.isBuffer(input.source) || input.source instanceof Uint8Array) {
|
|
119
|
+
if ((typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(input.source)) || input.source instanceof Uint8Array) {
|
|
106
120
|
return {
|
|
107
121
|
blob: new Blob([input.source]),
|
|
108
122
|
filename,
|
|
@@ -1954,7 +1968,9 @@ export class Telegram {
|
|
|
1954
1968
|
sendCardTables(chatId, tables, options = {}) {
|
|
1955
1969
|
if (options.asImage || options.photo || options.image) {
|
|
1956
1970
|
const svg = Table.multiCardSvg(tables, options);
|
|
1957
|
-
const buffer = Buffer.from
|
|
1971
|
+
const buffer = typeof Buffer !== 'undefined' && typeof Buffer.from === 'function'
|
|
1972
|
+
? Buffer.from(svg, 'utf-8')
|
|
1973
|
+
: new Blob([svg], { type: 'image/svg+xml' });
|
|
1958
1974
|
return this.sendPhoto(chatId, {
|
|
1959
1975
|
source: buffer,
|
|
1960
1976
|
filename: 'tables-cards.svg',
|
package/lib/context.js
CHANGED
|
@@ -1019,7 +1019,7 @@ export class Context {
|
|
|
1019
1019
|
rows = [
|
|
1020
1020
|
['Engine', data.engine || 'Telegix'],
|
|
1021
1021
|
['Runtime', data.runtime || '0h 19m 32s'],
|
|
1022
|
-
['Node', data.node || process.version || 'v23.11'],
|
|
1022
|
+
['Node', data.node || (typeof process !== 'undefined' && process.version ? process.version : 'Cloudflare Edge') || 'v23.11'],
|
|
1023
1023
|
['Features', data.features ?? 514],
|
|
1024
1024
|
['Groups', data.groups ?? 168],
|
|
1025
1025
|
['Users', data.users ?? 9528],
|
package/lib/telegix.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Composer } from './composer.js';
|
|
|
7
7
|
import { Telegram } from './api.js';
|
|
8
8
|
import { Context } from './context.js';
|
|
9
9
|
import { Polling } from './polling.js';
|
|
10
|
-
import { createWebhookCallback } from './webhook.js';
|
|
10
|
+
import { createWebhookCallback, createPagesWebhookHandler } from './webhook.js';
|
|
11
11
|
import { TelegixError } from './errors.js';
|
|
12
12
|
|
|
13
13
|
export class Telegix extends Composer {
|
|
@@ -173,4 +173,13 @@ export class Telegix extends Composer {
|
|
|
173
173
|
webhookCallback(path = '/', options = {}) {
|
|
174
174
|
return createWebhookCallback(this, path, options);
|
|
175
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Returns a Cloudflare Pages Functions / Cloudflare Workers / Fetch API webhook handler
|
|
179
|
+
* @param {object} [options]
|
|
180
|
+
* @returns {Function}
|
|
181
|
+
*/
|
|
182
|
+
pagesWebhookHandler(options = {}) {
|
|
183
|
+
return createPagesWebhookHandler(this, options);
|
|
184
|
+
}
|
|
176
185
|
}
|
package/lib/webhook.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegix - Webhook Handler & Adapter
|
|
3
|
+
* Supports standard Node.js HTTP servers (Express, Fastify, Connect)
|
|
4
|
+
* AND Cloudflare Pages Functions, Cloudflare Workers, and Web Fetch API.
|
|
3
5
|
* @module telegix/webhook
|
|
4
6
|
*/
|
|
5
7
|
|
|
@@ -55,7 +57,9 @@ export function createWebhookCallback(bot, path = '/', options = {}) {
|
|
|
55
57
|
for await (const chunk of req) {
|
|
56
58
|
chunks.push(chunk);
|
|
57
59
|
}
|
|
58
|
-
const rawBody = Buffer.concat
|
|
60
|
+
const rawBody = (typeof Buffer !== 'undefined' && Buffer.concat)
|
|
61
|
+
? Buffer.concat(chunks).toString('utf8')
|
|
62
|
+
: chunks.map((c) => (typeof c === 'string' ? c : new TextDecoder().decode(c))).join('');
|
|
59
63
|
update = JSON.parse(rawBody);
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -84,3 +88,403 @@ export function createWebhookCallback(bot, path = '/', options = {}) {
|
|
|
84
88
|
}
|
|
85
89
|
};
|
|
86
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a Cloudflare Pages Functions / Cloudflare Workers / Web Fetch API webhook handler.
|
|
94
|
+
* Compatible with:
|
|
95
|
+
* - Cloudflare Pages Functions: `export const onRequest = createPagesWebhookHandler(bot, options);`
|
|
96
|
+
* - Cloudflare Pages POST only: `export const onRequestPost = createPagesWebhookHandler(bot, options);`
|
|
97
|
+
* - Cloudflare Workers: `export default { fetch: createPagesWebhookHandler(bot, options) };`
|
|
98
|
+
*
|
|
99
|
+
* @param {import('./telegix.js').Telegix | ((env: any, context: any) => import('./telegix.js').Telegix)} [botOrFactory]
|
|
100
|
+
* @param {object} [options]
|
|
101
|
+
* @param {string|((env: any) => string)} [options.secretToken] - Secret token for header verification or getter function
|
|
102
|
+
* @param {string} [options.path] - Optional path restriction (e.g. '/webhook')
|
|
103
|
+
* @param {boolean} [options.waitUntil=true] - Use context.waitUntil() to respond 200 OK instantly and process in background
|
|
104
|
+
* @param {number} [options.respondOnError=200] - Status to respond with on unhandled update error (default 200 prevents Telegram retry storm)
|
|
105
|
+
* @param {boolean} [options.enableHealthCheck=true] - If true, GET requests return a JSON health check status
|
|
106
|
+
* @param {Function} [options.onUpdate] - Optional callback when update is received
|
|
107
|
+
* @param {Function} [options.onError] - Custom error handler
|
|
108
|
+
* @returns {Function} Universal Cloudflare Pages / Workers handler
|
|
109
|
+
*/
|
|
110
|
+
export function createPagesWebhookHandler(botOrFactory, options = {}) {
|
|
111
|
+
const opts = {
|
|
112
|
+
waitUntil: true,
|
|
113
|
+
respondOnError: 200,
|
|
114
|
+
enableHealthCheck: true,
|
|
115
|
+
...options,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Cache for bot instance when initialized via factory
|
|
119
|
+
let cachedBot = typeof botOrFactory === 'object' && botOrFactory !== null ? botOrFactory : null;
|
|
120
|
+
|
|
121
|
+
return async function handlePagesRequest(arg1, arg2, arg3) {
|
|
122
|
+
let request;
|
|
123
|
+
let env = {};
|
|
124
|
+
let waitUntil = null;
|
|
125
|
+
|
|
126
|
+
// Detect Cloudflare Pages context vs Cloudflare Workers vs standard Fetch
|
|
127
|
+
if (arg1 && typeof arg1 === 'object' && 'request' in arg1) {
|
|
128
|
+
// Cloudflare Pages: context = { request, env, waitUntil, next, params, data }
|
|
129
|
+
request = arg1.request;
|
|
130
|
+
env = arg1.env || {};
|
|
131
|
+
if (typeof arg1.waitUntil === 'function') {
|
|
132
|
+
waitUntil = arg1.waitUntil.bind(arg1);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
// Cloudflare Workers: (request, env, ctx) or Fetch: (request)
|
|
136
|
+
request = arg1;
|
|
137
|
+
env = arg2 || {};
|
|
138
|
+
if (arg3 && typeof arg3.waitUntil === 'function') {
|
|
139
|
+
waitUntil = arg3.waitUntil.bind(arg3);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!request || typeof request.method !== 'string') {
|
|
144
|
+
return new Response('Invalid Request Object', { status: 400 });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const method = request.method.toUpperCase();
|
|
148
|
+
const url = new URL(request.url);
|
|
149
|
+
|
|
150
|
+
// 1. CORS Preflight
|
|
151
|
+
if (method === 'OPTIONS') {
|
|
152
|
+
return new Response(null, {
|
|
153
|
+
status: 204,
|
|
154
|
+
headers: {
|
|
155
|
+
'Access-Control-Allow-Origin': '*',
|
|
156
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
157
|
+
'Access-Control-Allow-Headers': 'Content-Type, X-Telegram-Bot-Api-Secret-Token',
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 2. Path validation if specified
|
|
163
|
+
if (opts.path && opts.path !== '/' && url.pathname !== opts.path) {
|
|
164
|
+
return new Response('Not Found', { status: 404 });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 3. GET Method: Health check & diagnostics
|
|
168
|
+
if (method === 'GET') {
|
|
169
|
+
if (opts.enableHealthCheck) {
|
|
170
|
+
const hasToken = Boolean(
|
|
171
|
+
(env && (env.BOT_TOKEN || env.TELEGRAM_BOT_TOKEN)) ||
|
|
172
|
+
cachedBot?.telegram?.token
|
|
173
|
+
);
|
|
174
|
+
const hasSecret = Boolean(
|
|
175
|
+
opts.secretToken ||
|
|
176
|
+
(env && (env.WEBHOOK_SECRET || env.SECRET_TOKEN))
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return new Response(
|
|
180
|
+
JSON.stringify(
|
|
181
|
+
{
|
|
182
|
+
status: 'ok',
|
|
183
|
+
engine: 'Telegix',
|
|
184
|
+
runtime: 'Cloudflare Pages Functions / Workers',
|
|
185
|
+
service: 'Telegram Bot Webhook Endpoint',
|
|
186
|
+
timestamp: new Date().toISOString(),
|
|
187
|
+
endpoint: url.pathname,
|
|
188
|
+
configured: {
|
|
189
|
+
botToken: hasToken,
|
|
190
|
+
secretTokenVerification: hasSecret,
|
|
191
|
+
backgroundExecution: Boolean(opts.waitUntil && waitUntil),
|
|
192
|
+
},
|
|
193
|
+
hint: 'Send POST updates from Telegram to this URL.',
|
|
194
|
+
},
|
|
195
|
+
null,
|
|
196
|
+
2
|
|
197
|
+
),
|
|
198
|
+
{
|
|
199
|
+
status: 200,
|
|
200
|
+
headers: {
|
|
201
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
202
|
+
'Access-Control-Allow-Origin': '*',
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return new Response('Method Not Allowed', { status: 405 });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 4. Webhook updates MUST be POST
|
|
211
|
+
if (method !== 'POST') {
|
|
212
|
+
return new Response('Method Not Allowed', { status: 405 });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 5. Secret Token Verification
|
|
216
|
+
let secretToken = opts.secretToken;
|
|
217
|
+
if (typeof secretToken === 'function') {
|
|
218
|
+
secretToken = secretToken(env);
|
|
219
|
+
} else if (!secretToken && env) {
|
|
220
|
+
secretToken = env.WEBHOOK_SECRET || env.SECRET_TOKEN;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (secretToken) {
|
|
224
|
+
const receivedToken = request.headers.get('x-telegram-bot-api-secret-token');
|
|
225
|
+
if (receivedToken !== secretToken) {
|
|
226
|
+
return new Response('Forbidden: Invalid Webhook Secret Token', {
|
|
227
|
+
status: 403,
|
|
228
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 6. Resolve Bot Instance
|
|
234
|
+
let bot = cachedBot;
|
|
235
|
+
if (!bot) {
|
|
236
|
+
if (typeof botOrFactory === 'function') {
|
|
237
|
+
bot = botOrFactory(env, { request, env, waitUntil });
|
|
238
|
+
cachedBot = bot;
|
|
239
|
+
} else if (opts.getBot && typeof opts.getBot === 'function') {
|
|
240
|
+
bot = opts.getBot(env, { request, env, waitUntil });
|
|
241
|
+
} else if (env && (env.BOT_TOKEN || env.TELEGRAM_BOT_TOKEN)) {
|
|
242
|
+
const token = env.BOT_TOKEN || env.TELEGRAM_BOT_TOKEN;
|
|
243
|
+
const { Telegix } = await import('./telegix.js');
|
|
244
|
+
bot = new Telegix(token);
|
|
245
|
+
cachedBot = bot;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!bot) {
|
|
250
|
+
return new Response(
|
|
251
|
+
'Server Configuration Error: Telegix Bot instance or BOT_TOKEN is missing in Cloudflare environment.',
|
|
252
|
+
{
|
|
253
|
+
status: 500,
|
|
254
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 7. Parse incoming Telegram Update JSON
|
|
260
|
+
let update = null;
|
|
261
|
+
try {
|
|
262
|
+
update = await request.json();
|
|
263
|
+
} catch {
|
|
264
|
+
return new Response('Bad Request: Invalid JSON Payload', {
|
|
265
|
+
status: 400,
|
|
266
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!update || typeof update !== 'object') {
|
|
271
|
+
return new Response('Bad Request: Expected Telegram update object', {
|
|
272
|
+
status: 400,
|
|
273
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 8. Optional user hook before processing
|
|
278
|
+
if (typeof opts.onUpdate === 'function') {
|
|
279
|
+
try {
|
|
280
|
+
await opts.onUpdate(update, { request, env, waitUntil, bot });
|
|
281
|
+
} catch (hookErr) {
|
|
282
|
+
console.warn('[Telegix Webhook] onUpdate hook error:', hookErr);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 9. Pipeline execution
|
|
287
|
+
const processTask = (async () => {
|
|
288
|
+
try {
|
|
289
|
+
await bot.handleUpdate(update);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error('[Telegix Webhook] Error processing update:', err);
|
|
292
|
+
if (typeof opts.onError === 'function') {
|
|
293
|
+
opts.onError(err, { update, request, env, bot });
|
|
294
|
+
} else if (bot.errorHandler) {
|
|
295
|
+
bot.errorHandler(err);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
})();
|
|
299
|
+
|
|
300
|
+
// 10. If waitUntil is enabled and supported by Cloudflare, return 200 OK immediately
|
|
301
|
+
// to prevent Telegram webhook timeouts (< 5 seconds)!
|
|
302
|
+
if (opts.waitUntil && typeof waitUntil === 'function') {
|
|
303
|
+
waitUntil(processTask);
|
|
304
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
305
|
+
status: 200,
|
|
306
|
+
headers: {
|
|
307
|
+
'Content-Type': 'application/json',
|
|
308
|
+
'Access-Control-Allow-Origin': '*',
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Otherwise await synchronously
|
|
314
|
+
try {
|
|
315
|
+
await processTask;
|
|
316
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
317
|
+
status: 200,
|
|
318
|
+
headers: {
|
|
319
|
+
'Content-Type': 'application/json',
|
|
320
|
+
'Access-Control-Allow-Origin': '*',
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
} catch (err) {
|
|
324
|
+
const status = typeof opts.respondOnError === 'number' ? opts.respondOnError : 200;
|
|
325
|
+
return new Response(JSON.stringify({ ok: false, error: err.message }), {
|
|
326
|
+
status,
|
|
327
|
+
headers: {
|
|
328
|
+
'Content-Type': 'application/json',
|
|
329
|
+
'Access-Control-Allow-Origin': '*',
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Direct function to handle a Cloudflare Pages or Workers request
|
|
338
|
+
* @param {object} context - Cloudflare Pages context or request
|
|
339
|
+
* @param {import('./telegix.js').Telegix} bot
|
|
340
|
+
* @param {object} [options]
|
|
341
|
+
* @returns {Promise<Response>}
|
|
342
|
+
*/
|
|
343
|
+
export async function handleCloudflareWebhook(context, bot, options = {}) {
|
|
344
|
+
const handler = createPagesWebhookHandler(bot, options);
|
|
345
|
+
return handler(context);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Alias for createPagesWebhookHandler for Cloudflare Workers / Edge environments
|
|
350
|
+
*/
|
|
351
|
+
export const createCloudflareWebhookCallback = createPagesWebhookHandler;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Helper handler for Cloudflare Pages to set, check, or delete Telegram Webhook via HTTP
|
|
355
|
+
* Place in `functions/setWebhook.js` for instant webhook registration in your browser or curl.
|
|
356
|
+
*
|
|
357
|
+
* @param {import('./telegix.js').Telegix | ((env: any) => import('./telegix.js').Telegix)} [botOrFactory]
|
|
358
|
+
* @param {object} [options]
|
|
359
|
+
* @param {string} [options.webhookPath='/webhook']
|
|
360
|
+
* @returns {Function} Cloudflare Pages Request Handler
|
|
361
|
+
*/
|
|
362
|
+
export function createPagesSetWebhookHandler(botOrFactory, options = {}) {
|
|
363
|
+
const webhookPath = options.webhookPath || '/webhook';
|
|
364
|
+
|
|
365
|
+
return async function handleSetWebhook(context) {
|
|
366
|
+
const request = context.request || context;
|
|
367
|
+
const env = context.env || {};
|
|
368
|
+
const url = new URL(request.url);
|
|
369
|
+
const action = url.searchParams.get('action') || (request.method === 'POST' ? 'set' : 'info');
|
|
370
|
+
|
|
371
|
+
// Resolve bot instance
|
|
372
|
+
let bot = typeof botOrFactory === 'object' && botOrFactory !== null ? botOrFactory : null;
|
|
373
|
+
if (!bot) {
|
|
374
|
+
if (typeof botOrFactory === 'function') {
|
|
375
|
+
bot = botOrFactory(env);
|
|
376
|
+
} else if (env && (env.BOT_TOKEN || env.TELEGRAM_BOT_TOKEN)) {
|
|
377
|
+
const token = env.BOT_TOKEN || env.TELEGRAM_BOT_TOKEN;
|
|
378
|
+
const { Telegix } = await import('./telegix.js');
|
|
379
|
+
bot = new Telegix(token);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (!bot) {
|
|
384
|
+
return new Response(
|
|
385
|
+
JSON.stringify(
|
|
386
|
+
{
|
|
387
|
+
ok: false,
|
|
388
|
+
error: 'BOT_TOKEN is not configured in Cloudflare environment variables.',
|
|
389
|
+
},
|
|
390
|
+
null,
|
|
391
|
+
2
|
|
392
|
+
),
|
|
393
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const secretToken = options.secretToken || env.WEBHOOK_SECRET || env.SECRET_TOKEN || undefined;
|
|
398
|
+
const targetWebhookUrl = `${url.origin}${webhookPath}`;
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
if (action === 'set') {
|
|
402
|
+
const dropPending = url.searchParams.get('drop_pending_updates') === 'true';
|
|
403
|
+
const result = await bot.telegram.setWebhook(targetWebhookUrl, {
|
|
404
|
+
secret_token: secretToken,
|
|
405
|
+
drop_pending_updates: dropPending,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const info = await bot.telegram.getWebhookInfo();
|
|
409
|
+
|
|
410
|
+
return new Response(
|
|
411
|
+
JSON.stringify(
|
|
412
|
+
{
|
|
413
|
+
ok: true,
|
|
414
|
+
action: 'setWebhook',
|
|
415
|
+
message: 'Telegram Webhook successfully set for Cloudflare Pages!',
|
|
416
|
+
webhookUrl: targetWebhookUrl,
|
|
417
|
+
secretTokenSet: Boolean(secretToken),
|
|
418
|
+
telegramResponse: result,
|
|
419
|
+
currentWebhookInfo: info,
|
|
420
|
+
},
|
|
421
|
+
null,
|
|
422
|
+
2
|
|
423
|
+
),
|
|
424
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (action === 'delete') {
|
|
429
|
+
const dropPending = url.searchParams.get('drop_pending_updates') === 'true';
|
|
430
|
+
const result = await bot.telegram.deleteWebhook({
|
|
431
|
+
drop_pending_updates: dropPending,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return new Response(
|
|
435
|
+
JSON.stringify(
|
|
436
|
+
{
|
|
437
|
+
ok: true,
|
|
438
|
+
action: 'deleteWebhook',
|
|
439
|
+
message: 'Telegram Webhook successfully deleted.',
|
|
440
|
+
telegramResponse: result,
|
|
441
|
+
},
|
|
442
|
+
null,
|
|
443
|
+
2
|
|
444
|
+
),
|
|
445
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Default: show current Webhook Info
|
|
450
|
+
const info = await bot.telegram.getWebhookInfo();
|
|
451
|
+
const me = await bot.telegram.getMe().catch(() => null);
|
|
452
|
+
|
|
453
|
+
return new Response(
|
|
454
|
+
JSON.stringify(
|
|
455
|
+
{
|
|
456
|
+
ok: true,
|
|
457
|
+
action: 'getWebhookInfo',
|
|
458
|
+
bot: me
|
|
459
|
+
? { id: me.id, username: me.username, first_name: me.first_name }
|
|
460
|
+
: 'Unknown',
|
|
461
|
+
targetWebhookUrl,
|
|
462
|
+
currentWebhookInfo: info,
|
|
463
|
+
actions: {
|
|
464
|
+
setWebhook: `${url.origin}/setWebhook?action=set`,
|
|
465
|
+
deleteWebhook: `${url.origin}/setWebhook?action=delete`,
|
|
466
|
+
setWithDropUpdates: `${url.origin}/setWebhook?action=set&drop_pending_updates=true`,
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
null,
|
|
470
|
+
2
|
|
471
|
+
),
|
|
472
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
473
|
+
);
|
|
474
|
+
} catch (err) {
|
|
475
|
+
return new Response(
|
|
476
|
+
JSON.stringify(
|
|
477
|
+
{
|
|
478
|
+
ok: false,
|
|
479
|
+
error: err.message,
|
|
480
|
+
code: err.code || err.error_code,
|
|
481
|
+
},
|
|
482
|
+
null,
|
|
483
|
+
2
|
|
484
|
+
),
|
|
485
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "telegix",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Lightweight Telegram Bot API framework for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.cjs",
|
|
@@ -38,5 +38,11 @@
|
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
|
40
40
|
"url": "https://github.com/KazeDevID/telegix.git"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "node server.js",
|
|
44
|
+
"build": "node -e 'console.log(\"Telegix build complete\")'",
|
|
45
|
+
"start": "node server.js",
|
|
46
|
+
"lint": "node -c index.js server.js lib/*.js"
|
|
41
47
|
}
|
|
42
48
|
}
|