Телеграм бот обратной связи на Node.js и Telegraf.js

Простой Телеграм бот для связи с подписчиками и читателями на Node.js. Перепишем существующего бота, который ранее был написан на PHP.

View original


The Wayback Machine — https://web.archive.org/web/20250809061407/https://imakebots.ru/article/telegram-bot-obratnoy-svyazi-na-nodejs-i-telegrafjs

Ранее мной был написан бот обратной связи на PHP, статья про него есть в ленте на сайте. Сейчас я практикуюсь в Node.js и решил переписать бот с использованием «Современного фреймворка для Телеграм Бот на Node.js» это Telegraf.js. Принцип работы бота остался тем же.

Не стал разбивать на отдельные файлы весь код, для наглядности оставил в одном листинге.

В настройках необходимо указать свои параметры, так как бот будет работать через webHook, то нужно указать путь до сертификатов для https соединения.

Ниже приведены 3 варианта с использованием бота через webHook и вариант через getUpdates

* * *

index.js — вариант 1

//////////////////////
////  Запускаем webHook
//////////////////////

// Подключаем модули
let fs = require('fs');
let Telegraf = require('telegraf');

/**
 * Настройки
 * @type token: string - Токен бота
 * @type path: string - относительный путь до директории с сертификатами 
 * @type key: string - приватный ключ 
 * @type cert: string - сертификат сервера
 * @type ca: string - сертификат клиента 
 * @type port: number - порт
 * @type domain: string - домен
 * @type whpath: string - путь
 * @type admin: number - id владельца бота
 */
let config = {
    "token": "YOUR_TOKEN",
    "path": "./certs/",
    "key": "file.key",
    "cert": "file.crt",
    "ca": "file.ca",
    "port": 8443,
    "domain": "domain.com",
    "whpath": "/secret-path",
    "admin": 123456789
};

// Создаем объект Telegraf.js
let bot = new Telegraf(config.token);

// TLS options
let tlsOptions = {
    key: fs.readFileSync(config.path + config.key),
    cert: fs.readFileSync(config.path + config.cert),
    ca: [
        // This is necessary only if the client uses the self-signed certificate.
        fs.readFileSync(config.path + config.ca)
    ]
};

// Установливаем webHook
bot.telegram.setWebhook('https://' + config.domain + ':' + config.port + config.whpath);

// Запускаем https webhook
bot.startWebhook(config.whpath, tlsOptions, config.port);

/**
 * Значения текстовых ответов
 * @type helloAdmin: string - Приветствие для админа
 * @type helloUser: string - Приветствие для пользователя
 * @type replyWrong: string - Если админ написал сам себе, уведомляем его
 */
let replyText = {
    "helloAdmin": "Привет админ, ждем сообщения от пользователей",
    "helloUser":  "Приветствую, отправьте мне сообщение. Постараюсь ответить в ближайшее время.",
    "replyWrong": "Для ответа пользователю используйте функцию Ответить/Reply."
};

/**
 * Проверяем пользователя на права
 * @param userId {number}
 * @returns {boolean}
 */
let isAdmin = (userId) => {
    return userId == config.admin;
};

/**
 *  Перенаправляем админу от пользователя или уведомляем админа об ошибке
 * @param ctx
 */
let forwardToAdmin = (ctx) => {
    if (isAdmin(ctx.message.from.id)) {
        ctx.reply(replyText.replyWrong);
    } else {
        ctx.forwardMessage(config.admin, ctx.from.id, ctx.message.id);
    }
};

/**
 * Старт бота
 */
bot.start((ctx) => {
    ctx.reply(isAdmin(ctx.message.from.id)
        ? replyText.helloAdmin
        : replyText.helloUser);
});


//////////////////////
////  Основа 1
//////////////////////

/**
 * Текст
 */
