Привет, друзья!
В данном туториале мы разработаем простого бота для Telegram. Сначала зарегистрируем и кастомизируем бота с помощью BotFather, затем напишем для него сервер на Express, развернем сервер на Heroku и подключим бота к серверу с помощью веб-хука.
Функционал бота будет следующим:
- в ответ на сообщение joke возвращается программистская шутка, например: "Algorithm: a word used by programmers when they don't want to explain how their code works." (Алгоритм — это слово, используемое программистами, когда они не хотят объяснять, как работает их код));
- в ответ на сообщение, представляющее собой дату в формате ДД.ММ, возвращается либо список дел, запланированных на эту дату в таблице Google (массив объектов), либо фраза "You have nothing to do on this day.", если на эту дату не запланировано никаких дел;
- в ответ на любое другое сообщение возвращается фраза "I have nothing to say.".
При разработке бота я буду опираться в основном на официальную документацию.
- Репозиторий с кодом сервера для бота.
- Бот — @aio350_reminder_bot.
Если вам это интересно, прошу под кат.
Регистрация и кастомизация бота
Для регистрации бота нужен только Telegram (я буду использовать десктопную версию). Находим в нем BotFather (BotFather):
Нажимаем на Start и получаем список команд:
Выполняем команду /newbot для создания бота, указываем имя и username бота (username должно быть уникальным в пределах Telegram), например: "Neo" и "aio350_reminder_bot".
Получаем токен доступа: 5372263544:...
Выполняем команду /mybots для получения списка наших ботов, выбираем только что созданного бота и нажимаем Edit Bot:
Добавляем боту описание (Edit Description), характеристику (Edit About) и аватар (Edit Botpic):
Аватар (спасибо FlatIcon):
Отлично, мы зарегистрировали бота в Telegram и кастомизировали его. Поздороваемся с ним:
Бот молчит, потому что у него пока нет "мозгов") Давайте это исправим.
Разработка и деплой сервера для бота
Создаем директорию, переходим в нее и инициализируем Node.js-проект:
mkdir telegram-bot-server
cd telegram-bot-server
yarn init -yp
# or
npm init -y
Устанавливаем зависимости:
# производственные зависимости
yarn add axios dotenv express fs-extra google-spreadsheet
# зависимость для разработки
yarn add -D nodemon
- axios: клиент-серверная утилита для выполнения HTTP-запросов;
- dotenv: утилита для работы с переменными среды окружения;
- express: Node.js-фреймворк для разработки веб-серверов;
- fs-extra: расширенный Node.js-модуль fs;
- google-spreadsheet: пакет для работы с гугл-таблицами;
- nodemon: утилита для запуска сервера для разработки.
Определяем тип кода сервера (модуль) и команды для запуска сервера в производственном режиме (start) и режиме для разработки (dev) в файле package.json:
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
Создаем файл .env и записываем туда токен доступа:
TELEGRAM_API_TOKEN=5348751300:...
Прежде, чем приступать к разработке сервера, необходимо настроить гугл-таблицу, в которой будут храниться наши задачи.
Создаем таблицу в Google Spreadsheets следующего содержания:
Извлекаем идентификатор таблицы из адресной строки (набор символов между d/ и /edit) и записываем его в .env:
GOOGLE_SPREADSHEET_ID=1HG60...
Идем в Google Cloud Platform, переходим в раздел IAM & Admin -> Service Accounts и создаем сервис-аккаунт (Create Service Account), например: Telegram Bot Spreadsheet.
Выбираем созданный сервис-аккаунт, переходим в раздел Keys и генерируем ключ (Add Key -> Create new key) в формате JSON:
В скачанном JSON-файле нас интересуют поля client_email и private_key. Записываем значения этих полей в .env:
GOOGLE_SERVICE_ACCOUNT_EMAIL=telegram-bot-spreadsheet@telegram-bot-spreadsheet.iam.gserviceaccount.com
GOOGLE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----
Переходим в раздел APIs & Services -> Enabled APIs & services и подключаем интерфейс таблиц с сервис-аккаунту (Enable APIs and Services):
Обратите внимание: аналогичным образом можно настроить доступ к гугл-календарю (только для работы с ним потребуется другой пакет, например, @googleapis/calendar).
Теперь можно вернуться к разработке сервера.
Создаем файл index.js и импортируем зависимости:
import axios from 'axios'
import { config } from 'dotenv'
import express from 'express'
import { GoogleSpreadsheet } from 'google-spreadsheet'
Получаем доступ к переменным среды окружения, создаем экземпляр приложения Express и определяем пути к шуткам и телеграмму:
config()
const app = express()
const JOKE_API = 'https://v2.jokeapi.dev/joke/Programming?type=single'
const TELEGRAM_URI = `https://api.telegram.org/bot${process.env.TELEGRAM_API_TOKEN}/sendMessage`
Подключаем посредников (middleware) Express и инициализируем таблицу:
app.use(express.json())
app.use(
express.urlencoded({
extended: true
})
)
const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID)
await doc.useServiceAccountAuth({
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, '\n')
})
Определяем роут для POST-запроса к /new-message:
app.post('/new-message', async (req, res) => {
// ...
})
Извлекаем сообщение из тела запроса и проверяем, что сообщение содержит текст и идентификатор чата:
const { message } = req.body
const messageText = message?.text?.toLowerCase()?.trim()
const chatId = message?.chat?.id
if (!messageText || !chatId) {
return res.sendStatus(400)
}
Получаем данные из таблицы и формируем данные для ответа:
await doc.loadInfo()
const sheet = doc.sheetsByIndex[0]
const rows = await sheet.getRows()
const dataFromSpreadsheet = rows.reduce((obj, row) => {
if (row.date) {
const todo = { text: row.text, done: row.done }
obj[row.date] = obj[row.date] ? [...obj[row.date], todo] : [todo]
}
return obj
}, {})
Формируем текст ответа:
let responseText = 'I have nothing to say.'
if (messageText === 'joke') {
try {
const response = await axios(JOKE_API)
responseText = response.data.joke
} catch (e) {
console.log(e)
res.send(e)
}
} else if (/\d\d\.\d\d/.test(messageText)) {
responseText =
dataFromSpreadsheet[messageText] || 'You have nothing to do on this day.'
}
И отправляет ответ:
try {
await axios.post(TELEGRAM_URI, {
chat_id: chatId,
text: responseText
})
res.send('Done')
} catch (e) {
console.log(e)
res.send(e)
}
Наконец, определяем порт и запускаем сервер:
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
Бот не сможет взаимодействовать с сервером, запущенным локально, поэтому сервер необходимо где-нибудь развернуть, например, на Heroku.
Создаем там новое приложение, например: my-telegram-bot-server (название должно быть уникальным в пределах Heroku):
Глобально устанавливаем Heroku CLI и авторизуемся:
yarn global add heroku
heroku login
# далее следуем инструкциям
Находясь в корневой директории проекта, выполняем инициализацию Git-репозитория, подключаемся к Heroku, добавляем и фиксируем изменения и отправляем их в Heroku (не забудьте создать файл .gitignore с node_modules и .env):
git init
# у вас название проекта будет другим
heroku git:remote -a my-telegram-bot-server
git add .
git commit -m "create app"
git push heroku master
После деплоя получаем URL приложения, например: https://my-telegram-bot-server.herokuapp.com/ (он нам еще пригодится).
Открываем вкладку Settings на странице приложения и добавляем переменные среды окружения в разделе Config Vars (Reveal Config Vars):
Отлично, на этом с разработкой и деплоем сервера мы закончили.
Осталось подключить к нему бота.
Подключение бота к серверу
Существует несколько способов подключения бота к серверу, но самым простым является использование веб-хука.
Открываем терминал и выполняем следующую команду:
# в строке ("url=...") указываем `URL` проекта + `/new-message`
# после `bot` указываем токен доступа из переменной `TELEGRAM_API_TOKEN`
curl -F "url=https://my-telegram-bot-server.herokuapp.com/new-message" https://api.telegram.org/bot5372263544:.../setWebhook
Получаем сообщение об успешной установке хука.
Возвращаемся к боту.
Нажимаем /start, получаем от бота сообщение "I have nothing to say.". Отправляем сообщение "joke", получаем шутку. Отправляем "11.05", получаем задачи в виде массива объектов. Отправляем "12.05", получаем "You have nothing to do on this day.".
Примечание: при разработке бота я немного опечатался (вместо "to do" написал "to to"), но уже исправился.
It's alive!)
Пожалуй, это все, чем я хотел поделиться с вами в этой статье.
Надеюсь, вы нашли для себя что-то интересное и не зря потратили время.
Благодарю за внимание и happy coding!
gnome2_terminal_is_best
Ссылку на репозиторий с кодом, нужно пофиксить, нужно указать конктретную ветку, после названия репозитория.
aio350 Автор
Спасибо, друг.