@codebam/cf-workers-telegram-bot 11.18.0 → 12.0.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/dist/telegram_api.d.ts +23 -12
- package/dist/telegram_api.js +52 -25
- package/dist/telegram_bot.js +4 -0
- package/dist/telegram_execution_context.d.ts +15 -9
- package/dist/telegram_execution_context.js +223 -191
- package/dist/utils.js +4 -3
- package/package.json +2 -2
package/dist/telegram_api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InlineQueryResult as TelegramInlineQueryResult } from '@grammyjs/types';
|
|
2
2
|
/** Interface for common Telegram API parameters */
|
|
3
3
|
export interface TelegramApiBaseParams {
|
|
4
|
+
[key: string]: unknown;
|
|
4
5
|
chat_id: number | string;
|
|
5
6
|
message_thread_id?: number;
|
|
6
7
|
business_connection_id?: string | number;
|
|
@@ -95,7 +96,19 @@ export interface SendVoiceParams extends TelegramApiBaseParams {
|
|
|
95
96
|
}
|
|
96
97
|
/** Type for all possible API parameters */
|
|
97
98
|
export type TelegramApiParams = SendMessageParams | SendPhotoParams | SendVideoParams | SendVoiceParams | SendChatActionParams | AnswerCallbackParams | AnswerInlineParams | AnswerGuestParams | SendInvoiceParams | AnswerPreCheckoutParams | Record<string, unknown>;
|
|
98
|
-
/**
|
|
99
|
+
/** Interface for edit message text parameters */
|
|
100
|
+
export interface EditMessageTextParams extends Partial<TelegramApiBaseParams> {
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
chat_id?: number | string;
|
|
103
|
+
message_id?: number;
|
|
104
|
+
inline_message_id?: string;
|
|
105
|
+
text: string;
|
|
106
|
+
parse_mode?: string;
|
|
107
|
+
disable_web_page_preview?: boolean;
|
|
108
|
+
reply_markup?: object;
|
|
109
|
+
business_connection_id?: string | number;
|
|
110
|
+
}
|
|
111
|
+
/** Class representing the telegram API and all its methods */
|
|
99
112
|
export default class TelegramApi {
|
|
100
113
|
/**
|
|
101
114
|
* Get the API URL for a given bot API and slug
|
|
@@ -104,7 +117,7 @@ export default class TelegramApi {
|
|
|
104
117
|
* @param data - data to append to the request
|
|
105
118
|
* @returns Request object with the full URL and parameters
|
|
106
119
|
*/
|
|
107
|
-
getApiUrl(botApi: string, slug: string, data: TelegramApiParams): Request;
|
|
120
|
+
getApiUrl(botApi: string, slug: string, data: TelegramApiParams, method?: 'GET' | 'POST'): Request;
|
|
108
121
|
/**
|
|
109
122
|
* Fetch a URL and log the response
|
|
110
123
|
* @param url - the URL to fetch
|
|
@@ -195,16 +208,7 @@ export default class TelegramApi {
|
|
|
195
208
|
* @param data - data to append to the request
|
|
196
209
|
* @returns Promise with the API response
|
|
197
210
|
*/
|
|
198
|
-
editMessageText(botApi: string, data:
|
|
199
|
-
chat_id?: number | string;
|
|
200
|
-
message_id?: number;
|
|
201
|
-
inline_message_id?: string;
|
|
202
|
-
text: string;
|
|
203
|
-
parse_mode?: string;
|
|
204
|
-
disable_web_page_preview?: boolean;
|
|
205
|
-
reply_markup?: object;
|
|
206
|
-
business_connection_id?: string | number;
|
|
207
|
-
}): Promise<Response>;
|
|
211
|
+
editMessageText(botApi: string, data: EditMessageTextParams): Promise<Response>;
|
|
208
212
|
sendMessageDraft(botApi: string, data: SendMessageDraftParams): Promise<Response>;
|
|
209
213
|
/**
|
|
210
214
|
* Send an invoice to a user
|
|
@@ -220,6 +224,13 @@ export default class TelegramApi {
|
|
|
220
224
|
* @returns Promise with the API response
|
|
221
225
|
*/
|
|
222
226
|
answerPreCheckoutQuery(botApi: string, data: AnswerPreCheckoutParams): Promise<Response>;
|
|
227
|
+
/**
|
|
228
|
+
* Get information about a business connection
|
|
229
|
+
* @param botApi - full URL to the telegram API without slug
|
|
230
|
+
* @param business_connection_id - unique identifier of the business connection
|
|
231
|
+
* @returns Promise with the API response
|
|
232
|
+
*/
|
|
233
|
+
getBusinessConnection(botApi: string, business_connection_id: string): Promise<Response>;
|
|
223
234
|
/**
|
|
224
235
|
* Get basic information about the bot
|
|
225
236
|
* @param botApi - full URL to the telegram API without slug
|
package/dist/telegram_api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Class representing the
|
|
1
|
+
/** Class representing the telegram API and all its methods */
|
|
2
2
|
export default class TelegramApi {
|
|
3
3
|
/**
|
|
4
4
|
* Get the API URL for a given bot API and slug
|
|
@@ -7,15 +7,24 @@ export default class TelegramApi {
|
|
|
7
7
|
* @param data - data to append to the request
|
|
8
8
|
* @returns Request object with the full URL and parameters
|
|
9
9
|
*/
|
|
10
|
-
getApiUrl(botApi, slug, data) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
getApiUrl(botApi, slug, data, method = 'GET') {
|
|
11
|
+
const baseUrl = botApi + (slug.startsWith('/') || botApi.endsWith('/') ? '' : '/') + slug;
|
|
12
|
+
if (method === 'GET') {
|
|
13
|
+
const url = new URL(baseUrl);
|
|
14
|
+
for (const [key, value] of Object.entries(data)) {
|
|
15
|
+
if (value !== undefined) {
|
|
16
|
+
url.searchParams.append(key, typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value));
|
|
17
|
+
}
|
|
16
18
|
}
|
|
19
|
+
return new Request(url.toString());
|
|
17
20
|
}
|
|
18
|
-
return new Request(
|
|
21
|
+
return new Request(baseUrl, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(data),
|
|
27
|
+
});
|
|
19
28
|
}
|
|
20
29
|
/**
|
|
21
30
|
* Fetch a URL and log the response
|
|
@@ -31,12 +40,20 @@ export default class TelegramApi {
|
|
|
31
40
|
let errorDescription = '';
|
|
32
41
|
try {
|
|
33
42
|
const json = (await cloned.json());
|
|
34
|
-
errorDescription = json.description
|
|
43
|
+
errorDescription = json.description || '';
|
|
35
44
|
}
|
|
36
45
|
catch {
|
|
37
46
|
// ignore
|
|
38
47
|
}
|
|
39
|
-
|
|
48
|
+
if (errorDescription.includes('BUSINESS_CONNECTION_INVALID') || errorDescription.includes('BUSINESS_PEER_INVALID')) {
|
|
49
|
+
console.warn(`Telegram API business error: ${errorDescription}`);
|
|
50
|
+
throw new Error('BUSINESS_CONNECTION_INVALID');
|
|
51
|
+
}
|
|
52
|
+
if (errorDescription.includes('PEER_ID_INVALID')) {
|
|
53
|
+
console.warn(`Telegram API peer error: ${errorDescription}`);
|
|
54
|
+
throw new Error('PEER_ID_INVALID');
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Telegram API error: ${String(response.status)} ${response.statusText}${errorDescription ? ': ' + errorDescription : ''}`);
|
|
40
57
|
}
|
|
41
58
|
const cloned = response.clone();
|
|
42
59
|
try {
|
|
@@ -59,7 +76,7 @@ export default class TelegramApi {
|
|
|
59
76
|
* @returns Promise with the API response
|
|
60
77
|
*/
|
|
61
78
|
async sendChatAction(botApi, data) {
|
|
62
|
-
const url = this.getApiUrl(botApi, 'sendChatAction', data);
|
|
79
|
+
const url = this.getApiUrl(botApi, 'sendChatAction', data, 'POST');
|
|
63
80
|
return await this.fetchAndLog(url, 'sendChatAction', data);
|
|
64
81
|
}
|
|
65
82
|
/**
|
|
@@ -73,7 +90,7 @@ export default class TelegramApi {
|
|
|
73
90
|
if (!data.file_id || data.file_id === '') {
|
|
74
91
|
throw new Error('No file_id provided');
|
|
75
92
|
}
|
|
76
|
-
const url = this.getApiUrl(botApi, 'getFile', data);
|
|
93
|
+
const url = this.getApiUrl(botApi, 'getFile', data, 'POST');
|
|
77
94
|
const response = await this.fetchAndLog(url, 'getFile', data);
|
|
78
95
|
const json = await response.json();
|
|
79
96
|
if (!json.ok || !json.result?.file_path) {
|
|
@@ -92,7 +109,7 @@ export default class TelegramApi {
|
|
|
92
109
|
* @returns Promise with the API response
|
|
93
110
|
*/
|
|
94
111
|
async sendMessage(botApi, data) {
|
|
95
|
-
const url = this.getApiUrl(botApi, 'sendMessage', data);
|
|
112
|
+
const url = this.getApiUrl(botApi, 'sendMessage', data, 'POST');
|
|
96
113
|
return await this.fetchAndLog(url, 'sendMessage', data);
|
|
97
114
|
}
|
|
98
115
|
/**
|
|
@@ -102,7 +119,7 @@ export default class TelegramApi {
|
|
|
102
119
|
* @returns Promise with the API response
|
|
103
120
|
*/
|
|
104
121
|
async sendVideo(botApi, data) {
|
|
105
|
-
const url = this.getApiUrl(botApi, 'sendVideo', data);
|
|
122
|
+
const url = this.getApiUrl(botApi, 'sendVideo', data, 'POST');
|
|
106
123
|
return await this.fetchAndLog(url, 'sendVideo', data);
|
|
107
124
|
}
|
|
108
125
|
/**
|
|
@@ -112,7 +129,7 @@ export default class TelegramApi {
|
|
|
112
129
|
* @returns Promise with the API response
|
|
113
130
|
*/
|
|
114
131
|
async sendPhoto(botApi, data) {
|
|
115
|
-
const url = this.getApiUrl(botApi, 'sendPhoto', data);
|
|
132
|
+
const url = this.getApiUrl(botApi, 'sendPhoto', data, 'POST');
|
|
116
133
|
return await this.fetchAndLog(url, 'sendPhoto', data);
|
|
117
134
|
}
|
|
118
135
|
/**
|
|
@@ -122,7 +139,7 @@ export default class TelegramApi {
|
|
|
122
139
|
* @returns Promise with the API response
|
|
123
140
|
*/
|
|
124
141
|
async sendVoice(botApi, data) {
|
|
125
|
-
const url = this.getApiUrl(botApi, 'sendVoice', data);
|
|
142
|
+
const url = this.getApiUrl(botApi, 'sendVoice', data, 'POST');
|
|
126
143
|
return await this.fetchAndLog(url, 'sendVoice', data);
|
|
127
144
|
}
|
|
128
145
|
/**
|
|
@@ -139,7 +156,7 @@ export default class TelegramApi {
|
|
|
139
156
|
is_personal: data.is_personal,
|
|
140
157
|
next_offset: data.next_offset,
|
|
141
158
|
};
|
|
142
|
-
const url = this.getApiUrl(botApi, 'answerInlineQuery', params);
|
|
159
|
+
const url = this.getApiUrl(botApi, 'answerInlineQuery', params, 'POST');
|
|
143
160
|
return await this.fetchAndLog(url, 'answerInlineQuery', params);
|
|
144
161
|
}
|
|
145
162
|
/**
|
|
@@ -149,7 +166,7 @@ export default class TelegramApi {
|
|
|
149
166
|
* @returns Promise with the API response
|
|
150
167
|
*/
|
|
151
168
|
async answerCallback(botApi, data) {
|
|
152
|
-
const url = this.getApiUrl(botApi, 'answerCallbackQuery', data);
|
|
169
|
+
const url = this.getApiUrl(botApi, 'answerCallbackQuery', data, 'POST');
|
|
153
170
|
return await this.fetchAndLog(url, 'answerCallbackQuery', data);
|
|
154
171
|
}
|
|
155
172
|
/**
|
|
@@ -159,7 +176,7 @@ export default class TelegramApi {
|
|
|
159
176
|
* @returns Promise with the API response
|
|
160
177
|
*/
|
|
161
178
|
async answerGuestQuery(botApi, data) {
|
|
162
|
-
const url = this.getApiUrl(botApi, 'answerGuestQuery', data);
|
|
179
|
+
const url = this.getApiUrl(botApi, 'answerGuestQuery', data, 'POST');
|
|
163
180
|
return await this.fetchAndLog(url, 'answerGuestQuery', data);
|
|
164
181
|
}
|
|
165
182
|
/**
|
|
@@ -169,7 +186,7 @@ export default class TelegramApi {
|
|
|
169
186
|
* @returns Promise with the API response
|
|
170
187
|
*/
|
|
171
188
|
async deleteMessage(botApi, data) {
|
|
172
|
-
const url = this.getApiUrl(botApi, 'deleteMessage', data);
|
|
189
|
+
const url = this.getApiUrl(botApi, 'deleteMessage', data, 'POST');
|
|
173
190
|
return await this.fetchAndLog(url, 'deleteMessage', data);
|
|
174
191
|
}
|
|
175
192
|
/**
|
|
@@ -179,11 +196,11 @@ export default class TelegramApi {
|
|
|
179
196
|
* @returns Promise with the API response
|
|
180
197
|
*/
|
|
181
198
|
async editMessageText(botApi, data) {
|
|
182
|
-
const url = this.getApiUrl(botApi, 'editMessageText', data);
|
|
199
|
+
const url = this.getApiUrl(botApi, 'editMessageText', data, 'POST');
|
|
183
200
|
return await this.fetchAndLog(url, 'editMessageText', data);
|
|
184
201
|
}
|
|
185
202
|
async sendMessageDraft(botApi, data) {
|
|
186
|
-
const url = this.getApiUrl(botApi, 'sendMessageDraft', data);
|
|
203
|
+
const url = this.getApiUrl(botApi, 'sendMessageDraft', data, 'POST');
|
|
187
204
|
return await this.fetchAndLog(url, 'sendMessageDraft', data);
|
|
188
205
|
}
|
|
189
206
|
/**
|
|
@@ -193,7 +210,7 @@ export default class TelegramApi {
|
|
|
193
210
|
* @returns Promise with the API response
|
|
194
211
|
*/
|
|
195
212
|
async sendInvoice(botApi, data) {
|
|
196
|
-
const url = this.getApiUrl(botApi, 'sendInvoice', data);
|
|
213
|
+
const url = this.getApiUrl(botApi, 'sendInvoice', data, 'POST');
|
|
197
214
|
return await this.fetchAndLog(url, 'sendInvoice', data);
|
|
198
215
|
}
|
|
199
216
|
/**
|
|
@@ -203,16 +220,26 @@ export default class TelegramApi {
|
|
|
203
220
|
* @returns Promise with the API response
|
|
204
221
|
*/
|
|
205
222
|
async answerPreCheckoutQuery(botApi, data) {
|
|
206
|
-
const url = this.getApiUrl(botApi, 'answerPreCheckoutQuery', data);
|
|
223
|
+
const url = this.getApiUrl(botApi, 'answerPreCheckoutQuery', data, 'POST');
|
|
207
224
|
return await this.fetchAndLog(url, 'answerPreCheckoutQuery', data);
|
|
208
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Get information about a business connection
|
|
228
|
+
* @param botApi - full URL to the telegram API without slug
|
|
229
|
+
* @param business_connection_id - unique identifier of the business connection
|
|
230
|
+
* @returns Promise with the API response
|
|
231
|
+
*/
|
|
232
|
+
async getBusinessConnection(botApi, business_connection_id) {
|
|
233
|
+
const url = this.getApiUrl(botApi, 'getBusinessConnection', { business_connection_id }, 'POST');
|
|
234
|
+
return await this.fetchAndLog(url, 'getBusinessConnection', { business_connection_id });
|
|
235
|
+
}
|
|
209
236
|
/**
|
|
210
237
|
* Get basic information about the bot
|
|
211
238
|
* @param botApi - full URL to the telegram API without slug
|
|
212
239
|
* @returns Promise with the API response
|
|
213
240
|
*/
|
|
214
241
|
async getMe(botApi) {
|
|
215
|
-
const url = this.getApiUrl(botApi, 'getMe', {});
|
|
242
|
+
const url = this.getApiUrl(botApi, 'getMe', {}, 'GET');
|
|
216
243
|
return await this.fetchAndLog(url, 'getMe', {});
|
|
217
244
|
}
|
|
218
245
|
}
|
package/dist/telegram_bot.js
CHANGED
|
@@ -160,6 +160,10 @@ export default class TelegramBot {
|
|
|
160
160
|
console.log(this.update);
|
|
161
161
|
const ctx = new TelegramExecutionContext(this, this.update);
|
|
162
162
|
this.currentContext = ctx;
|
|
163
|
+
if (!(await ctx.shouldProcess())) {
|
|
164
|
+
console.log('Skipping update processing based on context validation');
|
|
165
|
+
return new Response('ok');
|
|
166
|
+
}
|
|
163
167
|
// Run middleware
|
|
164
168
|
for (const middleware of this.middleware) {
|
|
165
169
|
const result = await middleware(ctx);
|
|
@@ -3,6 +3,10 @@ import TelegramApi from './telegram_api.js';
|
|
|
3
3
|
import TelegramBot from './telegram_bot.js';
|
|
4
4
|
/** Class representing the context of execution */
|
|
5
5
|
export default class TelegramExecutionContext {
|
|
6
|
+
/** Cache for business connection owners */
|
|
7
|
+
private static businessOwners;
|
|
8
|
+
/** Cache for dead business connections */
|
|
9
|
+
private static poisonedConnections;
|
|
6
10
|
/** an instance of the telegram bot */
|
|
7
11
|
bot: TelegramBot;
|
|
8
12
|
/** an instance of the telegram update */
|
|
@@ -43,6 +47,11 @@ export default class TelegramExecutionContext {
|
|
|
43
47
|
* Determine the type of update received
|
|
44
48
|
* @returns The update type as a string
|
|
45
49
|
*/
|
|
50
|
+
/**
|
|
51
|
+
* Determine if the current update should be processed.
|
|
52
|
+
* For business messages, this checks if the connection is valid and has reply permissions.
|
|
53
|
+
*/
|
|
54
|
+
shouldProcess(): Promise<boolean>;
|
|
46
55
|
private determineUpdateType;
|
|
47
56
|
/**
|
|
48
57
|
* Get the chat ID from the current update
|
|
@@ -65,6 +74,10 @@ export default class TelegramExecutionContext {
|
|
|
65
74
|
* @param options - any additional options to pass to sendVideo
|
|
66
75
|
* @returns Promise with the API response
|
|
67
76
|
*/
|
|
77
|
+
/**
|
|
78
|
+
* Helper to handle business connection fallbacks
|
|
79
|
+
*/
|
|
80
|
+
private withBusinessFallback;
|
|
68
81
|
replyVideo(video: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
|
|
69
82
|
/**
|
|
70
83
|
* Get File from telegram file_id
|
|
@@ -148,14 +161,7 @@ export default class TelegramExecutionContext {
|
|
|
148
161
|
* @param options - any additional options to pass to sendMessage/editMessageText
|
|
149
162
|
* @returns Promise with the API response
|
|
150
163
|
*/
|
|
151
|
-
streamReply(message: string, draft_id: number, parse_mode?: string, options?: Record<string, number | string | boolean | object
|
|
152
|
-
/**
|
|
153
|
-
* Reply to the last message with text
|
|
154
|
-
* @param message - text to reply with
|
|
155
|
-
* @param parse_mode - one of HTML, MarkdownV2, Markdown, or an empty string for ascii
|
|
156
|
-
* @param options - any additional options to pass to sendMessage
|
|
157
|
-
* @returns Promise with the API response
|
|
158
|
-
*/
|
|
164
|
+
streamReply(message: string, draft_id: number, parse_mode?: string, options?: Record<string, number | string | boolean | object>, finish?: boolean): Promise<Response | null>;
|
|
159
165
|
reply(message: string, parse_mode?: string, reply?: boolean, options?: Record<string, number | string | boolean>): Promise<Response | null>;
|
|
160
166
|
/**
|
|
161
167
|
* Send an invoice for Telegram Stars
|
|
@@ -165,7 +171,7 @@ export default class TelegramExecutionContext {
|
|
|
165
171
|
* @param amount - amount of stars
|
|
166
172
|
* @returns Promise with the API response
|
|
167
173
|
*/
|
|
168
|
-
sendStarsInvoice(title: string, description: string, payload: string, amount: number): Promise<Response>;
|
|
174
|
+
sendStarsInvoice(title: string, description: string, payload: string, amount: number): Promise<Response | null>;
|
|
169
175
|
/**
|
|
170
176
|
* Answer a pre-checkout query
|
|
171
177
|
* @param ok - whether the payment can proceed
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import TelegramApi from './telegram_api.js';
|
|
2
2
|
/** Class representing the context of execution */
|
|
3
3
|
export default class TelegramExecutionContext {
|
|
4
|
+
/** Cache for business connection owners */
|
|
5
|
+
static businessOwners = new Map();
|
|
6
|
+
/** Cache for dead business connections */
|
|
7
|
+
static poisonedConnections = new Set();
|
|
4
8
|
/** an instance of the telegram bot */
|
|
5
9
|
bot;
|
|
6
10
|
/** an instance of the telegram update */
|
|
@@ -63,6 +67,54 @@ export default class TelegramExecutionContext {
|
|
|
63
67
|
* Determine the type of update received
|
|
64
68
|
* @returns The update type as a string
|
|
65
69
|
*/
|
|
70
|
+
/**
|
|
71
|
+
* Determine if the current update should be processed.
|
|
72
|
+
* For business messages, this checks if the connection is valid and has reply permissions.
|
|
73
|
+
*/
|
|
74
|
+
async shouldProcess() {
|
|
75
|
+
if (this.update_type !== 'business_message') {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const connectionId = this.update.business_message?.business_connection_id?.toString();
|
|
79
|
+
if (!connectionId) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (TelegramExecutionContext.poisonedConnections.has(connectionId)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
let ownerId = TelegramExecutionContext.businessOwners.get(connectionId);
|
|
86
|
+
if (ownerId === undefined) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await this.api.getBusinessConnection(this.bot.api.toString(), connectionId);
|
|
89
|
+
if (response.status === 200) {
|
|
90
|
+
const json = await response.json();
|
|
91
|
+
if (json.ok && json.result) {
|
|
92
|
+
ownerId = json.result.user?.id || json.result.user_chat_id;
|
|
93
|
+
if (ownerId) {
|
|
94
|
+
TelegramExecutionContext.businessOwners.set(connectionId, ownerId);
|
|
95
|
+
}
|
|
96
|
+
if (json.result.can_reply === false) {
|
|
97
|
+
console.warn('Business connection ' + connectionId + ' lacks reply permissions, poisoning connection');
|
|
98
|
+
TelegramExecutionContext.poisonedConnections.add(connectionId);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
if (e instanceof Error && e.message === 'BUSINESS_CONNECTION_INVALID') {
|
|
106
|
+
console.warn('Business connection ' + connectionId + ' is invalid, poisoning connection');
|
|
107
|
+
TelegramExecutionContext.poisonedConnections.add(connectionId);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
console.warn('Failed to fetch business connection info:', e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (ownerId !== undefined && this.getChatId() === ownerId.toString()) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
66
118
|
determineUpdateType() {
|
|
67
119
|
if (this.update.message?.photo) {
|
|
68
120
|
return 'photo';
|
|
@@ -123,6 +175,9 @@ export default class TelegramExecutionContext {
|
|
|
123
175
|
if (this.update.message?.message_id) {
|
|
124
176
|
return this.update.message.message_id.toString();
|
|
125
177
|
}
|
|
178
|
+
else if (this.update.business_message?.message_id) {
|
|
179
|
+
return this.update.business_message.message_id.toString();
|
|
180
|
+
}
|
|
126
181
|
else if (this.update.guest_message?.message_id) {
|
|
127
182
|
return this.update.guest_message.message_id.toString();
|
|
128
183
|
}
|
|
@@ -133,7 +188,7 @@ export default class TelegramExecutionContext {
|
|
|
133
188
|
* @returns The message thread ID as a number or undefined
|
|
134
189
|
*/
|
|
135
190
|
getThreadId() {
|
|
136
|
-
return this.update.message?.message_thread_id;
|
|
191
|
+
return this.update.message?.message_thread_id ?? this.update.business_message?.message_thread_id;
|
|
137
192
|
}
|
|
138
193
|
/**
|
|
139
194
|
* Reply to the last message with a video
|
|
@@ -141,36 +196,85 @@ export default class TelegramExecutionContext {
|
|
|
141
196
|
* @param options - any additional options to pass to sendVideo
|
|
142
197
|
* @returns Promise with the API response
|
|
143
198
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Helper to handle business connection fallbacks
|
|
201
|
+
*/
|
|
202
|
+
async withBusinessFallback(params, apiMethod) {
|
|
203
|
+
const connectionId = params.business_connection_id?.toString();
|
|
204
|
+
if (connectionId) {
|
|
205
|
+
if (TelegramExecutionContext.poisonedConnections.has(connectionId)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
let ownerId = TelegramExecutionContext.businessOwners.get(connectionId);
|
|
209
|
+
if (ownerId === undefined) {
|
|
210
|
+
try {
|
|
211
|
+
const response = await this.api.getBusinessConnection(this.bot.api.toString(), connectionId);
|
|
212
|
+
if (response.status === 200) {
|
|
213
|
+
const json = await response.json();
|
|
214
|
+
if (json.ok && json.result) {
|
|
215
|
+
ownerId = json.result.user?.id || json.result.user_chat_id;
|
|
216
|
+
if (ownerId) {
|
|
217
|
+
TelegramExecutionContext.businessOwners.set(connectionId, ownerId);
|
|
218
|
+
}
|
|
219
|
+
if (json.result.can_reply === false) {
|
|
220
|
+
TelegramExecutionContext.poisonedConnections.add(connectionId);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
if (e instanceof Error && e.message === 'BUSINESS_CONNECTION_INVALID') {
|
|
228
|
+
TelegramExecutionContext.poisonedConnections.add(connectionId);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (ownerId !== undefined && params.chat_id?.toString() === ownerId.toString()) {
|
|
172
234
|
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
return await apiMethod(this.bot.api.toString(), params);
|
|
173
239
|
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
if (e instanceof Error) {
|
|
242
|
+
if (e.message === 'BUSINESS_CONNECTION_INVALID') {
|
|
243
|
+
if (connectionId) {
|
|
244
|
+
TelegramExecutionContext.poisonedConnections.add(connectionId);
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
if (e.message === 'PEER_ID_INVALID') {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
throw e;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async replyVideo(video, options = {}) {
|
|
256
|
+
const params = {
|
|
257
|
+
...options,
|
|
258
|
+
chat_id: this.getChatId(),
|
|
259
|
+
message_thread_id: this.getThreadId(),
|
|
260
|
+
reply_to_message_id: this.getMessageId(),
|
|
261
|
+
video,
|
|
262
|
+
};
|
|
263
|
+
if (this.update_type === 'business_message') {
|
|
264
|
+
params['business_connection_id'] = this.update.business_message?.business_connection_id;
|
|
265
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendVideo(api, data));
|
|
266
|
+
}
|
|
267
|
+
if (this.update_type === 'guest_message') {
|
|
268
|
+
return await this.answerGuestQueryVideo(video);
|
|
269
|
+
}
|
|
270
|
+
if (this.update_type === 'inline') {
|
|
271
|
+
return await this.api.answerInline(this.bot.api.toString(), {
|
|
272
|
+
...options,
|
|
273
|
+
inline_query_id: this.update.inline_query?.id.toString() ?? '',
|
|
274
|
+
results: [{ type: 'video', id: crypto.randomUUID(), video_url: video, mime_type: 'video/mp4', thumbnail_url: video, title: 'Video' }],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return await this.api.sendVideo(this.bot.api.toString(), params);
|
|
174
278
|
}
|
|
175
279
|
/**
|
|
176
280
|
* Get File from telegram file_id
|
|
@@ -188,37 +292,28 @@ export default class TelegramExecutionContext {
|
|
|
188
292
|
* @returns Promise with the API response
|
|
189
293
|
*/
|
|
190
294
|
async replyPhoto(photo, caption = '', options = {}) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
...options,
|
|
206
|
-
chat_id: this.getChatId(),
|
|
207
|
-
message_thread_id: this.getThreadId(),
|
|
208
|
-
business_connection_id: this.update.business_message?.business_connection_id?.toString() ?? '',
|
|
209
|
-
photo,
|
|
210
|
-
caption,
|
|
211
|
-
});
|
|
212
|
-
case 'guest_message':
|
|
213
|
-
return await this.answerGuestQueryPhoto(photo, caption);
|
|
214
|
-
case 'inline':
|
|
215
|
-
return await this.api.answerInline(this.bot.api.toString(), {
|
|
216
|
-
inline_query_id: this.update.inline_query?.id.toString() ?? '',
|
|
217
|
-
results: [{ type: 'photo', id: crypto.randomUUID(), photo_url: photo, thumbnail_url: photo }],
|
|
218
|
-
});
|
|
219
|
-
default:
|
|
220
|
-
return null;
|
|
295
|
+
const params = {
|
|
296
|
+
...options,
|
|
297
|
+
chat_id: this.getChatId(),
|
|
298
|
+
message_thread_id: this.getThreadId(),
|
|
299
|
+
reply_to_message_id: this.getMessageId(),
|
|
300
|
+
photo,
|
|
301
|
+
caption,
|
|
302
|
+
};
|
|
303
|
+
if (this.update_type === 'business_message') {
|
|
304
|
+
params['business_connection_id'] = this.update.business_message?.business_connection_id;
|
|
305
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendPhoto(api, data));
|
|
306
|
+
}
|
|
307
|
+
if (this.update_type === 'guest_message') {
|
|
308
|
+
return await this.answerGuestQueryPhoto(photo, caption);
|
|
221
309
|
}
|
|
310
|
+
if (this.update_type === 'inline') {
|
|
311
|
+
return await this.api.answerInline(this.bot.api.toString(), {
|
|
312
|
+
inline_query_id: this.update.inline_query?.id.toString() ?? '',
|
|
313
|
+
results: [{ type: 'photo', id: crypto.randomUUID(), photo_url: photo, thumbnail_url: photo }],
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return await this.api.sendPhoto(this.bot.api.toString(), params);
|
|
222
317
|
}
|
|
223
318
|
/**
|
|
224
319
|
* Reply to the last message with a voice message
|
|
@@ -228,57 +323,38 @@ export default class TelegramExecutionContext {
|
|
|
228
323
|
* @returns Promise with the API response
|
|
229
324
|
*/
|
|
230
325
|
async replyVoice(voice, caption = '', options = {}) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
chat_id: this.getChatId(),
|
|
246
|
-
message_thread_id: this.getThreadId(),
|
|
247
|
-
business_connection_id: this.update.business_message?.business_connection_id?.toString() ?? '',
|
|
248
|
-
voice,
|
|
249
|
-
caption,
|
|
250
|
-
});
|
|
251
|
-
case 'guest_message':
|
|
252
|
-
return await this.answerGuestQueryVoice(voice, caption);
|
|
253
|
-
default:
|
|
254
|
-
return null;
|
|
326
|
+
const params = {
|
|
327
|
+
...options,
|
|
328
|
+
chat_id: this.getChatId(),
|
|
329
|
+
message_thread_id: this.getThreadId(),
|
|
330
|
+
reply_to_message_id: this.getMessageId(),
|
|
331
|
+
voice,
|
|
332
|
+
caption,
|
|
333
|
+
};
|
|
334
|
+
if (this.update_type === 'business_message') {
|
|
335
|
+
params['business_connection_id'] = this.update.business_message?.business_connection_id;
|
|
336
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendVoice(api, data));
|
|
337
|
+
}
|
|
338
|
+
if (this.update_type === 'guest_message') {
|
|
339
|
+
return await this.answerGuestQueryVoice(voice, caption);
|
|
255
340
|
}
|
|
341
|
+
return await this.api.sendVoice(this.bot.api.toString(), params);
|
|
256
342
|
}
|
|
257
343
|
/**
|
|
258
344
|
* Send typing in a chat
|
|
259
345
|
* @returns Promise with the API response
|
|
260
346
|
*/
|
|
261
347
|
async sendTyping() {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
action: 'typing',
|
|
271
|
-
});
|
|
272
|
-
case 'business_message':
|
|
273
|
-
return await this.api.sendChatAction(this.bot.api.toString(), {
|
|
274
|
-
business_connection_id: this.update.business_message?.business_connection_id?.toString() ?? '',
|
|
275
|
-
chat_id: this.getChatId(),
|
|
276
|
-
message_thread_id: this.getThreadId(),
|
|
277
|
-
action: 'typing',
|
|
278
|
-
});
|
|
279
|
-
default:
|
|
280
|
-
return null;
|
|
348
|
+
const params = {
|
|
349
|
+
chat_id: this.getChatId(),
|
|
350
|
+
message_thread_id: this.getThreadId(),
|
|
351
|
+
action: 'typing',
|
|
352
|
+
};
|
|
353
|
+
if (this.update_type === 'business_message') {
|
|
354
|
+
params['business_connection_id'] = this.update.business_message?.business_connection_id;
|
|
355
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendChatAction(api, data));
|
|
281
356
|
}
|
|
357
|
+
return await this.api.sendChatAction(this.bot.api.toString(), params);
|
|
282
358
|
}
|
|
283
359
|
/**
|
|
284
360
|
* Reply to an inline message with a title and content
|
|
@@ -356,104 +432,56 @@ export default class TelegramExecutionContext {
|
|
|
356
432
|
* @param options - any additional options to pass to sendMessage/editMessageText
|
|
357
433
|
* @returns Promise with the API response
|
|
358
434
|
*/
|
|
359
|
-
async streamReply(message, draft_id, parse_mode = '', options = {}) {
|
|
360
|
-
const message_id = this.drafts.get(draft_id);
|
|
361
|
-
const business_connection_id = this.update.business_message?.business_connection_id?.toString();
|
|
362
|
-
if (message_id) {
|
|
363
|
-
return await this.api.editMessageText(this.bot.api.toString(), {
|
|
364
|
-
chat_id: this.getChatId(),
|
|
365
|
-
message_id,
|
|
366
|
-
text: message,
|
|
367
|
-
parse_mode,
|
|
368
|
-
business_connection_id,
|
|
369
|
-
...options,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
435
|
+
async streamReply(message, draft_id, parse_mode = '', options = {}, finish = false) {
|
|
372
436
|
if (this.update_type === 'guest_message') {
|
|
373
|
-
if (
|
|
374
|
-
return
|
|
437
|
+
if (finish) {
|
|
438
|
+
return await this.answerGuestQueryText(message, parse_mode);
|
|
375
439
|
}
|
|
376
|
-
|
|
377
|
-
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
if (finish) {
|
|
443
|
+
return await this.reply(message, parse_mode, true, options);
|
|
378
444
|
}
|
|
379
|
-
const
|
|
445
|
+
const params = {
|
|
380
446
|
...options,
|
|
381
447
|
chat_id: this.getChatId(),
|
|
382
448
|
message_thread_id: this.getThreadId(),
|
|
383
449
|
text: message,
|
|
384
450
|
parse_mode,
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
if (
|
|
388
|
-
|
|
389
|
-
try {
|
|
390
|
-
const json = (await cloned.json());
|
|
391
|
-
if (json.ok && json.result?.message_id) {
|
|
392
|
-
this.drafts.set(draft_id, json.result.message_id);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
// ignore
|
|
397
|
-
}
|
|
451
|
+
draft_id,
|
|
452
|
+
};
|
|
453
|
+
if (this.update_type === 'business_message') {
|
|
454
|
+
params.business_connection_id = this.update.business_message?.business_connection_id;
|
|
398
455
|
}
|
|
399
|
-
return
|
|
456
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendMessageDraft(api, data));
|
|
400
457
|
}
|
|
401
|
-
/**
|
|
402
|
-
* Reply to the last message with text
|
|
403
|
-
* @param message - text to reply with
|
|
404
|
-
* @param parse_mode - one of HTML, MarkdownV2, Markdown, or an empty string for ascii
|
|
405
|
-
* @param options - any additional options to pass to sendMessage
|
|
406
|
-
* @returns Promise with the API response
|
|
407
|
-
*/
|
|
408
458
|
async reply(message, parse_mode = '', reply = true, options = {}) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (reply) {
|
|
415
|
-
return await this.api.sendMessage(this.bot.api.toString(), {
|
|
416
|
-
...options,
|
|
417
|
-
chat_id: this.getChatId(),
|
|
418
|
-
message_thread_id: this.getThreadId(),
|
|
419
|
-
reply_to_message_id: this.getMessageId(),
|
|
420
|
-
text: message,
|
|
421
|
-
parse_mode,
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
return await this.api.sendMessage(this.bot.api.toString(), {
|
|
425
|
-
...options,
|
|
426
|
-
chat_id: this.getChatId(),
|
|
427
|
-
message_thread_id: this.getThreadId(),
|
|
428
|
-
text: message,
|
|
429
|
-
parse_mode,
|
|
430
|
-
});
|
|
431
|
-
case 'guest_message':
|
|
432
|
-
return await this.answerGuestQueryText(message, parse_mode);
|
|
433
|
-
case 'business_message':
|
|
434
|
-
return await this.api.sendMessage(this.bot.api.toString(), {
|
|
435
|
-
chat_id: this.getChatId(),
|
|
436
|
-
message_thread_id: this.getThreadId(),
|
|
437
|
-
text: message,
|
|
438
|
-
business_connection_id: this.update.business_message?.business_connection_id?.toString() ?? '',
|
|
439
|
-
parse_mode,
|
|
440
|
-
});
|
|
441
|
-
case 'callback':
|
|
442
|
-
if (this.update.callback_query?.message?.chat.id) {
|
|
443
|
-
return await this.api.sendMessage(this.bot.api.toString(), {
|
|
444
|
-
...options,
|
|
445
|
-
chat_id: this.update.callback_query.message.chat.id.toString(),
|
|
446
|
-
message_thread_id: this.getThreadId(),
|
|
447
|
-
text: message,
|
|
448
|
-
parse_mode,
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
return null;
|
|
452
|
-
case 'inline':
|
|
453
|
-
return await this.replyInline('Response', message, parse_mode);
|
|
454
|
-
default:
|
|
455
|
-
return null;
|
|
459
|
+
if (this.update_type === 'guest_message') {
|
|
460
|
+
return await this.answerGuestQueryText(message, parse_mode);
|
|
461
|
+
}
|
|
462
|
+
if (this.update_type === 'inline') {
|
|
463
|
+
return await this.replyInline('Response', message, parse_mode);
|
|
456
464
|
}
|
|
465
|
+
const params = {
|
|
466
|
+
...options,
|
|
467
|
+
chat_id: this.getChatId(),
|
|
468
|
+
message_thread_id: this.getThreadId(),
|
|
469
|
+
text: message,
|
|
470
|
+
parse_mode,
|
|
471
|
+
};
|
|
472
|
+
if (reply) {
|
|
473
|
+
params.reply_to_message_id = this.getMessageId();
|
|
474
|
+
}
|
|
475
|
+
if (this.update_type === 'business_message') {
|
|
476
|
+
params['business_connection_id'] = this.update.business_message?.business_connection_id;
|
|
477
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendMessage(api, data));
|
|
478
|
+
}
|
|
479
|
+
if (this.update_type === 'callback') {
|
|
480
|
+
if (this.update.callback_query?.message?.chat.id) {
|
|
481
|
+
params['chat_id'] = this.update.callback_query.message.chat.id.toString();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return await this.api.sendMessage(this.bot.api.toString(), params);
|
|
457
485
|
}
|
|
458
486
|
/**
|
|
459
487
|
* Send an invoice for Telegram Stars
|
|
@@ -464,17 +492,21 @@ export default class TelegramExecutionContext {
|
|
|
464
492
|
* @returns Promise with the API response
|
|
465
493
|
*/
|
|
466
494
|
async sendStarsInvoice(title, description, payload, amount) {
|
|
467
|
-
|
|
495
|
+
const params = {
|
|
468
496
|
chat_id: this.getChatId(),
|
|
469
497
|
message_thread_id: this.getThreadId(),
|
|
470
|
-
business_connection_id: this.update.business_message?.business_connection_id?.toString(),
|
|
471
498
|
title,
|
|
472
499
|
description,
|
|
473
500
|
payload,
|
|
474
501
|
provider_token: '',
|
|
475
502
|
currency: 'XTR',
|
|
476
503
|
prices: [{ label: title, amount }],
|
|
477
|
-
}
|
|
504
|
+
};
|
|
505
|
+
if (this.update_type === 'business_message') {
|
|
506
|
+
params['business_connection_id'] = this.update.business_message?.business_connection_id;
|
|
507
|
+
return await this.withBusinessFallback(params, (api, data) => this.api.sendInvoice(api, data));
|
|
508
|
+
}
|
|
509
|
+
return await this.api.sendInvoice(this.bot.api.toString(), params);
|
|
478
510
|
}
|
|
479
511
|
/**
|
|
480
512
|
* Answer a pre-checkout query
|
package/dist/utils.js
CHANGED
|
@@ -27,16 +27,17 @@ export async function markdownToHtml(s) {
|
|
|
27
27
|
return result;
|
|
28
28
|
};
|
|
29
29
|
renderer.listitem = (item) => {
|
|
30
|
-
return renderer.parser.
|
|
30
|
+
return renderer.parser.parse(item.tokens).trim();
|
|
31
31
|
};
|
|
32
32
|
renderer.strong = ({ tokens }) => `<b>${renderer.parser.parseInline(tokens)}</b>`;
|
|
33
33
|
renderer.em = ({ tokens }) => `<i>${renderer.parser.parseInline(tokens)}</i>`;
|
|
34
34
|
renderer.codespan = ({ text }) => `<code>${text}</code>`;
|
|
35
35
|
renderer.code = ({ text, lang }) => {
|
|
36
|
+
const escapedText = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
36
37
|
if (lang) {
|
|
37
|
-
return `<pre><code class="language-${lang}">${
|
|
38
|
+
return `<pre><code class="language-${lang}">${escapedText}</code></pre>\n`;
|
|
38
39
|
}
|
|
39
|
-
return `<pre><code>${
|
|
40
|
+
return `<pre><code>${escapedText}</code></pre>\n`;
|
|
40
41
|
};
|
|
41
42
|
renderer.del = ({ tokens }) => `<s>${renderer.parser.parseInline(tokens)}</s>`;
|
|
42
43
|
renderer.link = ({ href, tokens }) => `<a href="${href}">${renderer.parser.parseInline(tokens)}</a>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codebam/cf-workers-telegram-bot",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "serverless telegram bot on cf workers",
|
|
5
5
|
"main": "./dist/main.js",
|
|
6
6
|
"module": "./dist/main.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"license": "Apache-2.0",
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "https://github.com/codebam/cf-workers-telegram-bot.git"
|
|
35
|
+
"url": "git+https://github.com/codebam/cf-workers-telegram-bot.git"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@cloudflare/workers-types": "^4.20260511.1",
|