bot.on('text', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendMessage(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.text
        );
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * Стикер
 */
bot.on('sticker', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendSticker(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.sticker.file_id
        );
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * 1 фотография - не медиагруппа
 */
bot.on('photo', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        let file = ctx.message.photo.length - 1;
        ctx.telegram.sendPhoto(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.photo[file].file_id,
            {
                'caption': ctx.message.caption
            });
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * Документ
 */
bot.on('document', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendDocument(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.document.file_id,
            {
                'caption': ctx.message.caption
            });
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * Голосовая заметка
 */
bot.on('voice', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendVoice(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.voice.file_id,
            {
                'caption': ctx.message.caption
            });
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * Видео заметка
 */
bot.on('video_note', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendVideoNote(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.video_note.file_id
        );
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * 1 видео ролик - не медиагруппа
 */
bot.on('video', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendVideo(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.video.file_id,
            {
                'caption': ctx.message.caption
            }
        );
    } else {
        forwardToAdmin(ctx);
    }
});

/**
 * Аудио ролик
 */
bot.on('audio', (ctx) => {
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        ctx.telegram.sendAudio(
            ctx.message.reply_to_message.forward_from.id,
            ctx.message.audio.file_id,
            {
                'caption': ctx.message.caption
            });
    } else {
        forwardToAdmin(ctx);
    }
});

* * *

index.js — вариант 2

Упрощаем код, ставим прослушку на общий метод Message. В соответствии с подтипом сообщения вызывая нужный метод отправляем сообщение пользователю.

//////////////////////
////  ... Здесь запускаем webHook из первого варианта
//////////////////////

//////////////////////
////  Основа 2
//////////////////////
/**
 * Слушаем на наличие объекта message
 */
bot.on('message', (ctx) => {
    // убеждаемся что это админ ответил на сообщение пользователя
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        // кому отправляем
        let userId = ctx.message.reply_to_message.forward_from.id;
        //  проверяем что пришло и отправляем соответствующим методом
        switch (ctx.updateSubTypes[0]) {
            case 'text':
                ctx.telegram.sendMessage(
                    userId,
                    ctx.message.text
                );
                break;
            case 'sticker':
                ctx.telegram.sendSticker(
                    userId,
                    ctx.message.sticker.file_id
                );
                break;
            case 'photo':
                let file = ctx.message.photo.length - 1;
                ctx.telegram.sendPhoto(
                    userId,
                    ctx.message.photo[file].file_id,
                    {
                        'caption': ctx.message.caption
                    }
                );
                break;
            case 'document':
                ctx.telegram.sendDocument(
                    userId,
                    ctx.message.document.file_id,
                    {
                        'caption': ctx.message.caption
                    }
                );
                break;
            case 'voice':
                ctx.telegram.sendVoice(
                    userId,
                    ctx.message.voice.file_id,
                    {
                        'caption': ctx.message.caption
                    }
                );
                break;
            case 'video_note':
                ctx.telegram.sendVideoNote(
                    userId,
                    ctx.message.video_note.file_id
                );
                break;
            case 'video':
                ctx.telegram.sendVideo(
                    userId,
                    ctx.message.video.file_id,
                    {
                        'caption': ctx.message.caption
                    }
                );
                break;
            case 'audio':
                ctx.telegram.sendAudio(
                    userId,
                    ctx.message.audio.file_id,
                    {
                        'caption': ctx.message.caption
                    }
                );
                break;
            default:
                console.log('other');
       }
    } else {
        forwardToAdmin(ctx);
    }
});

* * *

index.js — вариант 3

Максимально упростим код и используя метод sendCopy — просто отправляем копию сообщения от админа пользователю.

//////////////////////
////  ... Здесь запускаем webHook из первого варианта
//////////////////////

//////////////////////
////  Основа 3
//////////////////////
/**
 * Слушаем на наличие объекта message
 */
bot.on('message', (ctx) => {
    // убеждаемся что это админ ответил на сообщение пользователя
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        // отправляем копию пользователю
        ctx.telegram.sendCopy(ctx.message.reply_to_message.forward_from.id, ctx.message);
    } else {
        // перенаправляем админу
        forwardToAdmin(ctx);
    }
});

* * *

Вариант бота без webHook 

Этот вариант можно запустить без настройки webHook, также не нужны домен и ssl-сертификат. Его можно спокойно запустить на локальной машине, при необходимости можно настроить соединение через прокси.

Файл index.js

// Подключаем модули
const Telegraf = require('telegraf');
// const HttpsProxyAgent = require('https-proxy-agent');
// Общие настройки
let config = {
    "token": "YOUR_TOKEN", // Токен бота
    "admin": 0 // id владельца бота
};
// Создаем объект бота
const bot = new Telegraf(config.token, {
        // Если надо ходить через прокси - укажите: user, pass, host, port
        // telegram: { agent: new HttpsProxyAgent('http://user:pass@host:port') }
    }
);
// Текстовые настройки
let replyText = {
    "helloAdmin": "Привет админ, ждем сообщения от пользователей",
    "helloUser":  "Приветствую, отправьте мне сообщение. Постараюсь ответить в ближайшее время.",
    "replyWrong": "Для ответа пользователю используйте функцию Ответить/Reply."
};
// Проверяем пользователя на права
let isAdmin = (userId) => {
    return userId == config.admin;
};
// Перенаправляем админу от пользователя или уведомляем админа об ошибке
let forwardToAdmin = (ctx) => {
    if (isAdmin(ctx.message.from.id)) {
        ctx.reply(replyText.replyWrong);
    } else {
        ctx.forwardMessage(config.admin, ctx.from.id, ctx.message.id);
    }
};
// Старт бота
bot.start((ctx) => {
    ctx.reply(isAdmin(ctx.message.from.id)
        ? replyText.helloAdmin
        : replyText.helloUser);
});
// Слушаем на наличие объекта message
bot.on('message', (ctx) => {
    // убеждаемся что это админ ответил на сообщение пользователя
    if (ctx.message.reply_to_message
        && ctx.message.reply_to_message.forward_from
        && isAdmin(ctx.message.from.id)) {
        // отправляем копию пользователю
        ctx.telegram.sendCopy(ctx.message.reply_to_message.forward_from.id, ctx.message);
    } else {
        // перенаправляем админу
        forwardToAdmin(ctx);
    }
});
// запускаем бот
bot.launch();

* * *

Файл package.json

{
  "name": "telegramFeedBack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "iMakeBots.ru",
  "license": "",
  "dependencies": {
    "https-proxy-agent": "^2.2.1",
    "telegraf": "^3.26.0"
  }
}