Telegram бот на Grammy JS с функцией ПРЕДЛОЖКИ | Telegram bot обратной связи!

Для нужд своего своего канала я создал Telegram бота на grammY JS для получения обратной связи, в этой статье я поэтапно расскажу про создание такого Telegram бота и его функциональность!

Telegram бот на Grammy JS
Telegram бот на Grammy JS

1) Первым делом необходимо получить в Telegram токен для работы нашего бота, для этого необходимо:

- Откройте Telegram и найдите @BotFather в поиске.

- Отправьте ему команду /newbot.

- Бот @BotFather предложит выбрать имя и уникальное имя пользователя для вашего бота.

- Получите ключ — токен для управления ботом.

- Теперь ваш бот готов к разработке!

2) Для разработки нам понадобятся node.js и npm. Проверить наличие пакетов в системе можно с помощью следующих команд:

node -v
npm -v

3) Далее откроем в редакторе кода папку, в которой будем создавать проект. Затем в терминале инициализируем его с помощью npm.

npm init -y

4) Теперь необходимо подключить три библиотеки. Сначала основную - grammY JS, а затем две вспомогательные. Библиотека dotenv используется для хранения токена в качестве переменной окружения (env variable), а nodemon - для автоматического перезапуска кода бота после внесения изменений. Все они могут быть установлены одной командой.

npm i grammy dotenv nodemon

5) Код проекта:

Создадим переменные окружения в отдельном файле .env:

BOT_API_KEY=test ADMIN_ID=test

Теперь открываем index.js (в нем будет наш основной код) и вставляем строки:

require('dotenv').config(); const { Bot, GrammyError, HttpError, Keyboard, InlineKeyboard } = require('grammy'); const fs = require('fs'); // Создание экземпляра бота const bot = new Bot(process.env.BOT_API_KEY); // Файл, в котором будут храниться данные о пользователях const userDataFile = 'userData.json'; // Проверяем существование файла userData.json и создаем его, если он не существует if (!fs.existsSync(userDataFile)) { fs.writeFileSync(userDataFile, '{}'); } let userData = JSON.parse(fs.readFileSync(userDataFile));

Настройка команд и кнопок:

// Начнем с команды /start, которая будет вызываться при первом запуске бота. bot.command('start', async (ctx) => { if (!userData[ctx.from.id]) { // Обновляем данные о пользователе при первом запуске updateUserData(userDataFile, ctx.from.id); } const startKeyboard = new Keyboard() .text('🙋‍♂️ Предложка') .row() .text('📲 Социальные сети') .row() .text('🔥 Промокоды и скидки') .row() await ctx.reply( 'Привет! Я бот помошник канала Техноманьяк!', ); await ctx.reply('С чего начнем? Выбирай 👇', { reply_markup: startKeyboard, }); });
// Обработка команды администратора bot.command('admin', async (ctx) => { // Проверяем, является ли пользователь администратором if (isAdmin(ctx.from.id, process.env.ADMIN_ID)) { // Если пользователь администратор, отправляем статистику использования бота let totalStarts = 0; for (const userId in userData) { totalStarts += userData[userId].timesStarted; } await ctx.reply(`Статистика использования бота:\nВсего запусков: ${totalStarts}`); } else { await ctx.reply('У вас нет прав администратора!'); } });

Обработка пользовательских действий:

// Обработчик нажатий на кнопку "Социальные сети" bot.hears('📲 Социальные сети', async (ctx) => { const socialKeyboard = createKeyboard(socialNetworks); suggestionClicked[ctx.from.id] = false; await ctx.reply('Выберите социальную сеть:', { reply_markup: socialKeyboard, }); }); // Обработчик нажатий на кнопку "Промокоды и скидки" bot.hears('🔥 Промокоды и скидки', async (ctx) => { const promoKeyboard = createKeyboard(promoCodes); suggestionClicked[ctx.from.id] = false; await ctx.reply('Выберите категорию промокодов и скидок:', { reply_markup: promoKeyboard, }); });

Обработка предложений от пользователей:

