UNPKG

vvlad1973-telegram-framework

Version:
1,612 lines (1,390 loc) 65.2 kB
/** * @module base_telegram_bot */ 'use strict'; import EventEmitter from 'events'; import { Worker } from 'worker_threads'; import path from 'path'; import { jt, getCallerName } from 'vvlad1973-utils'; import { ValidationError } from 'vvlad1973-error-definitions'; import SimpleLogger from 'vvlad1973-simple-logger'; import { createFormData, getGotInstance, sendPostRequest, getMessageType, parseCommand, getFileId, } from '../../helpers/utils.js'; import { ParseModes, UpdateTypes, MessageTypes, TelegramBotApiMethods, TelegramBotApiMethodsWithMedia, MediaProperties, PollTypes, ServiceMessages, } from '../enums.js'; import { CallbackQuery } from '../callback_query.js'; import { Message } from '../message.js'; import { TELEGRAM_BOT_API_URL, TELEGRAM_BOT_FILE_URL, } from '../../helpers/definitions.js'; const instance = getGotInstance(); /** * @class Implements Interface to Telegram Bot API * * @description This object provide access to methods to communicate with * the Telegram * @readonly * @property {integer} id - Read only. Unique identifier for this user or bot. This number * may have more than 32 significant bits and some programming languages * may have difficulty/silent defects in interpreting it. But it has at * most 52 significant bits, so a 64-bit integer or double-precision float * type are safe for storing this identifier * @readonly * @property {string} firstName - Read only. User's or bot's first name * @readonly * @property {string} [username] - Read only. User's or bot's username * @readonly * @property {string} [lastName] - Read only. User's or bot's last name * @readonly * @property {string} [languageCode] - Read only. ETF language tag of the user's language * @readonly * @property {boolean} [canJoinGroups] - Read only. True, if the bot can be invited to * groups * @readonly * @property {boolean} [canReadAllGroupMessages] - Read only. True, if privacy mode is * disabled for the bot * @readonly * @property {boolean} [supportsInlineQueries] - Read only. True, if the bot supports * inline queries * @property {module:enums.ParseModes} [defaultParseMode] - Default value mode * for parsing messages wchich send by the bot * @property {Object} [logger] - Object implemented multilevel logging routines. If not * specified, will use default logger * @readonly * @property {Worker} sender - Read only. The bot's sender which will send data to * Telegram by using sending queue of messages * @property {boolean} [sendByQueue] - If True the bot will send data to * Telegram by using sending queue of messages * @readonly * @property {integer} lastMessageId - Read only. ID of the last sent message * @property {integer} [maxConnections] - The maximum allowed number of * simultaneous HTTPS connections to the webhook for update delivery, 1-100. * Defaults to 40. Use lower values to limit the load on your bot's server, * and higher values to increase your bot's throughput * @property {string} [secretToken] - A secret token to be sent in a header * “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 * characters. Only characters A-Z, a-z, 0-9, _ and - are allowed. The * header is useful to ensure that the request comes from a webhook set * by you * @property {Object} [certificate] - Upload your public key certificate so * that the root certificate in use can be checked. See our self-signed * guide for details * @property {string} [certificate.file] - Filename of the certificate * @property {Array.<string>} [allowedUpdates] - A JSON-serialized list of the * update types you want your bot to receive. For example, specify * [“message”, “edited_channel_post”, “callback_query”] to only receive * updates of these types. See Update for a complete list of available * update types. Specify an empty list to receive all update types except * chat_member (default). If not specified, the previous setting will be * used. * Please note that this parameter doesn't affect updates created before * the call to the setWebhook, so unwanted updates may be received for a * short period of time * @property {boolean} [dropPendingUpdates] - Pass True to drop all pending * updates * @property {string} [ipAddress] - The fixed IP address which will be used * to send webhook requests instead of the IP address resolved through DNS */ export class BaseTelegramBot extends EventEmitter { #token; #secretToken; #defaultParseMode; #senderPath; #id; #username; #firstName; #lastName; #languageCode; #canJoinGroups; #canReadAllGroupMessages; #supportsInlineQueries; webAppUrl; lastMessageId; totalSent = 0; totalReceived = 0; queueSize = 0; path = ''; maxConnections; ipAddress; allowedUpdates; dropPendingUpdates; certificate; /** * @param {string} token - Each bot is given a unique authentication token * when it is created. The token looks something like * 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 * @param {Object} [options] - some optional parameters to pass to * @param {string} [options.url] - HTTPS URL to send updates to. Use an * empty string to remove webhook integration * @param {string} [options.secretToken] - A secret token to be sent in a * header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, * 1-256 characters. Only characters A-Z, a-z, 0-9, _ and - are allowed. * The header is useful to ensure that the request comes from a webhook * set by you * @param {integer} [options.maxConnections] - The maximum allowed number of * simultaneous HTTPS connections to the webhook for update delivery, * 1-100. Defaults to 40. Use lower values to limit the load on your * bot's server, and higher values to increase your bot's throughput * @param {boolean} [options.sendByQueue] - If True the bot will send data to * Telegram by using sending queue of messages * @param {Object} [options.certificate] - Upload your public key certificate so * that the root certificate in use can be checked. See our self-signed * guide for details * @param {string} options.certificate.file - Frilename of the certificate * @param {Array.<string>} [options.allowedUpdates] - A JSON-serialized list of the * update types you want your bot to receive. For example, specify * [“message”, “edited_channel_post”, “callback_query”] to only receive * updates of these types. See Update for a complete list of available * update types. Specify an empty list to receive all update types except * chat_member (default). If not specified, the previous setting will be * used. * Please note that this parameter doesn't affect updates created before * the call to the setWebhook, so unwanted updates may be received for a * short period of time * @param {boolean} [options.dropPendingUpdates] - Pass True to drop all pending * updates * @param {string} [options.ipAddress] - The fixed IP address which will be used * to send webhook requests instead of the IP address resolved through DNS * @param {module:enums.ParseModes} [defaultParseMode] - Default value mode * for parsing messages wchich send by the bot * @param {Object} [options.sender] - Params for the bot's sender which will send * data to Telegram by using sending queue of messages * @param {integer} [options.sender.capacityLimit] - Limit of amount of messages * to send during one interval of time. Default value is 30 * @param {integer} [options.sender.capacityInterval] - Duration of one time * interval in milliseconds for message flow control. Default value is 1000 * @param {integer} [options.sender.queueSize] - Limit of size of message queue. * Default value is 10 000 * @param {Object} [options.sender.logger] - Object implemented multilevel logging * routines. If not specified, will use default logger * @returns {module:base_telegram_bot.BaseTelegramBot} Instance of the * BaseTelegramBot class */ constructor(token, options = {}) { super(); this.#token = token; this.#secretToken = options.secretToken; this.ipAddress = options.ipAddress; this.allowedUpdates = options.allowedUpdates; this.url = options.url ?? ''; this.path = options.path; this.dropPendingUpdates = options.dropPendingUpdates; this.certificate = options.certificate; this.sendByQueue = typeof options.sendByQueue === 'undefined' ? true : options.sendByQueue; this.maxConnections = options.maxConnections ?? 40; this.logger = options.logger ?? new SimpleLogger(); Object.defineProperties(this, { id: { get: () => { return this.#id; }, }, username: { get: () => { return this.#username; }, }, firstName: { get: () => { return this.#firstName; }, }, lastName: { get: () => { return this.#lastName; }, }, languageCode: { get: () => { return this.#languageCode; }, }, canJoinGroups: { get: () => { return this.#canJoinGroups; }, }, canReadAllGroupMessages: { get: () => { return this.#canReadAllGroupMessages; }, }, supportsInlineQueries: { get: () => { return this.#supportsInlineQueries; }, }, defaultParseMode: { get: () => { return this.#defaultParseMode; }, set: (x) => { if (Object.values(ParseModes).includes(x)) { this.#defaultParseMode = x; } else { this.#defaultParseMode = ParseModes.NONE; } }, }, apiUrl: { get: () => { return `${TELEGRAM_BOT_API_URL}${this.#token}/`; }, }, fileUrl: { get: () => { return `${TELEGRAM_BOT_FILE_URL}${this.#token}/`; }, }, webhookUrl: { get: () => { let result; if (this.url) result = `${this.url}/${this.path}`; // result = `${this.url}/${this.#token}`; return result; }, set: (x) => { this.url = x; }, }, }); this.defaultParseMode = options.defaultParseMode ?? ParseModes.NONE; if (process.env.TESTMODE) this.#senderPath = './src/classes/base_telegram_bot/sender.js'; else this.#senderPath = path.resolve( 'node_modules/vvlad1973-telegram-framework/src/classes/base_telegram_bot/sender.js' ); this.sender = new Worker(this.#senderPath, { workerData: { url: this.apiUrl, capacity: { limit: options.sender?.capacityLimit ?? 30, interval: options.sender?.capacityInterval ?? 1000, }, queue: { size: options.sender?.queueSize ?? 10000, }, }, }); this.sender.logger = options.sender?.logger ?? this.logger; this.sender.on('message', (data) => { if (data?.error) { this.sender.logger.error(data, `[TelegramBot.Sender] ${data.message}`); this.emit(data.request.id, data); } else if (data?.debug) this.sender.logger.debug(data.debug, `[TelegramBot.Sender]`); else if (data?.warn) this.sender.logger.warn(data.warn, `[TelegramBot.Sender]`); else if (data?.info) this.sender.logger.info(data.info, `[TelegramBot.Sender]`); else if (data?.trace) this.sender.logger.trace(data.trace, `[TelegramBot.Sender]`); else { if ( typeof data.result === 'object' && typeof data.request?.id === 'string' ) { if (data?.result?.ok && data?.response?.result?.message_id) this.lastMessageId = data?.response?.result?.message_id; this.emit(data.request.id, data); } else { throw new TypeError( `Unexpected content of Sender response: ${jt(data)}` ); } } }); } #emitIfExists(type, contents, params, chatId, user, data, chatType) { let userId = String(user?.id); this.eventNames().forEach((item) => { let filter; try { filter = JSON.parse(item); } catch (error) { } if (typeof filter === 'object') { let filterIsDefined = typeof filter.type !== 'undefined' || typeof filter.contents !== 'undefined' || typeof filter.params !== 'undefined' || typeof filter.chat !== 'undefined' || typeof filter.user !== 'undefined' || typeof filter.chat_type !== 'undefined'; if ( filterIsDefined && (typeof filter.type === 'undefined' || (typeof filter.type && typeof type !== 'undefined' && type.match(filter.type))) && (typeof filter.contents === 'undefined' || (typeof filter.contents && typeof contents !== 'undefined' && contents.match(filter.contents))) && (typeof filter.params === 'undefined' || (typeof filter.params && typeof params !== 'undefined' && params.match(filter.params))) && (typeof filter.chat === 'undefined' || (typeof filter.chat && typeof chatId !== 'undefined' && chatId.match(filter.chat))) && (typeof filter.chat_type === 'undefined' || (typeof filter.chat_type && typeof chatType !== 'undefined' && chatType.match(filter.chat_type))) && (typeof filter.user === 'undefined' || (typeof filter.user && typeof userId !== 'undefined' && userId.match(filter.user))) ) { this.emit(item, type, contents, params, chatId, user, data, chatType); } } }); } #getNextId() { this.totalSent++; return `${Date.now()}-${this.totalSent}`; } #logEntry(functionName) { functionName = functionName ?? getCallerName(); this.logger.trace(`Function ${functionName}() execution...`); } #logExit(functionName) { functionName = functionName ?? getCallerName(); this.logger.trace(`Function ${functionName}() complete`); } #getEventName(event) { let result; if (typeof event === 'object') { if ( typeof event.type !== 'undefined' || typeof event.contents !== 'undefined' || typeof event.params !== 'undefined' || typeof event.chat !== 'undefined' || typeof event.user != 'undefined' ) { result = JSON.stringify(event); } else { throw new ValidationError('Filter is incorrect'); } } else { result = event; } return result; } #defineContents(data) { let result = data?.message?.text ?? data?.message?.caption ?? data?.edited_message?.text ?? data?.edited_message?.caption ?? data?.channel_post?.text ?? data?.channel_post?.caption ?? data?.edited_channel_post?.text ?? data?.edited_channel_post?.caption ?? data?.callback_query?.data ?? data?.inline_query?.query ?? data?.chosenInlineResult?.result_id ?? data?.poll?.question ?? data?.poll_answer?.option_ids.toString() ?? data?.my_chat_member?.new_chat_member?.status; return result; } #defineChatId(data) { let result = data?.message?.chat?.id ?? data?.edited_message?.chat?.id ?? data?.channel_post?.chat?.id ?? data?.edited_channel_post?.chat?.id ?? data?.callback_query?.message?.chat?.id ?? data?.my_chat_member?.chat?.id ?? data?.chat_join_request?.chat?.id ?? data?.chat_member?.chat?.id; return result; } #defineChatType(data) { let result = data?.message?.chat?.type ?? data?.edited_message?.chat?.type ?? data?.channel_post?.chat?.type ?? data?.edited_channel_post?.chat?.type ?? data?.callback_query?.message?.chat?.type ?? data?.my_chat_member?.chat?.type ?? data?.chat_join_request?.chat?.type ?? data?.chat_member?.chat?.type; return result; } #defineUser(data) { let result = data?.message?.from ?? data?.edited_message?.from ?? data?.channel_post?.from ?? data?.edited_channel_post?.from ?? data?.callback_query?.from ?? data?.inline_query?.from ?? data?.chosen_inline_result?.from ?? data?.shipping_query?.from ?? data?.pre_checkout_query?.from ?? data?.inline_query?.from ?? data?.poll_answer?.user ?? data?.my_chat_member?.from ?? data?.chat_join_request?.from ?? data?.chat_member?.from; return result; } #defineUpdateType(data) { let result; if (typeof data === 'object') { if (data.message) { result = UpdateTypes.MESSAGE; } else if (data.edited_message) { result = UpdateTypes.EDITED_MESSAGE; } else if (data.channel_post) { result = UpdateTypes.CHANNEL_POST; } else if (data.edited_channel_post) { result = UpdateTypes.EDITED_CHANNEL_POST; } else if (data.callback_query) { result = UpdateTypes.CALLBACK_QUERY; } else if (data.inline_query) { result = UpdateTypes.INLINE_QUERY; } else if (data.chosen_inline_result) { result = UpdateTypes.CHOSEN_INLINE_RESULT; } else if (data.shipping_query) { result = UpdateTypes.SHIPPING_QUERY; } else if (data.pre_checkout_query) { result = UpdateTypes.PRE_CHECKOUT_QUERY; } else if (data.poll) { result = UpdateTypes.POLL; } else if (data.poll_answer) { result = UpdateTypes.POLL_ANSWER; } else if (data.my_chat_member) { result = UpdateTypes.MY_CHAT_MEMBER; } else if (data.chat_member) { result = UpdateTypes.CHAT_MEMBER; } else if (data.chat_join_request) { result = UpdateTypes.CHAT_JOIN_REQUEST; } else { result = UpdateTypes.UNKNOWN; } } return result; } #defineParams(data) { let result = getFileId(data?.message) ?? data?.callback_query?.id ?? data?.inline_query?.id ?? data?.chosenInlineResult?.query ?? data?.poll?.options ?? data?.chat_join_request?.invite_link ?? data?.poll_answer?.poll_id; return result; } on(event, listener) { let eventName = this.#getEventName(event); super.on(eventName, listener); } once(event, listener) { let eventName = this.#getEventName(event); super.once(eventName, listener); } init(forceSetWebhook = false) { this.#logEntry(); return new Promise((resolve, reject) => { this.getMe() .then( (response) => new Promise((resolve, reject) => { this.logger.trace( response.response?.result, `Bot initialisation data:` ); this.#id = response.response?.result?.id; this.#firstName = response.response?.result?.first_name; this.#lastName = response.response?.result?.last_name; this.#username = response.response?.result?.username; this.#languageCode = response.response?.result?.language_code; this.#canJoinGroups = response.response?.result?.can_join_groups; this.#canReadAllGroupMessages = response.response?.result?.can_read_all_group_messages; this.#supportsInlineQueries = response.response?.result?.supports_inline_queries; this.getWebhookInfo() .then((response) => { this.logger.trace(response, "Got bot's webhook info:"); return resolve(response); }) .catch((error) => reject(error)); }) ) .then( (response) => new Promise((resolve, reject) => { if ( forceSetWebhook || this.webhookUrl !== response.response.result.url ) this.setWebhook() .then((response) => { this.logger.trace(response, "Bot's webhook was reset"); return resolve(); }) .catch((error) => { return reject(error); }); else return resolve(response); }) ) .then((response) => { this.#logExit('init'); return resolve(response); }) .catch((error) => reject(error)); }); } async processUpdate(data, route) { this.#logEntry(); this.logger.debug(data, `Process update. Contents:`); route = route ?? this.#username; let object; let botName; let command; let contents; let chatId; let chatType; let user; let params; let type; if (typeof data === 'object' && data.update_id) { this.totalReceived++; contents = this.#defineContents(data); chatId = this.#defineChatId(data); chatType = this.#defineChatType(data); user = this.#defineUser(data); params = this.#defineParams(data); type = this.#defineUpdateType(data); switch (type) { case UpdateTypes.MESSAGE: type = getMessageType(data?.message); if (type === MessageTypes.TEXT && typeof contents === 'string') { ({ command, params, botName } = parseCommand(contents) ?? {}); if (command) { type = MessageTypes.COMMAND; contents = command; } } object = new Message(data?.message, this); break; case UpdateTypes.CALLBACK_QUERY: object = new CallbackQuery(data?.callback_query, this); break; case UpdateTypes.EDITED_MESSAGE: object = new Message(data?.edited_message, this); break; case UpdateTypes.CHANNEL_POST: object = new Message(data?.channel_post, this); break; case UpdateTypes.EDITED_CHANNEL_POST: object = new Message(data?.edited_channel_post, this); break; case UpdateTypes.INLINE_QUERY: object = data?.inline_query; break; case UpdateTypes.CHOSEN_INLINE_RESULT: object = data?.chosen_inline_result; break; case UpdateTypes.SHIPPING_QUERY: object = data?.shipping_query; break; case UpdateTypes.PRE_CHECKOUT_QUERY: object = data?.pre_checkout_query; break; case UpdateTypes.POLL: object = data?.poll; break; case UpdateTypes.POLL_ANSWER: object = data?.poll_answer; break; case UpdateTypes.MY_CHAT_MEMBER: object = data?.my_chat_member; break; case UpdateTypes.CHAT_MEMBER: object = data?.chat_member; break; case UpdateTypes.CHAT_JOIN_REQUEST: object = data?.chat_join_request; break; default: object = data; } if (chatId === user?.id) { this.#emitIfExists(type, contents, params, 'private', user, object, route); } else { this.#emitIfExists( type, contents, params, chatId, user, object, chatType, route ); } if (command) { if (botName === '' || botName === this.username) { this.emit( MessageTypes.COMMAND, command, params, chatId, user, object, route ); this.emit(command, contents, params, chatId, user, object, chatType, route); if (chatId === user?.id) { this.emit( `${MessageTypes.COMMAND}@private`, command, params, chatId, user, object, route ); this.emit( `${command}@private`, contents, params, chatId, user, object, route ); } } } if (ServiceMessages.includes(type)) this.emit( MessageTypes.SERVICE_MESSAGE, contents, params, chatId, user, object, chatType, route ); this.emit('*', type, contents, params, chatId, user, object, chatType, route); if (chatId === user?.id) this.emit('*@private', type, contents, params, chatId, user, object, route); } this.#logExit(); return object; } #sendByQueue(data) { this.#logEntry(); return new Promise(async (resolve, reject) => { let id = this.#getNextId(); this.sender.postMessage({ id: id, data: data }); this.logger.debug(data, `[queueId=${id}] Data was enqueued:`); this.once(id, (data) => { if (data.result.ok) { this.logger.debug(data, `[queueId=${id}] Data was sent:`); if (data.queue_size) this.queueSize = data.queue_size; this.#logExit('#sendByQueue'); return resolve(data); } else { return reject(data); } }); }); } #callApi(data) { this.#logEntry(); let self = this; return new Promise(async (resolve, reject) => { let url = data.payload.token ? `${TELEGRAM_BOT_API_URL}${data.payload.token}/` : this.apiUrl, result, _options = {}, response; delete data.payload.token; try { if (data.multipart) { let form = createFormData(data.payload); _options = { headers: form.getHeaders(), body: form, }; } else _options.json = data.payload; _options.responseType = 'json'; } catch (error) { self.logger.error(error); } self.logger.debug(_options, `Data will sent:`); try { response = await sendPostRequest(url, _options, instance); self.logger.debug(response.body, `Data was sent:`); self.#logExit('callApi'); return resolve(response); } catch (error) { return reject(error); } }); } #baseTelegramMethod(required, ...optional) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data, hasMedia = false, result, obj = Object.assign(required, ...optional); if (obj._options) { obj = Object.assign(obj, obj._options); delete obj._options; } if (obj[MediaProperties.MEDIA]) { if (Array.isArray(obj[MediaProperties.MEDIA])) { for (let item of obj[MediaProperties.MEDIA]) if (item.media.file || item.thumbnail?.file) hasMedia = true; } else { if (obj.media.media?.file || obj.media.thumbnail?.file) hasMedia = true; } } else if ( Object.values(TelegramBotApiMethodsWithMedia).includes(obj.method) ) { for (let item of Object.values(MediaProperties)) { if (Object.keys(obj).includes(item) && obj[item].file) { hasMedia = true; } } } data = { multipart: hasMedia, payload: obj, }; try { if (this.sendByQueue) { result = await this.#sendByQueue(data); } else { result = await this.#callApi(data); } this.totalSent++; this.#logExit('#baseTelegramMethod'); return resolve(result); } catch (error) { return reject(error); } }); } async terminate() { this.#logEntry(); this.sender.terminate(); this.logger.debug(`Sender worker was terminated successfully`); this.#logExit('terminate'); } getMe(token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.GET_ME, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('getMe'); return resolve(response); }) .catch((error) => reject(error)); }); } close(token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.CLOSE, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('close'); return resolve(response); }) .catch((error) => reject(error)); }); } logOut(token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.LOG_OUT, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('logOut'); return resolve(response); }) .catch((error) => reject(error)); }); } getWebhookInfo(token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.GET_WEBHOOK_INFO, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('getWebhookInfo'); return resolve(response); }) .catch((error) => reject(error)); }); } deleteWebhook(dropPendingUpdates = true, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.DELETE_WEBHOOK, token, }; if (!dropPendingUpdates) data['drop_pending_updates'] = false; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('deleteWebhook'); return resolve(response); }) .catch((error) => reject(error)); }); } /** * Use this method to specify a URL and receive incoming updates via an * outgoing webhook. Whenever there is an update for the bot, we will send * an HTTPS POST request to the specified URL, containing a JSON-serialized * Update. In case of an unsuccessful request, we will give up after a * reasonable amount of attempts. Returns True on success. * If you'd like to make sure that the webhook was set by you, you can * specify secret data in the parameter secret_token. If specified, the * request will contain a header “X-Telegram-Bot-Api-Secret-Token” with the * secret token as content * @param {string} [url] - HTTPS URL to send updates to. Use an * empty string to remove webhook integration. Default value is empty * string * @param {Object} [options] - some optional parameters to pass to * @param {string} [options.secretToken] - A secret token to be sent in a * header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, * 1-256 characters. Only characters A-Z, a-z, 0-9, _ and - are allowed. * The header is useful to ensure that the request comes from a webhook * set by you * @param {integer} [options.maxConnections] - The maximum allowed number of * simultaneous HTTPS connections to the webhook for update delivery, * 1-100. Defaults to 40. Use lower values to limit the load on your * bot's server, and higher values to increase your bot's throughput * @param {boolean} [options.sendByQueue] - If True the bot will send data to * Telegram by using sending queue of messages * @param {Object} [options.certificate] - Upload your public key certificate so * that the root certificate in use can be checked. See our self-signed * guide for details * @param {string} options.certificate.file - Frilename of the certificate * @param {Array.<string>} [options.allowedUpdates] - A JSON-serialized list of the * update types you want your bot to receive. For example, specify * [“message”, “edited_channel_post”, “callback_query”] to only receive * updates of these types. See Update for a complete list of available * update types. Specify an empty list to receive all update types except * chat_member (default). If not specified, the previous setting will be * used. * Please note that this parameter doesn't affect updates created before * the call to the setWebhook, so unwanted updates may be received for a * short period of time * @param {boolean} [options.dropPendingUpdates] - Pass True to drop all pending * updates * @param {string} [options.ipAddress] - The fixed IP address which will be used * to send webhook requests instead of the IP address resolved through DNS * @returns {Object} Response with method executing result */ setWebhook(url, options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (typeof url === 'string') this.webhookUrl = url; else url = this.webhookUrl; if (typeof this.webhookUrl === 'undefined') this.webhookUrl = ''; if (options.maxConnections) this.maxConnections = options.maxConnections; if (options.secretToken) this.#secretToken = options.secretToken; if (options.ipAddress) this.ipAddress = options.ipAddress; if (options.allowedUpdates) this.allowedUpdates = options.allowedUpdates; if (options.dropPendingUpdates) this.dropPendingUpdates = options.dropPendingUpdates; if (options.certificate) this.certificate = options.certificate; let data = { method: TelegramBotApiMethods.SET_WEBHOOK, url: this.webhookUrl, max_connections: this.maxConnections, secret_token: this.#secretToken, ip_address: this.ipAddress, allowed_updates: this.allowedUpdates, drop_pending_updates: this.dropPendingUpdates, certificate: this.certificate, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('setWebhook'); return resolve(response); }) .catch((error) => reject(error)); }); } getUpdates(_options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.GET_UPDATES, token, }; return this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('getUpdates'); return resolve(response); }) .catch((error) => reject(error)); }); } sendMessage(chatId, text, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && text) { let data = { method: TelegramBotApiMethods.SEND_MESSAGE, chat_id: chatId, text: text, token, }; _options['parse_mode'] = _options['parse_mode'] ?? this.defaultParseMode; return this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('sendMessage'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('ChatId and text are required')); }); } sendPhoto(chatId, photo, caption, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && photo) { let data = { method: TelegramBotApiMethods.SEND_PHOTO, chat_id: chatId, photo: photo, caption: caption, token, }; _options['parse_mode'] = _options['parse_mode'] ?? this.defaultParseMode; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('sendPhoto'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('ChatId and photo are required')); }); } sendMediaGroup(chatId, media, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && media) { let data = { method: TelegramBotApiMethods.SEND_MEDIA_GROUP, chat_id: chatId, media: media, token, }; for (let mediaItem of media) mediaItem['parse_mode'] = mediaItem['parse_mode'] ?? this.defaultParseMode; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('sendMediaGroup'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError('ChatId and media group are required') ); }); } forwardMessage(chatId, fromChatId, messageId, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && fromChatId && messageId) { let data = { method: TelegramBotApiMethods.FORWARD_MESSAGE, chat_id: chatId, from_chat_id: fromChatId, message_id: messageId, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('forwardMessage'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError('ChatId, fromChatId and messageId are required') ); }); } copyMessage(chatId, fromChatId, messageId, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && fromChatId && messageId) { let data = { method: TelegramBotApiMethods.COPY_MESSAGE, chat_id: chatId, from_chat_id: fromChatId, message_id: messageId, token, }; _options['parse_mode'] = _options['parse_mode'] ?? this.defaultParseMode; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('copyMessage'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError('ChatId, fromChatId and messageId are required') ); }); } deleteMessage(chatId, messageId, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && messageId) { let data = { method: TelegramBotApiMethods.DELETE_MESSAGE, chat_id: chatId, message_id: messageId, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('deleteMessage'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('ChatId and messageId are required')); }); } pinChatMessage(chatId, messageId, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && messageId) { let data = { method: TelegramBotApiMethods.PIN_CHAT_MESSAGE, chat_id: chatId, message_id: messageId, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('pinChatMessage'); return resolve(response); }) .catch((error) => reject(error)); } else return Promise.reject( new ValidationError('ChatId and messageId are required') ); }); } unpinChatMessage(chatId, messageId, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && messageId) { let data = { method: TelegramBotApiMethods.UNPIN_CHAT_MESSAGE, chat_id: chatId, message_id: messageId, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('unpinChatMessage'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('ChatId and messageId are required')); }); } unpinAllChatMessages(chatId, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId) { let data = { method: TelegramBotApiMethods.UNPIN_ALL_CHAT_MESSAGES, chat_id: chatId, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('unpinAllChatMessages'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('ChatId is required')); }); } answerCallbackQuery(callbackQueryId, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (callbackQueryId) { let data = { method: TelegramBotApiMethods.ANSWER_CALLBACK_QUERY, callback_query_id: callbackQueryId, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('answerCallbackQuery'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('callbackQueryId is required')); }); } sendDice(chatId, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId) { let data = { method: TelegramBotApiMethods.SEND_DICE, chat_id: chatId, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('sendDice'); return resolve(response); }) .catch((error) => reject(error)); } else return Promise.reject(new ValidationError('ChatId is required')); }); } sendChatAction(chatId, action, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (chatId && action) { let data = { method: 'sendChatAction', chat_id: chatId, action: action, token, }; this.#baseTelegramMethod(data) .then((response) => { this.#logExit('sendChatAction'); return resolve(response); }) .catch((error) => reject(error)); } else return Promise.reject( new ValidationError('ChatId and action are required') ); }); } getMyCommands(_options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.GET_MY_COMMANDS, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('getMyCommands'); return resolve(response); }) .catch((error) => reject(error)); }); } deleteMyCommands(_options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { let data = { method: TelegramBotApiMethods.DELETE_MY_COMMANDS, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('deleteMyCommands'); return resolve(response); }) .catch((error) => reject(error)); }); } setMyCommands(commands, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if (commands) { let data = { method: TelegramBotApiMethods.SET_MY_COMMANDS, commands: JSON.stringify(commands), token, }; _options['scope'] = jt(_options['scope']); this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('setMyCommands'); return resolve(response); }) .catch((error) => reject(error)); } else return reject(new ValidationError('Commands are required')); }); } editMessageText(text, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if ( text && (_options.inline_message_id || (_options.chat_id && _options.message_id)) ) { let data = { method: TelegramBotApiMethods.EDIT_MESSAGE_TEXT, text: text, token, }; _options['parse_mode'] = _options['parse_mode'] ?? this.defaultParseMode; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('editMessageText'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError( 'Text and inlineMessageId or chatId with ' + 'messageId must be defined' ) ); }); } editMessageCaption(caption, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if ( _options.inline_message_id || (_options.chat_id && _options.message_id) ) { let data = { method: TelegramBotApiMethods.EDIT_MESSAGE_CAPTION, caption: caption, token, }; _options['parse_mode'] = _options['parse_mode'] ?? this.defaultParseMode; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('editMessageCaption'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError( 'inlineMessageId or chatId with messageId must be defined' ) ); }); } editMessageReplyMarkup(_options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if ( _options.inline_message_id || (_options.chat_id && _options.message_id) ) { let data = { method: TelegramBotApiMethods.EDIT_MESSAGE_REPLY_MARKUP, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('editMessageReplyMarkup'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError( 'inlineMessageId or chatId with messageId must be defined' ) ); }); } editMessageMedia(media, _options = {}, token) { this.#logEntry(); return new Promise(async (resolve, reject) => { if ( _options.inline_message_id || (_options.chat_id && _options.message_id) ) { media.parse_mode = media.parse_mode ?? this.defaultParseMode; let data = { method: TelegramBotApiMethods.EDIT_MESSAGE_MEDIA, media: media, token, }; this.#baseTelegramMethod(data, _options) .then((response) => { this.#logExit('editMessageMedia'); return resolve(response); }) .catch((error) => reject(error)); } else return reject( new ValidationError( 'inlineMessageId or chatId with messageId must be defined' ) ); }); } sendVideo(chatId, video, caption, _options = {}, token) { this.#logEntry(); return new Pro