/**
* @module base_telegram_bot
*/
'use strict';
import EventEmitter from 'events';
import { Worker } from 'worker_threads';
import path from 'path';
import got from 'got';
import { jt, getCallerName } from 'vvlad1973-utils';
import { ValidationError } from 'vvlad1973-error-definitions';
import SimpleLogger from 'vvlad1973-simple-logger';
import { createFormData } from '../../helpers/utils.js';
import {
ParseModes,
UpdateTypes,
MessageTypes,
TelegramBotApiMethods,
StatusCodes,
TelegramBotApiMethodsWithMedia,
MediaProperties,
PollTypes,
ServiceMessages,
} from '../enums.js';
import { CallbackQuery } from '../callback_query.js';
import { Message } from '../message.js';
const
telegramBotApiUrl = 'https://api.telegram.org/bot',
telegramBotFileUrl = 'https://api.telegram.org/file/bot',
instance = got.extend({
handlers: [
(options, next) => {
Error.captureStackTrace(options.context);
return next(options);
},
],
hooks: {
beforeError: [
(error) => {
error.source = error.options.context.stack.split('\n');
return error;
},
],
},
});
/**
* @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;
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.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 `${telegramBotApiUrl}${this.#token}/`;
},
},
fileUrl: {
get: () => {
return `${telegramBotFileUrl}${this.#token}/`;
},
},
webhookUrl: {
get: () => {
let result;
if (this.url)
result = `${this.url}/${this.#token}`;
return result;
},
set: (x) => {
this.url = x;
},
},
});
this.defaultParseMode = options.defaultParseMode ?? ParseModes.NONE;
if (process.env.TESTMODE)
this.#senderPath = './classes/base_telegram_bot/sender.js';
else
this.#senderPath = path.resolve(
'node_modules/vvlad1973-telegram-framework/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.error,
`[TelegramBot.Sender] ${data.error.message}`
);
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) {
let userId = String(user?.id);
this.eventNames().forEach((item) => {
let filter;
try {
filter = JSON.parse(item);
} catch (error) { }
if (typeof filter === 'object') {
let fIsDefined =
typeof filter.type !== 'undefined' ||
typeof filter.contents !== 'undefined' ||
typeof filter.params !== 'undefined' ||
typeof filter.chat !== 'undefined' ||
typeof filter.user !== 'undefined';
if (
fIsDefined &&
(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.user === 'undefined' ||
(typeof filter.user &&
typeof userId !== 'undefined' &&
userId.match(filter.user)))
) {
this.emit(item, type, contents, params, chatId, user, data);
}
}
});
}
#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;
}
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() {
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 (this.webhookUrl !== response.response.result.url)
this.setWebhook()
.then((response) => {
this.logger.info(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));
});
}
processUpdate(data) {
this.#logEntry();
this.logger.debug(data, `Process update. Contents:`);
return new Promise((resolve, reject) => {
let object,
parts,
bot,
command,
type,
contents =
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,
chatId =
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,
user =
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,
message = data?.message ? new Message(data?.message, this) : undefined,
editedMessage = data?.edited_message
? new Message(data?.message, this)
: undefined,
channelPost = data?.channel_post
? new Message(data?.message, this)
: undefined,
editedСhannelPost = data?.edited_channel_post
? new Message(data?.message, this)
: undefined,
callbackQuery = data?.callback_query
? new CallbackQuery(data?.callback_query, this)
: undefined,
inlineQuery = data?.inline_query,
chosenInlineResult = data?.chosen_inline_result,
shippingQuery = data?.shipping_query,
preCheckoutQuery = data?.pre_checkout_query,
poll = data?.poll,
pollAnswer = data?.poll_answer,
myChatMember = data?.my_chat_member,
chatMember = data?.chat_member,
chatJoinRequest = data?.chat_join_request,
params =
message?.fileId ??
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;
if (message) {
object = message;
type = UpdateTypes.MESSAGE;
if (typeof contents === 'string')
parts = contents.match(/^[\\/]([^@\s]+)(@([\S]+))?( (.*))?$/);
if (parts) {
command = parts[1];
params = parts[5] ? parts[5] : '';
bot = parts[3] ? parts[3] : '';
}
type = message.type;
} else if (callbackQuery) {
object = callbackQuery;
type = UpdateTypes.CALLBACK_QUERY;
} else if (editedMessage) {
object = editedMessage;
type = UpdateTypes.EDITED_MESSAGE;
} else if (channelPost) {
object = channelPost;
type = UpdateTypes.CHANNEL_POST;
} else if (editedСhannelPost) {
object = editedСhannelPost;
type = UpdateTypes.EDITED_CHANNEL_POST;
} else if (inlineQuery) {
object = inlineQuery;
type = UpdateTypes.INLINE_QUERY;
} else if (chosenInlineResult) {
object = chosenInlineResult;
type = UpdateTypes.CHOSEN_INLINE_RESULT;
} else if (preCheckoutQuery) {
object = preCheckoutQuery;
type = UpdateTypes.PRE_CHECKOUT_QUERY;
} else if (poll) {
object = poll;
type = UpdateTypes.POLL;
} else if (pollAnswer) {
object = pollAnswer;
type = UpdateTypes.POLL_ANSWER;
} else if (myChatMember) {
object = myChatMember;
type = UpdateTypes.MY_CHAT_MEMBER;
} else if (chatMember) {
object = chatMember;
type = UpdateTypes.CHAT_MEMBER;
} else if (chatJoinRequest) {
object = chatJoinRequest;
type = UpdateTypes.CHAT_JOIN_REQUEST;
} else if (shippingQuery) {
object = shippingQuery;
type = UpdateTypes.SHIPPING_QUERY;
}
if (object) {
this.totalReceived++;
if (chatId === user?.id)
this.#emitIfExists(type, contents, params, 'private', user, object);
else this.#emitIfExists(type, contents, params, chatId, user, object);
if (command) {
if (bot === '' || bot === this.username) {
this.emit(
MessageTypes.COMMAND,
command,
params,
chatId,
user,
object
);
this.emit(command, contents, params, chatId, user, object);
if (chatId === user?.id) {
this.emit(
`${MessageTypes.COMMAND}@private`,
command,
params,
chatId,
user,
object
);
this.emit(
`${command}@private`,
contents,
params,
chatId,
user,
object
);
}
}
type = 'command';
contents = command;
}
if (ServiceMessages.includes(type))
this.emit('service_message', contents, params, chatId, user, object);
this.emit('*', type, contents, params, chatId, user, object);
if (chatId === user?.id)
this.emit('*@private', type, contents, params, chatId, user, object);
}
this.#logExit('processUpdate');
return resolve();
});
}
#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 = this.apiUrl,
result,
_options = {},
response;
if (data.multipart) {
let form = createFormData(data.payload);
_options = {
headers: form.getHeaders(),
body: form,
};
} else _options.json = data.payload;
_options.responseType = 'json';
instance
.post(url, _options)
.then((result) => {
result = {
result: {
ok: true,
code: StatusCodes.OK,
},
response: result.body,
request: data,
};
self.logger.debug(result, `Data was sent:`);
self.#logExit('callApi');
return resolve(result);
})
.catch((error) => {
let code;
switch (error.code) {
case 'ENOTFOUND': {
code = StatusCodes.NOT_FOUND;
break;
}
default:
code = StatusCodes.BAD_REQUEST;
}
result = {
result: {
ok: false,
code: code,
message: error.message,
},
response: error.response?.body,
request: data,
};
return reject(result);
});
});
}
#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 (
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);
}
});
}
getMe() {
this.#logEntry();
return new Promise(async (resolve, reject) => {
let data = {
method: TelegramBotApiMethods.GET_ME,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('getMe');
return resolve(response);
})
.catch((error) => reject(error));
});
}
getWebhookInfo() {
this.#logEntry();
return new Promise(async (resolve, reject) => {
let data = {
method: TelegramBotApiMethods.GET_WEBHOOK_INFO,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('getWebhookInfo');
return resolve(response);
})
.catch((error) => reject(error));
});
}
deleteWebhook(dropPendingUpdates = true) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
let data = {
method: TelegramBotApiMethods.DELETE_WEBHOOK,
};
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 = {}) {
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
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('setWebhook');
return resolve(response);
})
.catch((error) => reject(error));
});
}
sendMessage(chatId, text, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && text) {
let data = {
method: TelegramBotApiMethods.SEND_MESSAGE,
chat_id: chatId,
text: text,
};
_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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && photo) {
let data = {
method: TelegramBotApiMethods.SEND_PHOTO,
chat_id: chatId,
photo: photo,
caption: caption,
};
_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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && media) {
let data = {
method: TelegramBotApiMethods.SEND_MEDIA_GROUP,
chat_id: chatId,
media: media,
};
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 = {}) {
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,
};
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 = {}) {
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,
};
_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) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && messageId) {
let data = {
method: TelegramBotApiMethods.DELETE_MESSAGE,
chat_id: chatId,
message_id: messageId,
};
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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && messageId) {
let data = {
method: TelegramBotApiMethods.PIN_CHAT_MESSAGE,
chat_id: chatId,
message_id: messageId,
};
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) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && messageId) {
let data = {
method: TelegramBotApiMethods.UNPIN_CHAT_MESSAGE,
chat_id: chatId,
message_id: messageId,
};
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) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId) {
let data = {
method: TelegramBotApiMethods.UNPIN_ALL_CHAT_MESSAGES,
chat_id: chatId,
};
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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (callbackQueryId) {
let data = {
method: TelegramBotApiMethods.ANSWER_CALLBACK_QUERY,
callback_query_id: callbackQueryId,
};
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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId) {
let data = {
method: TelegramBotApiMethods.SEND_DICE,
chat_id: chatId,
};
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) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && action) {
let data = {
method: 'sendChatAction',
chat_id: chatId,
action: action,
};
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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
let data = {
method: TelegramBotApiMethods.GET_MY_COMMANDS,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('getMyCommands');
return resolve(response);
})
.catch((error) => reject(error));
});
}
deleteMyCommands(_options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
let data = {
method: TelegramBotApiMethods.DELETE_MY_COMMANDS,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('deleteMyCommands');
return resolve(response);
})
.catch((error) => reject(error));
});
}
setMyCommands(commands, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (commands) {
let data = {
method: TelegramBotApiMethods.SET_MY_COMMANDS,
commands: JSON.stringify(commands),
};
_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 = {}) {
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,
};
_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 = {}) {
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,
};
_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 = {}) {
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,
};
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 = {}) {
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: jt(media),
};
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 = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && video) {
let data = {
method: TelegramBotApiMethods.SEND_VIDEO,
chat_id: chatId,
video: video,
caption: caption,
};
_options['parse_mode'] =
_options['parse_mode'] ?? this.defaultParseMode;
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendVideo');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and video are required'));
});
}
sendContact(chatId, phoneNumber, firstName, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && phoneNumber && firstName) {
let data = {
method: TelegramBotApiMethods.SEND_CONTACT,
chat_id: chatId,
phone_number: phoneNumber,
first_name: firstName,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendContact');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(
new ValidationError('ChatId, phoneNumber and firstName are required')
);
});
}
sendAudio(chatId, audio, caption, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && audio) {
let data = {
method: TelegramBotApiMethods.SEND_AUDIO,
chat_id: chatId,
audio: audio,
caption: caption,
};
_options['parse_mode'] =
_options['parse_mode'] ?? this.defaultParseMode;
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendAudio');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and audio are required'));
});
}
sendVoice(chatId, voice, caption, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && voice) {
let data = {
method: TelegramBotApiMethods.SEND_VOICE,
chat_id: chatId,
voice: voice,
caption: caption,
};
_options['parse_mode'] =
_options['parse_mode'] ?? this.defaultParseMode;
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendVoice');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and voice are required'));
});
}
sendDocument(chatId, document, caption = '', _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && document) {
let data = {
method: TelegramBotApiMethods.SEND_DOCUMENT,
chat_id: chatId,
document: document,
caption: caption,
};
_options['parse_mode'] =
_options['parse_mode'] ?? this.defaultParseMode;
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendDocument');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and document are required'));
});
}
sendAnimation(chatId, animation, caption = '', _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && animation) {
let data = {
method: TelegramBotApiMethods.SEND_ANIMATION,
chat_id: chatId,
animation: animation,
caption: caption,
};
_options['parse_mode'] =
_options['parse_mode'] ?? this.defaultParseMode;
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendAnimation');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and animation are required'));
});
}
getChat(chatId) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId) {
let data = {
method: TelegramBotApiMethods.GET_CHAT,
chat_id: chatId,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('getChat');
return resolve(response);
})
.catch((error) => reject(error));
} else return reject(new ValidationError('ChatId is required'));
});
}
getChatAdministrators(chatId) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId) {
let data = {
method: TelegramBotApiMethods.GET_CHAT_ADMINISTRATORS,
chat_id: chatId,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('getChatAdministrators');
return resolve(response);
})
.catch((error) => reject(error));
} else return reject(new ValidationError('ChatId is required'));
});
}
getChatMemberCount(chatId) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId) {
let data = {
method: TelegramBotApiMethods.GET_CHAT_MEMBER_COUNT,
chat_id: chatId,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('getChatMemberCount');
return resolve(response);
})
.catch((error) => reject(error));
} else return reject(new ValidationError('ChatId is required'));
});
}
getChatMember(chatId, userId) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && userId) {
let data = {
method: TelegramBotApiMethods.GET_CHAT_MEMBER,
chat_id: chatId,
user_id: userId,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('getChatMember');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and userId are required'));
});
}
getFile(fileId) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (fileId) {
let data = {
method: TelegramBotApiMethods.GET_FILE,
file_id: fileId,
};
this.#baseTelegramMethod(data)
.then((response) => {
if (response?.response?.result?.file_path)
response.response.result.file_url =
`${this.fileUrl}/${response.response.result.file_path}`;
this.#logExit('getFile');
return resolve(response);
})
.catch((error) => reject(error));
} else return reject(new ValidationError('FileId is required'));
});
}
sendVideoNote(chatId, videoNote, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && videoNote) {
let data = {
method: TelegramBotApiMethods.SEND_VIDEO_NOTE,
chat_id: chatId,
video_note: videoNote,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendVideoNote');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and VideoNote are required'));
});
}
sendPoll(chatId, question, pollOptions, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (
chatId &&
question &&
pollOptions &&
((_options.type == PollTypes.QUIZ && _options.correct_option_id) ||
_options.type !== PollTypes.QUIZ)
) {
let data = {
method: TelegramBotApiMethods.SEND_POLL,
chat_id: chatId,
question: question,
options: jt(pollOptions),
};
_options['explanation_parse_mode'] =
_options['explanation_parse_mode'] ?? this.defaultParseMode;
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendPoll');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(
new ValidationError(
'ChatId, question and options are required. ' +
'If the pool type is quiz, correct_option_id is required too'
)
);
});
}
stopPoll(chatId, messageId) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && messageId) {
let data = {
method: TelegramBotApiMethods.STOP_POLL,
chat_id: chatId,
message_id: messageId,
};
this.#baseTelegramMethod(data)
.then((response) => {
this.#logExit('stopPoll');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and messageId are required'));
});
}
sendSticker(chatId, sticker, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && sticker) {
let data = {
method: TelegramBotApiMethods.SEND_STICKER,
chat_id: chatId,
sticker: sticker,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('sendSticker');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and Sticker are required'));
});
}
banChatMember(chatId, userId, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && userId) {
let data = {
method: TelegramBotApiMethods.BAN_CHAT_MEMBER,
chat_id: chatId,
user_id: userId,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('banChatMember');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and userId are required'));
});
}
unbanChatMember(chatId, userId, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && userId) {
let data = {
method: TelegramBotApiMethods.UNBAN_CHAT_MEMBER,
chat_id: chatId,
user_id: userId,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('unbanChatMember');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and userId are required'));
});
}
restrictChatMember(chatId, userId, permissions, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && userId && permissions) {
let data = {
method: TelegramBotApiMethods.RESTRICT_CHAT_MEMBER,
chat_id: chatId,
user_id: userId,
permissions: permissions,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('restrictChatMember');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and userId and permissions are required'));
});
}
promoteChatMember(chatId, userId, _options = {}) {
this.#logEntry();
return new Promise(async (resolve, reject) => {
if (chatId && userId) {
let data = {
method: TelegramBotApiMethods.PROMOTE_CHAT_MEMBER,
chat_id: chatId,
user_id: userId,
};
this.#baseTelegramMethod(data, _options)
.then((response) => {
this.#logExit('promoteChatMember');
return resolve(response);
})
.catch((error) => reject(error));
} else
return reject(new ValidationError('ChatId and userId are required'));
});
}
}