UNPKG

umbot

Version:

Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф

470 lines (469 loc) 19 kB
"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('&', '&amp;') .replaceAll('<', '&lt;') .replaceAll('>', '&gt;') .replaceAll('"', '&quot;') .replaceAll("'", '&#39;'); } 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;