umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
470 lines (469 loc) • 19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelegramRequest = void 0;
const index_1 = require("../../../index");
const constants_1 = require("../Telegram/constants");
const constants_2 = require("./constants");
/**
* Базовый URL для всех методов Telegram API
*/
const API_ENDPOINT = 'https://api.telegram.org/bot';
/**
* Класс для взаимодействия с API Telegram
* Предоставляет методы для отправки сообщений, файлов и других типов контента
* @see (https://core.telegram.org/bots/api) Смотри тут
*
* @example
* ```ts
* import { TelegramRequest } from './api/TelegramRequest';
*
* // Создание экземпляра
* const telegram = new TelegramRequest();
* telegram.initToken('your-bot-token');
*
* // Отправка простого сообщения
* await telegram.sendMessage(12345, 'Привет!');
*
* // Отправка форматированного сообщения
* await telegram.sendMessage(12345,
* '*Жирный текст* и _курсив_\n' +
* '[Ссылка](http://localhost)\n' +
* '`code` и ```pre```',
* { parse_mode: 'Markdown' }
* );
*
* // Отправка сообщения с клавиатурой
* const keyboard = {
* keyboard: [[
* { text: 'Кнопка 1' },
* { text: 'Кнопка 2' }
* ]],
* resize_keyboard: true,
* one_time_keyboard: true
* };
*
* await telegram.sendMessage(12345, 'Выберите:', {
* reply_markup: JSON.stringify(keyboard)
* });
*
* // Отправка файлов
* await telegram.sendPhoto(12345, 'photo.jpg', 'Описание фото');
* await telegram.sendDocument(12345, 'document.pdf');
* await telegram.sendAudio(12345, 'audio.mp3', {
* title: 'Название',
* performer: 'Исполнитель'
* });
* ```
*/
class TelegramRequest {
/**
* Экземпляр класса для выполнения HTTP-запросов
*
*/
#request;
/**
* Текст последней возникшей ошибки
*
*/
#error;
/**
* Токен доступа к Telegram API
*/
token;
/**
* Контекст приложения.
*/
#appContext;
/**
* Создает экземпляр класса для работы с API Telegram
* Устанавливает токен из конфигурации приложения, если он доступен
*/
constructor(appContext) {
this.#request = new index_1.Request(appContext);
this.#request.maxTimeQuery = 5500;
this.token = null;
this.#error = null;
this.#appContext = appContext;
if (appContext.appConfig.tokens[constants_1.T_TELEGRAM]?.token !== undefined) {
this.initToken(appContext.appConfig.tokens[constants_1.T_TELEGRAM].token);
}
}
/**
* Инициализирует токен доступа к Telegram API
* @param token Токен для доступа к API
*/
initToken(token) {
this.token = token;
}
/**
* Формирует URL для отправки запроса
* @returns Полный URL для API запроса
*
*/
_getUrl() {
return `${API_ENDPOINT}${this.#appContext.appConfig.tokens[constants_1.T_TELEGRAM].token}/`;
}
/**
* Подготавливает данные для отправки файла
* @param type Тип отправляемого файла
* @param file Путь к файлу или его содержимое
*
*/
async #initPostFile(type, file) {
this.#request.post = {};
if (type === 'media' && typeof file !== 'string') {
const formData = new FormData();
const media = [];
for (let index = 0; index < file.length; index++) {
const item = file[index];
const key = `photo${index}`;
let mediaItem = item.media;
if (item.media.includes('attach://')) {
await this.#request.addAttachFile(formData, item.media.replace('attach://', ''), key);
mediaItem = `attach://${key}`;
}
media.push({
type: item.type,
media: mediaItem,
});
}
formData.append('media', JSON.stringify(media));
this.#request.post = formData;
}
else if (index_1.Text.isUrl(file)) {
this.#request.post[type] = file;
}
else {
this.#request.attach = file;
this.#request.attachName = type;
}
}
/**
* Отправляет запрос к Telegram API
* @param method Название метода API
* @param userId ID пользователя или чата
* @returns Результат выполнения метода или null при ошибке
*/
async call(method, userId = null) {
if (userId) {
if (this.#request.post instanceof FormData) {
this.#request.post.append('chat_id', userId.toString());
}
else {
this.#request.post ??= {};
this.#request.post.chat_id = userId;
}
}
if (this.token) {
if (method) {
const data = await this.#request.send(this._getUrl() + method);
if (data.status && data.data) {
if (!data.data.ok) {
this.#error = data;
this.#log('call() Запрос к платформе завершился с ошибкой.');
return null;
}
return data.data;
}
this.#log(data.err);
}
}
else {
this.#log((0, constants_2.getErrorToken)(constants_1.T_TELEGRAM, 'call'));
}
return null;
}
/**
* Санитизировать текст сообщения
* @param text
* @param parseMode
*
*/
#sanitizeTelegramMessage(text, parseMode) {
if (parseMode === 'HTML') {
// Экранирование HTML сущностей
return text
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
return text;
}
/**
* Отправляет текстовое сообщение
* @param chatId ID чата или пользователя
* @param message Текст сообщения
* @param params Дополнительные параметры:
* - parse_mode: формат текста
* - Markdown: *жирный*, _курсив_, [ссылка](http://localhost), `код`, ```pre```
* - HTML: <b>жирный</b>, <i>курсив</i>, <a href="http://localhost">ссылка</a>, <code>код</code>, <pre>pre</pre>
* - disable_web_page_preview: отключить предпросмотр ссылок
* - disable_notification: отключить уведомление
* - reply_to_message_id: ID сообщения для ответа
* - reply_markup: клавиатура в JSON формате
* - keyboard: обычная клавиатура
* - inline_keyboard: встроенная клавиатура
* - remove_keyboard: удалить клавиатуру
* - force_reply: форсировать ответ
*
* @example
* ```ts
* // Простое сообщение
* await telegram.sendMessage(12345, 'Привет!');
*
* // Форматированное сообщение
* await telegram.sendMessage(12345,
* '<b>Жирный</b> и <i>курсив</i>\n' +
* '<a href="http://localhost">Ссылка</a>\n' +
* '<code>code</code>',
* { parse_mode: 'HTML' }
* );
*
* // Сообщение с обычной клавиатурой
* const keyboard = {
* keyboard: [[
* { text: 'Кнопка 1' },
* { text: 'Кнопка 2' }
* ]],
* resize_keyboard: true
* };
* await telegram.sendMessage(12345, 'Выберите:', {
* reply_markup: JSON.stringify(keyboard)
* });
*
* // Сообщение с inline-клавиатурой
* const inlineKeyboard = {
* inline_keyboard: [[{
* text: 'Кнопка',
* callback_data: 'button_1'
* }]]
* };
* await telegram.sendMessage(12345, 'Нажмите:', {
* reply_markup: JSON.stringify(inlineKeyboard)
* });
* ```
*
* @returns Информация об отправленном сообщении или null при ошибке
*/
sendMessage(chatId, message, params = null) {
const safeMessage = this.#sanitizeTelegramMessage(message, params?.parse_mode);
this.#request.post = {
chat_id: chatId,
text: safeMessage,
};
if (params) {
this.#request.post = { ...params, ...this.#request.post };
}
return this.call('sendMessage');
}
/**
* Отправляет опрос
* @param chatId ID чата или пользователя
* @param question Текст вопроса
* @param options Массив вариантов ответов (2-10 вариантов)
* @param params Дополнительные параметры:
* - is_anonymous: анонимный опрос (по умолчанию true)
* - type: тип опроса
* - 'regular': обычный опрос (по умолчанию)
* - 'quiz': викторина с одним правильным ответом
* - allows_multiple_answers: разрешить несколько ответов (только для regular)
* - correct_option_id: ID правильного ответа (0-9, только для quiz)
* - explanation: пояснение правильного ответа (только для quiz)
* - explanation_parse_mode: формат пояснения (HTML/Markdown)
* - open_period: время в секундах, когда опрос активен
* - close_date: дата закрытия опроса (Unix timestamp)
* - is_closed: закрыть опрос сразу
*
* @example
* ```ts
* // Обычный опрос
* await telegram.sendPoll(12345,
* 'Любимый цвет?',
* ['Красный', 'Синий', 'Зеленый'],
* { allows_multiple_answers: true }
* );
*
* // Викторина
* await telegram.sendPoll(12345,
* 'Столица России?',
* ['Санкт-Петербург', 'Москва', 'Новосибирск'],
* {
* type: 'quiz',
* correct_option_id: 1,
* explanation: 'Москва - столица России с 1918 года',
* explanation_parse_mode: 'HTML'
* }
* );
* ```
*
* @returns Информация об отправленном опросе или null при ошибке
*/
sendPoll(chatId, question, options, params = null) {
this.#request.post = {
chat_id: chatId,
question,
};
let isSend = true;
if (options) {
const countOptions = options.length;
if (countOptions > 1) {
if (countOptions > 10) {
this.#request.post.options = options.slice(0, 10);
}
else {
this.#request.post.options = options;
}
}
else {
isSend = false;
}
}
if (isSend) {
if (params) {
this.#request.post = { ...params, ...this.#request.post };
}
return this.call('sendPoll');
}
else {
this.#log('sendPoll() Указано недостаточное количество вариантов. Платформа ожидает от 2 - 10 вариантов, указано ' +
(options?.length || 0));
return null;
}
}
/**
* Отвечает на callback-запрос от инлайн-кнопки.
* @param callbackQueryId - ID запроса из поля callback_query.id
* @param text - Текст уведомления (показывается всплывающим окном)
* @param showAlert - Показывать как alert (true) или всплывающее уведомление (false)
* @param url - URL для открытия после нажатия
* @param cacheTime - Время кэширования ответа (сек)
*/
async answerCallbackQuery(callbackQueryId, text, showAlert, url, cacheTime) {
this.#request.post = {
callback_query_id: callbackQueryId,
text,
show_alert: showAlert,
url,
cache_time: cacheTime,
};
return this.call('answerCallbackQuery');
}
/**
* Отправляет фотографию
* @param userId ID чата или пользователя
* @param file Путь к файлу или его содержимое
* Поддерживаемые форматы:
* - JPEG, JPG, PNG, GIF, WEBP
* - Максимальный размер: 10MB
* - Максимальное разрешение: 10000x10000
* @param desc Подпись к фотографии
* @param params Дополнительные параметры:
* - caption: подпись к фото (0-1024 символа)
* - caption: подпись к фото
* - parse_mode: формат текста
* - disable_notification: отключить уведомление
* - reply_to_message_id: ID сообщения для ответа
* - reply_markup: клавиатура в JSON
* @returns Информация об отправленной фотографии или null при ошибке
*/
async sendPhoto(userId, file, desc = null, params = null) {
this.#request.post ??= {};
await this.#initPostFile('photo', file);
if (desc) {
this.#request.post.caption = desc;
}
if (params) {
this.#request.post = { ...params, ...this.#request.post };
}
return this.call('sendPhoto', userId);
}
/**
* Отправляет документ
* @param userId ID чата или пользователя
* @param file Путь к файлу или его содержимое
* @param params Дополнительные параметры:
* - caption: подпись к документу
* - parse_mode: формат текста
* - disable_notification: отключить уведомление
* - reply_to_message_id: ID сообщения для ответа
* - reply_markup: клавиатура в JSON
* @returns Информация об отправленном документе или null при ошибке
*/
async sendDocument(userId, file, params = null) {
await this.#initPostFile('document', file);
if (params) {
this.#request.post = { ...params, ...this.#request.post };
}
return this.call('sendDocument', userId);
}
/**
* Отправляет аудиофайл
* @param userId ID чата или пользователя
* @param file Путь к файлу или его содержимое
* @param params Дополнительные параметры:
* - caption: подпись к аудио
* - parse_mode: формат текста
* - duration: длительность в секундах
* - performer: исполнитель
* - title: название
* - disable_notification: отключить уведомление
* - reply_to_message_id: ID сообщения для ответа
* - reply_markup: клавиатура в JSON
* @returns Информация об отправленном аудио или null при ошибке
*/
async sendAudio(userId, file, params = null) {
await this.#initPostFile('audio', file);
if (params) {
this.#request.post = { ...params, ...this.#request.post };
}
return this.call('sendAudio', userId);
}
/**
* Отправляет видео
* @param userId ID чата или пользователя
* @param file Путь к файлу или его содержимое
* @param params Дополнительные параметры:
* - caption: подпись к видео
* - parse_mode: формат текста
* - duration: длительность в секундах
* - width: ширина
* - height: высота
* - disable_notification: отключить уведомление
* - reply_to_message_id: ID сообщения для ответа
* - reply_markup: клавиатура в JSON
* @returns Информация об отправленном видео или null при ошибке
*/
async sendVideo(userId, file, params = null) {
await this.#initPostFile('video', file);
if (params) {
this.#request.post = { ...params, ...this.#request.post };
}
return this.call('sendVideo', userId);
}
/**
* Отправляет группу медиа
* @param userId ID чата или пользователя
* @param media Массив объектов ITelegramMedia
* @param params Дополнительные параметры:
*/
async sendMediaGroup(userId, media, params = null) {
await this.#initPostFile('media', media);
if (params) {
this.#request.post = { ...this.#request.post, ...params };
}
return this.call('sendMediaGroup', userId);
}
/**
* Записывает информацию об ошибках в лог-файл
* @param error Текст ошибки для логирования
*
*/
#log(error = '') {
this.#appContext.logError((0, constants_2.getErrorMsg)(error, 'TelegramRequest', this.#request.url), {
error: this.#error,
});
}
}
exports.TelegramRequest = TelegramRequest;