@codebam/cf-workers-telegram-bot 8.1.1 → 9.1.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.
@@ -1,79 +1,149 @@
1
1
  import TelegramInlineQueryResultArticle from './types/TelegramInlineQueryResultArticle.js';
2
2
  import TelegramInlineQueryResultPhoto from './types/TelegramInlineQueryResultPhoto.js';
3
3
  import TelegramInlineQueryResultVideo from './types/TelegramInlineQueryResultVideo.js';
4
- /** Class representing the Telegram API and all it's methods */
4
+ /** Interface for common Telegram API parameters */
5
+ interface TelegramApiBaseParams {
6
+ chat_id: number | string;
7
+ business_connection_id?: string | number;
8
+ }
9
+ /** Interface for message parameters */
10
+ interface SendMessageParams extends TelegramApiBaseParams {
11
+ text: string;
12
+ parse_mode: string;
13
+ reply_to_message_id?: number | string;
14
+ disable_web_page_preview?: boolean;
15
+ disable_notification?: boolean;
16
+ protect_content?: boolean;
17
+ reply_markup?: object;
18
+ }
19
+ /** Interface for photo parameters */
20
+ interface SendPhotoParams extends TelegramApiBaseParams {
21
+ photo: string;
22
+ caption?: string;
23
+ parse_mode?: string;
24
+ reply_to_message_id?: number | string;
25
+ disable_notification?: boolean;
26
+ protect_content?: boolean;
27
+ reply_markup?: object;
28
+ }
29
+ /** Interface for video parameters */
30
+ interface SendVideoParams extends TelegramApiBaseParams {
31
+ video: string;
32
+ caption?: string;
33
+ parse_mode?: string;
34
+ reply_to_message_id?: number | string;
35
+ disable_notification?: boolean;
36
+ protect_content?: boolean;
37
+ reply_markup?: object;
38
+ }
39
+ /** Interface for chat action parameters */
40
+ interface SendChatActionParams extends TelegramApiBaseParams {
41
+ action: string;
42
+ }
43
+ /** Interface for callback query parameters */
44
+ interface AnswerCallbackParams {
45
+ callback_query_id: number | string;
46
+ text?: string;
47
+ show_alert?: boolean;
48
+ url?: string;
49
+ cache_time?: number;
50
+ }
51
+ /** Interface for inline query parameters */
52
+ interface AnswerInlineParams {
53
+ inline_query_id: number | string;
54
+ results: TelegramInlineQueryResultArticle[] | TelegramInlineQueryResultPhoto[] | TelegramInlineQueryResultVideo[];
55
+ cache_time?: number;
56
+ is_personal?: boolean;
57
+ next_offset?: string;
58
+ }
59
+ /** Type for all possible API parameters */
60
+ type TelegramApiParams = SendMessageParams | SendPhotoParams | SendVideoParams | SendChatActionParams | AnswerCallbackParams | AnswerInlineParams | Record<string, unknown>;
61
+ /** Class representing the Telegram API and all its methods */
5
62
  export default class TelegramApi {
6
63
  /**
7
64
  * Get the API URL for a given bot API and slug
8
65
  * @param botApi - full URL to the telegram API without slug
9
66
  * @param slug - slug to append to the API URL
10
67
  * @param data - data to append to the request
68
+ * @returns Request object with the full URL and parameters
11
69
  */
12
- getApiUrl(botApi: string, slug: string, data: Record<string, number | string | boolean>): Request<unknown, CfProperties<unknown>>;
13
- sendChatAction(botApi: string, data: {
14
- business_connection_id?: string;
15
- chat_id: number | string;
16
- action: string;
17
- }): Promise<Response>;
70
+ getApiUrl(botApi: string, slug: string, data: TelegramApiParams): Request;
71
+ /**
72
+ * Send a chat action to indicate the bot is doing something
73
+ * @param botApi - full URL to the telegram API without slug
74
+ * @param data - data to append to the request
75
+ * @returns Promise with the API response
76
+ */
77
+ sendChatAction(botApi: string, data: SendChatActionParams): Promise<Response>;
18
78
  /**
19
79
  * Get a file with a given file_id
20
80
  * @param botApi - full URL to the telegram API without slug
21
81
  * @param data - data to append to the request
22
82
  * @param token - bot token
83
+ * @returns Promise with the file response
23
84
  */
24
- getFile(botApi: string, data: Record<string, number | string | boolean>, token: string): Promise<Response>;
85
+ getFile(botApi: string, data: {
86
+ file_id: string;
87
+ } & Record<string, number | string | boolean>, token: string): Promise<Response>;
25
88
  /**
26
89
  * Send a message to a given botApi
27
90
  * @param botApi - full URL to the telegram API without slug
28
91
  * @param data - data to append to the request
92
+ * @returns Promise with the API response
29
93
  */
30
- sendMessage(botApi: string, data: {
31
- reply_to_message_id?: number | string;
32
- chat_id: number | string;
33
- text: string;
34
- parse_mode: string;
35
- business_connection_id?: number | string;
36
- }): Promise<Response>;
94
+ sendMessage(botApi: string, data: SendMessageParams): Promise<Response>;
37
95
  /**
38
96
  * Send a video message to a given botApi
39
97
  * @param botApi - full URL to the telegram API without slug
40
98
  * @param data - data to append to the request
99
+ * @returns Promise with the API response
41
100
  */
42
- sendVideo(botApi: string, data: {
43
- reply_to_message_id: number | string;
44
- chat_id: number | string;
45
- video: string;
46
- }): Promise<Response>;
101
+ sendVideo(botApi: string, data: SendVideoParams): Promise<Response>;
47
102
  /**
48
103
  * Send a photo message to a given botApi
49
104
  * @param botApi - full URL to the telegram API without slug
50
105
  * @param data - data to append to the request
106
+ * @returns Promise with the API response
51
107
  */
52
- sendPhoto(botApi: string, data: {
53
- reply_to_message_id: number | string;
54
- chat_id: number | string;
55
- photo: string;
56
- caption: string;
57
- }): Promise<Response>;
108
+ sendPhoto(botApi: string, data: SendPhotoParams): Promise<Response>;
58
109
  /**
59
110
  * Send an inline response to a given botApi
60
111
  * @param botApi - full URL to the telegram API without slug
61
112
  * @param data - data to append to the request
113
+ * @returns Promise with the API response
62
114
  */
63
- answerInline(botApi: string, data: {
64
- inline_query_id: number | string;
65
- results: TelegramInlineQueryResultArticle[] | TelegramInlineQueryResultPhoto[] | TelegramInlineQueryResultVideo[];
115
+ answerInline(botApi: string, data: AnswerInlineParams): Promise<Response>;
116
+ /**
117
+ * Send a callback response to a given botApi
118
+ * @param botApi - full URL to the telegram API without slug
119
+ * @param data - data to append to the request
120
+ * @returns Promise with the API response
121
+ */
122
+ answerCallback(botApi: string, data: AnswerCallbackParams): Promise<Response>;
123
+ /**
124
+ * Delete a message
125
+ * @param botApi - full URL to the telegram API without slug
126
+ * @param data - data to append to the request
127
+ * @returns Promise with the API response
128
+ */
129
+ deleteMessage(botApi: string, data: {
130
+ chat_id: number | string;
131
+ message_id: number;
66
132
  }): Promise<Response>;
67
133
  /**
68
- * Send an callback response to a given botApi
134
+ * Edit a message text
69
135
  * @param botApi - full URL to the telegram API without slug
70
136
  * @param data - data to append to the request
137
+ * @returns Promise with the API response
71
138
  */
72
- answerCallback(botApi: string, data: {
73
- callback_query_id: number | string;
74
- text?: string;
75
- show_alert?: boolean;
76
- url?: string;
77
- cache_time?: number;
139
+ editMessageText(botApi: string, data: {
140
+ chat_id?: number | string;
141
+ message_id?: number;
142
+ inline_message_id?: string;
143
+ text: string;
144
+ parse_mode?: string;
145
+ disable_web_page_preview?: boolean;
146
+ reply_markup?: object;
78
147
  }): Promise<Response>;
79
148
  }
149
+ export {};
@@ -1,61 +1,75 @@
1
- /** Class representing the Telegram API and all it's methods */
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
5
5
  * @param botApi - full URL to the telegram API without slug
6
6
  * @param slug - slug to append to the API URL
7
7
  * @param data - data to append to the request
8
+ * @returns Request object with the full URL and parameters
8
9
  */
9
10
  getApiUrl(botApi, slug, data) {
10
11
  const request = new URL(botApi + (slug.startsWith('/') || botApi.endsWith('/') ? '' : '/') + slug);
11
12
  const params = new URLSearchParams();
12
- for (const i in data) {
13
- params.append(i, data[i].toString());
13
+ for (const [key, value] of Object.entries(data)) {
14
+ if (value !== undefined) {
15
+ params.append(key, typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value));
16
+ }
14
17
  }
15
18
  return new Request(`${request.toString()}?${params.toString()}`);
16
19
  }
20
+ /**
21
+ * Send a chat action to indicate the bot is doing something
22
+ * @param botApi - full URL to the telegram API without slug
23
+ * @param data - data to append to the request
24
+ * @returns Promise with the API response
25
+ */
17
26
  async sendChatAction(botApi, data) {
18
27
  const url = this.getApiUrl(botApi, 'sendChatAction', data);
19
- const response = await fetch(url);
20
- return response;
28
+ return await fetch(url);
21
29
  }
22
30
  /**
23
31
  * Get a file with a given file_id
24
32
  * @param botApi - full URL to the telegram API without slug
25
33
  * @param data - data to append to the request
26
34
  * @param token - bot token
35
+ * @returns Promise with the file response
27
36
  */
28
37
  async getFile(botApi, data, token) {
29
- if (data.file_id === '') {
30
- return new Response();
38
+ if (!data.file_id || data.file_id === '') {
39
+ return new Response('No file_id provided', { status: 400 });
31
40
  }
32
- const url = this.getApiUrl(botApi, 'getFile', data);
33
- const response = await fetch(url);
34
- const json = await response.json();
35
- let file_path;
36
41
  try {
37
- file_path = json.result.file_path;
42
+ const url = this.getApiUrl(botApi, 'getFile', data);
43
+ const response = await fetch(url);
44
+ if (!response.ok) {
45
+ return new Response(`API error: ${String(response.status)} ${response.statusText}`, { status: response.status });
46
+ }
47
+ const json = await response.json();
48
+ if (!json.ok || !json.result?.file_path) {
49
+ return new Response(json.description ?? 'Failed to get file path', { status: 400 });
50
+ }
51
+ return await fetch(`https://api.telegram.org/file/bot${token}/${json.result.file_path}`);
38
52
  }
39
53
  catch (e) {
40
- console.log(`Error: ${e}`);
41
- return new Response('cant read file_path. is the file too large?');
54
+ console.error(`Error in getFile: ${e instanceof Error ? e.message : String(e)}`);
55
+ return new Response(`Error retrieving file: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
42
56
  }
43
- return await fetch(`https://api.telegram.org/file/bot${token}/${file_path}`);
44
57
  }
45
58
  /**
46
59
  * Send a message to a given botApi
47
60
  * @param botApi - full URL to the telegram API without slug
48
61
  * @param data - data to append to the request
62
+ * @returns Promise with the API response
49
63
  */
50
64
  async sendMessage(botApi, data) {
51
65
  const url = this.getApiUrl(botApi, 'sendMessage', data);
52
- console.log(url.url);
53
66
  return await fetch(url);
54
67
  }
55
68
  /**
56
69
  * Send a video message to a given botApi
57
70
  * @param botApi - full URL to the telegram API without slug
58
71
  * @param data - data to append to the request
72
+ * @returns Promise with the API response
59
73
  */
60
74
  async sendVideo(botApi, data) {
61
75
  const url = this.getApiUrl(botApi, 'sendVideo', data);
@@ -65,6 +79,7 @@ export default class TelegramApi {
65
79
  * Send a photo message to a given botApi
66
80
  * @param botApi - full URL to the telegram API without slug
67
81
  * @param data - data to append to the request
82
+ * @returns Promise with the API response
68
83
  */
69
84
  async sendPhoto(botApi, data) {
70
85
  const url = this.getApiUrl(botApi, 'sendPhoto', data);
@@ -74,21 +89,46 @@ export default class TelegramApi {
74
89
  * Send an inline response to a given botApi
75
90
  * @param botApi - full URL to the telegram API without slug
76
91
  * @param data - data to append to the request
92
+ * @returns Promise with the API response
77
93
  */
78
94
  async answerInline(botApi, data) {
79
95
  const url = this.getApiUrl(botApi, 'answerInlineQuery', {
80
96
  inline_query_id: data.inline_query_id,
81
- results: JSON.stringify(data.results),
97
+ results: data.results,
98
+ cache_time: data.cache_time,
99
+ is_personal: data.is_personal,
100
+ next_offset: data.next_offset,
82
101
  });
83
102
  return await fetch(url);
84
103
  }
85
104
  /**
86
- * Send an callback response to a given botApi
105
+ * Send a callback response to a given botApi
87
106
  * @param botApi - full URL to the telegram API without slug
88
107
  * @param data - data to append to the request
108
+ * @returns Promise with the API response
89
109
  */
90
110
  async answerCallback(botApi, data) {
91
111
  const url = this.getApiUrl(botApi, 'answerCallbackQuery', data);
92
112
  return await fetch(url);
93
113
  }
114
+ /**
115
+ * Delete a message
116
+ * @param botApi - full URL to the telegram API without slug
117
+ * @param data - data to append to the request
118
+ * @returns Promise with the API response
119
+ */
120
+ async deleteMessage(botApi, data) {
121
+ const url = this.getApiUrl(botApi, 'deleteMessage', data);
122
+ return await fetch(url);
123
+ }
124
+ /**
125
+ * Edit a message text
126
+ * @param botApi - full URL to the telegram API without slug
127
+ * @param data - data to append to the request
128
+ * @returns Promise with the API response
129
+ */
130
+ async editMessageText(botApi, data) {
131
+ const url = this.getApiUrl(botApi, 'editMessageText', data);
132
+ return await fetch(url);
133
+ }
94
134
  }
@@ -15,17 +15,40 @@ export default class TelegramBot {
15
15
  commands: Record<string, (ctx: TelegramExecutionContext) => Promise<Response>>;
16
16
  /** The current bot context */
17
17
  currentContext: TelegramExecutionContext;
18
+ /** Default command to use when no matching command is found */
19
+ defaultCommand: string;
18
20
  /**
19
21
  * Create a bot
20
22
  * @param token - the telegram secret token
23
+ * @param options - optional configuration for the bot
21
24
  */
22
- constructor(token: string);
25
+ constructor(token: string, options?: {
26
+ defaultCommand?: string;
27
+ });
23
28
  /**
24
29
  * Register a function on the bot
25
30
  * @param event - the event or command name
26
31
  * @param callback - the bot context
27
32
  */
28
33
  on(event: string, callback: (ctx: TelegramExecutionContext) => Promise<Response>): this;
34
+ /**
35
+ * Register multiple command handlers at once
36
+ * @param handlers - object mapping command names to handler functions
37
+ */
38
+ registerHandlers(handlers: Record<string, (ctx: TelegramExecutionContext) => Promise<Response>>): this;
39
+ /**
40
+ * Determine the command from the update
41
+ * @param ctx - the execution context
42
+ * @param args - command arguments
43
+ * @returns the command string
44
+ */
45
+ private determineCommand;
46
+ /**
47
+ * Parse arguments from the update
48
+ * @param ctx - the execution context
49
+ * @returns array of argument strings
50
+ */
51
+ private parseArguments;
29
52
  /**
30
53
  * Handle a request on a given bot
31
54
  * @param request - the request to handle
@@ -15,13 +15,21 @@ export default class TelegramBot {
15
15
  commands = {};
16
16
  /** The current bot context */
17
17
  currentContext;
18
+ /** Default command to use when no matching command is found */
19
+ defaultCommand = ':message';
18
20
  /**
19
21
  * Create a bot
20
22
  * @param token - the telegram secret token
23
+ * @param options - optional configuration for the bot
21
24
  */
22
- constructor(token) {
25
+ constructor(token, options) {
23
26
  this.token = token;
24
27
  this.api = new URL('https://api.telegram.org/bot' + token);
28
+ if (options?.defaultCommand) {
29
+ this.defaultCommand = options.defaultCommand;
30
+ }
31
+ // Register default handler for the default command to avoid errors
32
+ this.commands[this.defaultCommand] = () => Promise.resolve(new Response('Command not implemented'));
25
33
  }
26
34
  /**
27
35
  * Register a function on the bot
@@ -29,11 +37,60 @@ export default class TelegramBot {
29
37
  * @param callback - the bot context
30
38
  */
31
39
  on(event, callback) {
32
- if (!['on', 'handle'].includes(event)) {
33
- this.commands[event] = callback;
40
+ this.commands[event] = callback;
41
+ return this;
42
+ }
43
+ /**
44
+ * Register multiple command handlers at once
45
+ * @param handlers - object mapping command names to handler functions
46
+ */
47
+ registerHandlers(handlers) {
48
+ for (const [event, callback] of Object.entries(handlers)) {
49
+ this.on(event, callback);
34
50
  }
35
51
  return this;
36
52
  }
53
+ /**
54
+ * Determine the command from the update
55
+ * @param ctx - the execution context
56
+ * @param args - command arguments
57
+ * @returns the command string
58
+ */
59
+ determineCommand(ctx, args) {
60
+ // First check if it's a special update type
61
+ switch (ctx.update_type) {
62
+ case 'photo':
63
+ return ':photo' in this.commands ? ':photo' : this.defaultCommand;
64
+ case 'document':
65
+ return ':document' in this.commands ? ':document' : this.defaultCommand;
66
+ case 'callback':
67
+ return ':callback' in this.commands ? ':callback' : this.defaultCommand;
68
+ case 'inline':
69
+ return ':inline' in this.commands ? ':inline' : this.defaultCommand;
70
+ }
71
+ // Then check if it's a command starting with /
72
+ if (args.at(0)?.startsWith('/')) {
73
+ const command = args.at(0)?.slice(1) ?? '';
74
+ return command in this.commands ? command : this.defaultCommand;
75
+ }
76
+ return this.defaultCommand;
77
+ }
78
+ /**
79
+ * Parse arguments from the update
80
+ * @param ctx - the execution context
81
+ * @returns array of argument strings
82
+ */
83
+ parseArguments(ctx) {
84
+ switch (ctx.update_type) {
85
+ case 'message':
86
+ case 'business_message':
87
+ return this.update.message?.text?.split(' ') ?? [];
88
+ case 'inline':
89
+ return this.update.inline_query?.query.split(' ') ?? [];
90
+ default:
91
+ return [];
92
+ }
93
+ }
37
94
  /**
38
95
  * Handle a request on a given bot
39
96
  * @param request - the request to handle
@@ -41,64 +98,36 @@ export default class TelegramBot {
41
98
  async handle(request) {
42
99
  this.webhook = new Webhook(this.token, request);
43
100
  const url = new URL(request.url);
44
- if (`/${this.token}` === url.pathname) {
45
- switch (request.method) {
46
- case 'POST': {
101
+ // Check if the request is for this bot
102
+ if (`/${this.token}` !== url.pathname) {
103
+ return new Response('Invalid token', { status: 404 });
104
+ }
105
+ // Handle different HTTP methods
106
+ switch (request.method) {
107
+ case 'POST': {
108
+ try {
47
109
  this.update = await request.json();
48
110
  console.log(this.update);
49
- let command = ':message';
50
- let args = [];
51
111
  const ctx = new TelegramExecutionContext(this, this.update);
52
112
  this.currentContext = ctx;
53
- switch (ctx.update_type) {
54
- case 'message': {
55
- args = this.update.message?.text?.split(' ') ?? [];
56
- break;
57
- }
58
- case 'business_message': {
59
- args = this.update.message?.text?.split(' ') ?? [];
60
- break;
61
- }
62
- case 'inline': {
63
- args = this.update.inline_query?.query.split(' ') ?? [];
64
- break;
65
- }
66
- case 'photo': {
67
- command = ':photo';
68
- break;
69
- }
70
- case 'document': {
71
- command = ':document';
72
- break;
73
- }
74
- case 'callback': {
75
- command = ':callback';
76
- break;
77
- }
78
- default:
79
- break;
80
- }
81
- if (args.at(0)?.startsWith('/')) {
82
- command = args.at(0)?.slice(1) ?? ':message';
83
- }
84
- if (!(command in this.commands)) {
85
- command = ':message';
86
- }
113
+ const args = this.parseArguments(ctx);
114
+ const command = this.determineCommand(ctx, args);
87
115
  return await this.commands[command](ctx);
88
116
  }
89
- case 'GET': {
90
- switch (url.searchParams.get('command')) {
91
- case 'set':
92
- return this.webhook.set();
93
- default:
94
- break;
95
- }
96
- break;
117
+ catch (error) {
118
+ console.error('Error handling Telegram update:', error);
119
+ return new Response('Error processing request', { status: 500 });
120
+ }
121
+ }
122
+ case 'GET': {
123
+ const command = url.searchParams.get('command');
124
+ if (command === 'set') {
125
+ return this.webhook.set();
97
126
  }
98
- default:
99
- break;
127
+ return new Response('Invalid command', { status: 400 });
100
128
  }
129
+ default:
130
+ return new Response('Method not allowed', { status: 405 });
101
131
  }
102
- return new Response('ok');
103
132
  }
104
133
  }
@@ -17,15 +17,32 @@ export default class TelegramExecutionContext {
17
17
  * @param update - the telegram update
18
18
  */
19
19
  constructor(bot: TelegramBot, update: TelegramUpdate);
20
+ /**
21
+ * Determine the type of update received
22
+ * @returns The update type as a string
23
+ */
24
+ private determineUpdateType;
25
+ /**
26
+ * Get the chat ID from the current update
27
+ * @returns The chat ID as a string or empty string if not available
28
+ */
29
+ private getChatId;
30
+ /**
31
+ * Get the message ID from the current update
32
+ * @returns The message ID as a string or empty string if not available
33
+ */
34
+ private getMessageId;
20
35
  /**
21
36
  * Reply to the last message with a video
22
37
  * @param video - string to a video on the internet or a file_id on telegram
23
38
  * @param options - any additional options to pass to sendVideo
39
+ * @returns Promise with the API response
24
40
  */
25
- replyVideo(video: string, options?: Record<string, number | string | boolean>): Promise<Response | undefined>;
41
+ replyVideo(video: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
26
42
  /**
27
43
  * Get File from telegram file_id
28
44
  * @param file_id - telegram file_id
45
+ * @returns Promise with the file response
29
46
  */
30
47
  getFile(file_id: string): Promise<Response>;
31
48
  /**
@@ -33,24 +50,28 @@ export default class TelegramExecutionContext {
33
50
  * @param photo - url or file_id to photo
34
51
  * @param caption - photo caption
35
52
  * @param options - any additional options to pass to sendPhoto
53
+ * @returns Promise with the API response
36
54
  */
37
- replyPhoto(photo: string, caption?: string, options?: Record<string, number | string | boolean>): Promise<Response | undefined>;
55
+ replyPhoto(photo: string, caption?: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
38
56
  /**
39
57
  * Send typing in a chat
58
+ * @returns Promise with the API response
40
59
  */
41
- sendTyping(): Promise<Response | undefined>;
60
+ sendTyping(): Promise<Response | null>;
42
61
  /**
43
62
  * Reply to an inline message with a title and content
44
63
  * @param title - title to reply with
45
64
  * @param message - message contents to reply with
46
65
  * @param parse_mode - parse mode to use
66
+ * @returns Promise with the API response
47
67
  */
48
- replyInline(title: string, message: string, parse_mode?: string): Promise<Response | undefined>;
68
+ replyInline(title: string, message: string, parse_mode?: string): Promise<Response | null>;
49
69
  /**
50
70
  * Reply to the last message with text
51
71
  * @param message - text to reply with
52
72
  * @param parse_mode - one of HTML, MarkdownV2, Markdown, or an empty string for ascii
53
73
  * @param options - any additional options to pass to sendMessage
74
+ * @returns Promise with the API response
54
75
  */
55
- reply(message: string, parse_mode?: string, options?: Record<string, number | string | boolean>): Promise<Response | undefined>;
76
+ reply(message: string, parse_mode?: string, options?: Record<string, number | string | boolean>): Promise<Response | null>;
56
77
  }
@@ -20,37 +20,66 @@ export default class TelegramExecutionContext {
20
20
  constructor(bot, update) {
21
21
  this.bot = bot;
22
22
  this.update = update;
23
+ this.update_type = this.determineUpdateType();
24
+ }
25
+ /**
26
+ * Determine the type of update received
27
+ * @returns The update type as a string
28
+ */
29
+ determineUpdateType() {
23
30
  if (this.update.message?.photo) {
24
- this.update_type = 'photo';
31
+ return 'photo';
25
32
  }
26
33
  else if (this.update.message?.text) {
27
- this.update_type = 'message';
34
+ return 'message';
28
35
  }
29
36
  else if (this.update.inline_query?.query) {
30
- this.update_type = 'inline';
37
+ return 'inline';
31
38
  }
32
39
  else if (this.update.message?.document) {
33
- this.update_type = 'document';
40
+ return 'document';
34
41
  }
35
42
  else if (this.update.callback_query?.id) {
36
- this.update_type = 'callback';
43
+ return 'callback';
37
44
  }
38
45
  else if (this.update.business_message) {
39
- this.update_type = 'business_message';
46
+ return 'business_message';
40
47
  }
48
+ return '';
49
+ }
50
+ /**
51
+ * Get the chat ID from the current update
52
+ * @returns The chat ID as a string or empty string if not available
53
+ */
54
+ getChatId() {
55
+ if (this.update.message?.chat.id) {
56
+ return this.update.message.chat.id.toString();
57
+ }
58
+ else if (this.update.business_message?.chat.id) {
59
+ return this.update.business_message.chat.id.toString();
60
+ }
61
+ return '';
62
+ }
63
+ /**
64
+ * Get the message ID from the current update
65
+ * @returns The message ID as a string or empty string if not available
66
+ */
67
+ getMessageId() {
68
+ return this.update.message?.message_id.toString() ?? '';
41
69
  }
42
70
  /**
43
71
  * Reply to the last message with a video
44
72
  * @param video - string to a video on the internet or a file_id on telegram
45
73
  * @param options - any additional options to pass to sendVideo
74
+ * @returns Promise with the API response
46
75
  */
47
76
  async replyVideo(video, options = {}) {
48
77
  switch (this.update_type) {
49
78
  case 'message':
50
79
  return await this.api.sendVideo(this.bot.api.toString(), {
51
80
  ...options,
52
- chat_id: this.update.message?.chat.id.toString() ?? '',
53
- reply_to_message_id: this.update.message?.message_id.toString() ?? '',
81
+ chat_id: this.getChatId(),
82
+ reply_to_message_id: this.getMessageId(),
54
83
  video,
55
84
  });
56
85
  case 'inline':
@@ -60,12 +89,13 @@ export default class TelegramExecutionContext {
60
89
  results: [new TelegramInlineQueryResultVideo(video)],
61
90
  });
62
91
  default:
63
- break;
92
+ return null;
64
93
  }
65
94
  }
66
95
  /**
67
96
  * Get File from telegram file_id
68
97
  * @param file_id - telegram file_id
98
+ * @returns Promise with the file response
69
99
  */
70
100
  async getFile(file_id) {
71
101
  return await this.api.getFile(this.bot.api.toString(), { file_id }, this.bot.token);
@@ -75,22 +105,16 @@ export default class TelegramExecutionContext {
75
105
  * @param photo - url or file_id to photo
76
106
  * @param caption - photo caption
77
107
  * @param options - any additional options to pass to sendPhoto
108
+ * @returns Promise with the API response
78
109
  */
79
110
  async replyPhoto(photo, caption = '', options = {}) {
80
111
  switch (this.update_type) {
81
112
  case 'photo':
82
- return await this.api.sendPhoto(this.bot.api.toString(), {
83
- ...options,
84
- chat_id: this.update.message?.chat.id.toString() ?? '',
85
- reply_to_message_id: this.update.message?.message_id.toString() ?? '',
86
- photo,
87
- caption,
88
- });
89
113
  case 'message':
90
114
  return await this.api.sendPhoto(this.bot.api.toString(), {
91
115
  ...options,
92
- chat_id: this.update.message?.chat.id.toString() ?? '',
93
- reply_to_message_id: this.update.message?.message_id.toString() ?? '',
116
+ chat_id: this.getChatId(),
117
+ reply_to_message_id: this.getMessageId(),
94
118
  photo,
95
119
  caption,
96
120
  });
@@ -100,27 +124,30 @@ export default class TelegramExecutionContext {
100
124
  results: [new TelegramInlineQueryResultPhoto(photo)],
101
125
  });
102
126
  default:
103
- break;
127
+ return null;
104
128
  }
105
129
  }
106
130
  /**
107
131
  * Send typing in a chat
132
+ * @returns Promise with the API response
108
133
  */
109
134
  async sendTyping() {
110
135
  switch (this.update_type) {
111
136
  case 'message':
137
+ case 'photo':
138
+ case 'document':
112
139
  return await this.api.sendChatAction(this.bot.api.toString(), {
113
- chat_id: this.update.message?.chat.id.toString() ?? '',
140
+ chat_id: this.getChatId(),
114
141
  action: 'typing',
115
142
  });
116
143
  case 'business_message':
117
144
  return await this.api.sendChatAction(this.bot.api.toString(), {
118
- business_connection_id: this.update.business_message?.business_connection_id.toString(),
119
- chat_id: this.update.business_message?.chat.id.toString() ?? '',
145
+ business_connection_id: this.update.business_message?.business_connection_id.toString() ?? '',
146
+ chat_id: this.getChatId(),
120
147
  action: 'typing',
121
148
  });
122
149
  default:
123
- break;
150
+ return null;
124
151
  }
125
152
  }
126
153
  /**
@@ -128,64 +155,57 @@ export default class TelegramExecutionContext {
128
155
  * @param title - title to reply with
129
156
  * @param message - message contents to reply with
130
157
  * @param parse_mode - parse mode to use
158
+ * @returns Promise with the API response
131
159
  */
132
160
  async replyInline(title, message, parse_mode = '') {
133
- switch (this.update_type) {
134
- case 'inline':
135
- return await this.api.answerInline(this.bot.api.toString(), {
136
- inline_query_id: this.update.inline_query?.id.toString() ?? '',
137
- results: [new TelegramInlineQueryResultArticle({ content: message, title, parse_mode })],
138
- });
139
- default:
140
- break;
161
+ if (this.update_type === 'inline') {
162
+ return await this.api.answerInline(this.bot.api.toString(), {
163
+ inline_query_id: this.update.inline_query?.id.toString() ?? '',
164
+ results: [new TelegramInlineQueryResultArticle({ content: message, title, parse_mode })],
165
+ });
141
166
  }
167
+ return null;
142
168
  }
143
169
  /**
144
170
  * Reply to the last message with text
145
171
  * @param message - text to reply with
146
172
  * @param parse_mode - one of HTML, MarkdownV2, Markdown, or an empty string for ascii
147
173
  * @param options - any additional options to pass to sendMessage
174
+ * @returns Promise with the API response
148
175
  */
149
176
  async reply(message, parse_mode = '', options = {}) {
150
177
  switch (this.update_type) {
151
178
  case 'message':
179
+ case 'photo':
180
+ case 'document':
152
181
  return await this.api.sendMessage(this.bot.api.toString(), {
153
182
  ...options,
154
- chat_id: this.update.message?.chat.id.toString() ?? '',
155
- reply_to_message_id: this.update.message?.message_id.toString() ?? '',
183
+ chat_id: this.getChatId(),
184
+ reply_to_message_id: this.getMessageId(),
156
185
  text: message,
157
186
  parse_mode,
158
187
  });
159
188
  case 'business_message':
160
189
  return await this.api.sendMessage(this.bot.api.toString(), {
161
- chat_id: this.update.business_message?.chat.id.toString() ?? '',
162
- text: message,
163
- business_connection_id: this.update.business_message?.business_connection_id.toString(),
164
- parse_mode,
165
- });
166
- case 'photo':
167
- return await this.api.sendMessage(this.bot.api.toString(), {
168
- ...options,
169
- chat_id: this.update.message?.chat.id.toString() ?? '',
170
- reply_to_message_id: this.update.message?.message_id.toString() ?? '',
190
+ chat_id: this.getChatId(),
171
191
  text: message,
192
+ business_connection_id: this.update.business_message?.business_connection_id.toString() ?? '',
172
193
  parse_mode,
173
194
  });
195
+ case 'callback':
196
+ if (this.update.callback_query?.message.chat.id) {
197
+ return await this.api.sendMessage(this.bot.api.toString(), {
198
+ ...options,
199
+ chat_id: this.update.callback_query.message.chat.id.toString(),
200
+ text: message,
201
+ parse_mode,
202
+ });
203
+ }
204
+ return null;
174
205
  case 'inline':
175
- return await this.api.answerInline(this.bot.api.toString(), {
176
- inline_query_id: this.update.inline_query?.id.toString() ?? '',
177
- results: [new TelegramInlineQueryResultArticle({ title: message, content: message, parse_mode })],
178
- });
179
- case 'document':
180
- return await this.api.sendMessage(this.bot.api.toString(), {
181
- ...options,
182
- chat_id: this.update.message?.chat.id.toString() ?? '',
183
- reply_to_message_id: this.update.message?.message_id.toString() ?? '',
184
- text: message,
185
- parse_mode,
186
- });
206
+ return await this.replyInline('Response', message, parse_mode);
187
207
  default:
188
- break;
208
+ return null;
189
209
  }
190
210
  }
191
211
  }
package/dist/webhook.d.ts CHANGED
@@ -1,6 +1,31 @@
1
+ /**
2
+ * Webhook class for managing Telegram bot webhook configuration.
3
+ * Handles setting up and configuring webhooks for Telegram bots.
4
+ */
1
5
  export default class Webhook {
2
- api: URL;
3
- webhook: URL;
6
+ /** Base URL for the Telegram Bot API */
7
+ private readonly api;
8
+ /** Webhook URL that Telegram will send updates to */
9
+ private readonly webhook;
10
+ /**
11
+ * Creates a new Webhook instance.
12
+ *
13
+ * @param token - The Telegram bot token
14
+ * @param request - The incoming request object used to determine the webhook URL
15
+ */
4
16
  constructor(token: string, request: Request);
17
+ /**
18
+ * Sets the webhook URL for the Telegram bot.
19
+ *
20
+ * @returns Promise that resolves to the fetch response from Telegram
21
+ * @throws Will throw an error if the fetch request fails
22
+ */
5
23
  set(): Promise<Response>;
24
+ /**
25
+ * Removes the webhook configuration from Telegram.
26
+ *
27
+ * @returns Promise that resolves to the fetch response from Telegram
28
+ * @throws Will throw an error if the fetch request fails
29
+ */
30
+ delete(): Promise<Response>;
6
31
  }
package/dist/webhook.js CHANGED
@@ -1,17 +1,59 @@
1
+ /**
2
+ * Webhook class for managing Telegram bot webhook configuration.
3
+ * Handles setting up and configuring webhooks for Telegram bots.
4
+ */
1
5
  export default class Webhook {
6
+ /** Base URL for the Telegram Bot API */
2
7
  api;
8
+ /** Webhook URL that Telegram will send updates to */
3
9
  webhook;
10
+ /**
11
+ * Creates a new Webhook instance.
12
+ *
13
+ * @param token - The Telegram bot token
14
+ * @param request - The incoming request object used to determine the webhook URL
15
+ */
4
16
  constructor(token, request) {
5
- this.api = new URL('https://api.telegram.org/bot' + token);
6
- this.webhook = new URL(new URL(request.url).origin + `/${token}`);
17
+ this.api = new URL(`https://api.telegram.org/bot${token}`);
18
+ this.webhook = new URL(`${new URL(request.url).origin}/${token}`);
7
19
  }
20
+ /**
21
+ * Sets the webhook URL for the Telegram bot.
22
+ *
23
+ * @returns Promise that resolves to the fetch response from Telegram
24
+ * @throws Will throw an error if the fetch request fails
25
+ */
8
26
  async set() {
9
- const url = new URL(`${this.api.origin}${this.api.pathname}/setWebhook`);
10
- const params = url.searchParams;
11
- params.append('url', this.webhook.toString());
12
- params.append('max_connections', '100');
13
- params.append('allowed_updates', JSON.stringify(['message', 'inline_query', 'business_message', 'business_connection']));
14
- params.append('drop_pending_updates', 'true');
15
- return await fetch(`${url.toString()}?${params.toString()}`);
27
+ const url = new URL(`${this.api.toString()}setWebhook`);
28
+ // Configure webhook parameters
29
+ const params = new URLSearchParams({
30
+ url: this.webhook.toString(),
31
+ max_connections: '100',
32
+ allowed_updates: JSON.stringify(['message', 'inline_query', 'business_message', 'business_connection']),
33
+ drop_pending_updates: 'true',
34
+ });
35
+ try {
36
+ return await fetch(`${url.toString()}?${params.toString()}`);
37
+ }
38
+ catch (error) {
39
+ console.error('Failed to set webhook:', error);
40
+ throw error;
41
+ }
42
+ }
43
+ /**
44
+ * Removes the webhook configuration from Telegram.
45
+ *
46
+ * @returns Promise that resolves to the fetch response from Telegram
47
+ * @throws Will throw an error if the fetch request fails
48
+ */
49
+ async delete() {
50
+ const url = new URL(`${this.api.toString()}deleteWebhook`);
51
+ try {
52
+ return await fetch(url.toString());
53
+ }
54
+ catch (error) {
55
+ console.error('Failed to delete webhook:', error);
56
+ throw error;
57
+ }
16
58
  }
17
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebam/cf-workers-telegram-bot",
3
- "version": "8.1.1",
3
+ "version": "9.1.0",
4
4
  "description": "serverless telegram bot on cf workers",
5
5
  "main": "./dist/main.js",
6
6
  "module": "./dist/main.js",
@@ -31,16 +31,16 @@
31
31
  "url": "https://github.com/codebam/cf-workers-telegram-bot.git"
32
32
  },
33
33
  "devDependencies": {
34
- "@cloudflare/workers-types": "^4.20241202.0",
35
- "@eslint/js": "^9.16.0",
36
- "@typescript-eslint/eslint-plugin": "^8.17.0",
37
- "@typescript-eslint/parser": "^8.17.0",
38
- "eslint": "^9.16.0",
39
- "eslint-config-prettier": "^9.1.0",
40
- "globals": "^15.13.0",
41
- "prettier": "^3.4.2",
42
- "typescript": "^5.7.2",
43
- "typescript-eslint": "^8.17.0",
44
- "vitest": "^2.1.8"
34
+ "@cloudflare/workers-types": "^4.20250303.0",
35
+ "@eslint/js": "^9.21.0",
36
+ "@typescript-eslint/eslint-plugin": "^8.26.0",
37
+ "@typescript-eslint/parser": "^8.26.0",
38
+ "eslint": "^9.21.0",
39
+ "eslint-config-prettier": "^10.0.2",
40
+ "globals": "^16.0.0",
41
+ "prettier": "^3.5.3",
42
+ "typescript": "^5.8.2",
43
+ "typescript-eslint": "^8.26.0",
44
+ "vitest": "^3.0.8"
45
45
  }
46
46
  }