// Обработчик команды "Предложка" bot.hears('🙋‍♂️ Предложка', async (ctx) => { suggestionClicked[ctx.from.id] = true; await ctx.reply('Опишите ваше предложение или сообщение, которое вы хотели бы отправить автору бота.'); }); // Обработчик всех текстовых сообщений bot.on('message', async (ctx) => { const authorId = process.env.ADMIN_ID; if (suggestionClicked[ctx.from.id]) { // Пересылаем сообщение от пользователя автору бота if (ctx.message.text) { await ctx.forwardMessage(authorId, { text: ctx.message.text }); } else if (ctx.message.voice) { await ctx.forwardMessage(authorId, { voice: ctx.message.voice }); } else if (ctx.message.photo) { await ctx.forwardMessage(authorId, { photo: ctx.message.photo }); } else if (ctx.message.video) { await ctx.forwardMessage(authorId, { video: ctx.message.video }); } else { await ctx.forwardMessage(authorId, ctx.message); } await ctx.reply('Ваше сообщение успешно отправлено автору бота'); } else { await ctx.reply('Пожалуйста, сначала нажмите кнопку "Предложка" для отправки сообщения автору канала!'); } });

Вынесение вспомогательных функций в отдельный файл:

Создадим файл buttons.js:

// Массив кнопок для каждой социальной сети const socialNetworks = [ { name: 'YouTube', url: 'https://www.youtube.com/@tehno.maniak', type: 'social' }, { name: 'Telegram', url: 'https://t.me/tehnomaniak07', type: 'social' }, { name: 'Vk', url: 'https://vk.com/public212223166', type: 'social' }, { name: 'ДЗЕН', url: 'https://dzen.ru/filimonov-blog.ru', type: 'social' }, { name: 'TikTok', url: 'https://www.tiktok.com/@texno_maniak', type: 'social' }, { name: 'X', url: 'https://twitter.com/F1L_zZz', type: 'social' }, { name: 'Instagram', url: 'https://www.instagram.com/tehnomaniak_blog/', type: 'social' }, { name: 'Boosty', url: 'https://boosty.to/tehnomaniak', type: 'social' }, ]; // Массив кнопок для каждой категории промокодов и скидок const promoCodes = [ { name: '', url: '', code: 'tehnomaniak', description: '1 месяц в подарок при оплате сервера на 1 год', type: 'promo' }, { name: '', url: '', code: 'super', description: '3 месяца в подарок при оплате сервера на 2 года', type: 'promo' }, { name: '', url: '', code: 'Не нужен, скидка предоставляет при переходе по ссылке', description: 'Скидка 7% на курсы Яндекс Практикум', type: 'promo' }, ]; module.exports = { socialNetworks, promoCodes };

Для вспомогательных функций создадим файл helpers.js:

//Вспомогательные функции const fs = require('fs'); const { Keyboard } = require('grammy'); // Функция для обновления данных о пользователе function updateUserData(userDataFile, userId) { let userData = JSON.parse(fs.readFileSync(userDataFile)); if (!userData[userId]) { userData[userId] = { timesStarted: 0, }; } userData[userId].timesStarted++; fs.writeFileSync(userDataFile, JSON.stringify(userData, null, 2)); } // Функция для проверки, является ли пользователь администратором function isAdmin(userId, adminId) { return userId.toString() === adminId; } // Функция для создания клавиатуры с кнопками и кнопкой "Назад" function createKeyboard(buttons) { const keyboard = new Keyboard(); const buttonCount = buttons.length; // Вычисляем количество кнопок в каждой колонке const buttonsPerColumn = Math.ceil(buttonCount / 2); for (let i = 0; i < buttonsPerColumn; i++) { // Добавляем кнопку в первую колонку const index1 = i; keyboard.text(buttons[index1].name); // Проверяем, есть ли кнопка для второй колонки const index2 = i + buttonsPerColumn; if (index2 < buttonCount) { // Добавляем кнопку во вторую колонку keyboard.text(buttons[index2].name); } // Переходим на следующую строку keyboard.row(); } // Добавляем кнопку "Назад" keyboard.text('Назад ↩️'); return keyboard; } module.exports = { updateUserData, isAdmin, createKeyboard };

Опробовать бота в действии можно по ссылке!

Код проекта целиком доступен у меня на GitHub.

Инструкция как выполнить деплой Telegram бота на сервер, ссылка.

33
3 комментария

Этот бот работает по вебхукам или по методу поллинга?

Конкретно этот — поллит, для хуков grammy нужен отдельный веб-сервер. https://grammy.dev/guide/deployment-types#how-to-use-webhooks

1

readFileSync отличная функция для высоконагруженных систем