Telegram бот в доспехах или разработка ТГ бота с нуля.
Всем мир! Сегодня я вам на практике покажу, как с 0 написать несложного Telegram бота.
Для начала придумаем тестовое задание.
У меня есть сервер Counter Strike 1.6 на котором я временами играю с друзьями. Заходить в игру, чтобы проверить наличие там игроков, лень. Напишем бота, который будет доставать для нас информацию о сервере и список игроков со счетом и выводить в удобном формате.
Для того, чтобы проследить за действиями, достаточно базовых знаний linux, web и python.
Постановка задачи
Для работы телеграм бот, который крутится на нашей машине, должен получать обновления от сервера Telegram. Это можно сделать двумя методами:
- Long polling. Метод, когда ваша программа в определенный промежуток времени опрашивает сервер об обновлениях.
- Web hook. Тут вы поднимаете веб сервер, на который телеграм бот шлет обновления, если они есть.
Второй метод популярнее и считается надежнее, поэтому остановимся на нем.
Писать будем на Python с использованием Flask. В роли веб сервера используем nginx. В работе с сервером CS нам поможет чудесный модуль python-valve
Выбор сервера для хостинга нашего бота роли не играет, я буду работать с Digital Ocean.
Сам бот будет довольно простым: одна кнопка Обновить, нажатие на которую, как и любое сообщение, будет обновлять информацию о карте и игроках.
Регистрация бота
Первым делом нам надо зарегистрировать своего бота у @BotFather. Он отвечает за управление информацией о боте и получение и обновление токенов для работы с ними. От него мы получим такую строку
"Use this token to access the HTTP API:
344036403:AAE0JMeFjn97OiaTLzJy_oGKC4zEWuQuSYQ"
Это ключ по которому сервера телеграм будут знать, что вы владельцы этого бота. Если вы его потеряете, вы можете получить новый у того же @BotFather.
Первый шаг сделали. Мы уже сейчас можем найти нашего бота в ТГ и писать ему, но это пока только оболочка и отвечать вам не будет. Ему нужен код/призрак.
Аренда выделенного сервера на DO
Как и любой сайт и серверсайд программа, наш код должен быть запущен на машине с белым ip и выходом в интернет. Для такого случая мы арендуем машину у Ditial Ocean
Показывать пошаговую регистрацию на сайте я не буду, покажу только как получить и настроить сам сервер. Кстати, если вы студент, можете попробовать получить 50$ на хостинг бесплатно.
Нажимаем на большую копку Create droplet.
Выбираем желаемую ОС. Я буду работать с Ubuntu.
Дальше можно выбрать самый дешевый сервер за 5 баксов в месяц или 0.007 в час. С выбором региона чуть тяжелее. Для телеграм бота особой разницы нет, но если будете хостить игры тут, следует подумать о пингах. Тут можно сравнивать состояние от вас, до разных регионов DO. http://speedtest-sfo1.digitalocean.com/
Больше нам ничего менять тут не надо, нажимаем Create.
В течении пары минут мы получим ip адрес и root пароль от сервера на нашу почту. Все, машина работает. Можем коннектиться к ней по ssh.
ssh root@%ваш_ip_адрес
Вводим пароль и получаем полный контроль над сервером. Возможно вас попросят сменить пароль, поставьте посложнее.
Первым делом нам сразу надо создать нового пользователя и входить на сервер из под него, чтобы не стать легкой мишенью для хакеров.
adduser gorec # Добавляем пользователя. Вводим данные, которые нас запросит система
sudo usermod -aG sudo gorec # Даем юзеру возможность исполнять команды от имени админа
Теперь необходимо перезайти на сервер под новым пользователем
ssh gorec@ваш_ip_адрес
Установка нужных сервисов
sudo apt update
sudo apt install python-pip python-dev nginx
sudo service nginx start
Первая команда обновит локальные репозитории, чтобы не было проблем с поиском нужных пакетов. Вторая поставит PIP (систему управления пакетами python), веб сервер nginx и dev пакет python. Третья команда запустит веб сервер.
Если вы сейчас введете ip адрес вашего сервера в браузере любого компьютера подключенного к сети интернет, вы получите страницу приветствия nginx.
Выключим на пока наш веб сервер и займемся python.
sudo service nginx stop
Python и virtualenv
По хорошему, нам надо создавать окружения для каждого из наших python проектов, чтобы их зависимости не начали конфликтовать между собой. Для этого воспользуемся virtualenv.
sudo pip install virtualenv
Создаем папку проекта в домашней и стартуем в ней новое изолированное окружение.
cd ~
mkdir CSBot
cd CSBot
virtualenv csbot
source csbot/bin/activate
Теперь у нас есть локальная копия python, pip и все python модули установленные внутри останутся в этом окружении.
Устанавливем Flask и UWSGI
Бота будем писать на микрофреймворке flask, который позволит нам сократит время на разработку. Для перенаправления запросов с nginx на flask будем пользоваться uwsgi.
pip install uwsgi flask
Для начала запустим простую страничку в Flask и попробуем достучаться к ней через nginx.
В папке с проектом создадим наш главный файл в котором будет хранится код.
# file - ~/CSBot/csbot.py
from flask import Flask # Импортируем модули
app = Flask(__name__) # Создаем приложение
@app.route("/") # Говорим Flask, что за этот адрес отвечает эта функция
def hello_world():
return "It's working"
Так же напишем скрипт, который будет запускать наше Flask приложение.
# file - ~/CSBot/wsgi.py
from csbot import app # Импортируем наше приложение
if __name__ == "__main__":
app.run() # запускаем его
Оба файла находятся в папке с проектом.
Запускам uwsgi командой:
cd ~/CSBot
uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app
Если все прошло успешно, вы, перейдя в своем браузере по адресу вашего сервера и порт 5000 должны увидеть It’s working.
Теперь напишем конфиг файл, чтобы Flask приложение поднималось само, даже если сервер перезагрузится. Сначала выйдем из нашего окружения.
deactivate
Потом создадим .ini конфиг для uwsgi в папке с ботом по пути~/CSBot/csbot.ini
.
Конфиг задает количество процессов, имя сокета, права и файл для логирования.
[uwsgi]
module = wsgi:app
master = true
processes = 5
socket = csbot.sock
chmod-socket = 660
vacuum = true
die-on-term = true
logto = /var/log/uwsgi/%n.log
Так же создадим папку для логов и сделаем ее владельцем себя.
sudo mkdir /var/log/uwsgi
sudo chown -R gorec:gorec /var/log/uwsgi
Создадим так же systemd unit файл, для атвоматизации запуска нашего бота.
sudo nano /etc/systemd/system/csbot.service
и сам файл
[Unit]
Description=Serving our csbot
After=network.target
[Service]
User=gorec
Group=www-data
WorkingDirectory=/home/gorec/CSBot
Environment="PATH=/home/gorec/CSBot/csbot/bin"
ExecStart=/home/gorec/CSBot/csbot/bin/uwsgi --ini csbot.ini
Теперь после ввода команды, мы должны увидеть сокет файл в нашей папке проекта.
sudo service csbot start
sudo systemctl enable csbot
Мы так же можем проверить работу с помощью просмотра логов в папке и статуса сервиса.
sudo service csbot status
less /var/log/uwsgi/csbot.log
Настройка nginx
Дальше нам необходимо настроить веб сервер, чтобы свои запросы он направлял в сокет uwsgi.
sudo nano /etc/nginx/sites-available/csbot
server {
listen 80;
server_name ваш_айпи;
location / {
include uwsgi_params;
uwsgi_pass unix:/home/gorec/CSBot/csbot.sock;
}
}
Теперь нам надо сделать ссылку на конфиг, в папке sites-enabled
sudo ln -s /etc/nginx/sites-available/csbot /etc/nginx/sites-enabled
Мы можем проверить наши конфиг файлы с помощью nginx.
sudo nginx -t
Если он сказал, что все ОК, перезапускаем веб сервер.
sudo service nginx restart
Теперь по ip адресу нашего сервера в браузере вы должны получить выдачу Flask приложения.
Включаем https
Телеграм бот не станет работать по обычному протоколу http т.к. это ставит под угрозу переписку. У нас нет домена на сервере, будем работать с самоподписным сертификатом.
Сначала генерируем наш сертификат и ключ с помощью openssl.
cd /etc/ssl/
sudo mkdir gorec
cd gorec
sudo openssl genrsa -des3 -passout pass:xxxx -out server.pass.key 2048
sudo openssl rsa -passin pass:xxxx -in server.pass.key -out server.key
rm server.pass.key
sudo openssl req -new -key server.key -out server.csr
На последней команде openssl задаст вам несколько вопросов. Можно игнорировать все, кроме Common Name (FQDN). В нем следует указать ip вашего сервера.
Следующей командой мы получим сертификат из сгененрированного выше приватного ключа.
cd /etc/ssl/gorec
sudo openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
Так-с, серты сгенерировали. Идем к настройке nginx.
Открываем конфиги нашего сайта в nginx:
sudo nano /etc/nginx/sites-available/csbot
Указвыаем новый порт взамен 80. Указвыаем ключи и сертификаты и протоколы, которые мы можем хендлить.
server {
listen 443 default ssl;
server_name ваш_ип;
keepalive_timeout 60;
ssl_certificate /etc/ssl/gorec/server.crt;
ssl_certificate_key /etc/ssl/gorec/server.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "HIGH:!RC4:!aNULL:!MD5:!kEDH";
add_header Strict-Transport-Security 'max-age=604800';
access_log /var/log/nginx_access.log;
error_log /var/log/ngingx_error.log;
location / {
include uwsgi_params;
uwsgi_pass unix:/home/gorec/CSBot/csbot.sock;
}
}
Проверяем конфиги и перезапускаем веб сервер
sudo nginx -t
sudo service nginx restart
Теперь в браузере по https://ваш_ип, вы должны увидеть вашу страницу flask приложения. Браузер будет ругаться на сертификат, игнорируйте.
Пишем код для бота
Дошли до самого интересного.
Сначала установим модуль для работы с Telegram - python-telegram-bot
cd ~/CSBot
source csbot/bin/activate
sudo pip install python-telegram-bot
Теперь напишем код бота (~/CSBot/csbot.py
), который всегда отвечает словом hello на любое наше сообщение.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from flask import Flask, request
import telegram
app = Flask(__name__)
app.debug = True
TOKEN = "ваш_токен"
global bot
bot = telegram.Bot(token=TOKEN)
URL = 'ваш_ip'
#WebHook
@app.route('/HOOK', methods=['POST', 'GET'])
def webhook_handler():
if request.method == "POST":
update = telegram.Update.de_json(request.get_json(force=True), bot)
try:
chat_id = update.message.chat.id
text = update.message.text
userid = update.message.from_user.id
username = update.message.from_user.username
bot.send_message(chat_id=chat_id, text="hello")
except Exception, e:
print e
return 'ok'
#Set_webhook
@app.route('/set_webhook', methods=['GET', 'POST'])
def set_webhook():
s = bot.setWebhook('https://%s:443/HOOK' % URL, certificate=open('/etc/ssl/server.crt', 'rb'))
if s:
print(s)
return "webhook setup ok"
else:
return "webhook setup failed"
@app.route('/')
def index():
return '<h1>Hello</h1>'
Разберем код.
В начале мы импортируем нужные модули flask, request, telegram. Дальше мы создаем наше приложение, включаем режим отладки (надо присвоить False, когда закончим разработку).
После мы задаем наш token полученный при создании бота в самом начале, адрес сервера и создаем бота.
У нас есть два главных метода:
*set_webhook - отвечает за создание вебхука с сервером Telegram и вызывается один раз.
*webhook_handler - слушает на /HOOK и занимается обработкой сообщений с серверов телеграма.
В хендлере мы получаем данные в Post запросе, достаем нужную информацию и исходя из этого делаем какие-то действия и шлем сообщение юзеру методом bot.send_message.
Перезапускаем бота и заходим на https://ваш_ип/set_webhook. Если в ответе вы получили webhook setup ok, можно пойти тестить бота.
Давайте чуть усложним его и добавим кнопку “Обновить”.
Для начала импортируем ReplyKeyboardMarkup для создания клавиатуры и после отправим его в сообщении параметром reply_markup.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from flask import Flask, request
from telegram.replykeyboardmarkup import ReplyKeyboardMarkup
import telegram
app = Flask(__name__)
app.debug = True
TOKEN = "ваш_токен"
global bot
bot = telegram.Bot(token=TOKEN)
URL = 'ваш_ip'
#WebHook
@app.route('/HOOK', methods=['POST', 'GET'])
def webhook_handler():
if request.method == "POST":
update = telegram.Update.de_json(request.get_json(force=True), bot)
try:
kb = ReplyKeyboardMarkup([["Обновить"]])
chat_id = update.message.chat.id
text = update.message.text
userid = update.message.from_user.id
username = update.message.from_user.username
bot.send_message(chat_id=chat_id, text="hello", reply_markup=kb)
except Exception, e:
print e
return 'ok'
#Set_webhook
@app.route('/set_webhook', methods=['GET', 'POST'])
def set_webhook():
s = bot.setWebhook('https://%s:443/HOOK' % URL, certificate=open('/etc/ssl/server.crt', 'rb'))
if s:
print(s)
return "webhook setup ok"
else:
return "webhook setup failed"
@app.route('/')
def index():
return '<h1>Hello</h1>'
Немного функционала
Напишем функцию, которая будет опрашивать наш CS сервер и выдавать информацию по нему.
Для начала установим наш модуль для работы с valve серверами. Не забудьте перейти в окружение перед установкой.
cd ~/CSBot
source csbot/bin/activate
pip install python-valve
Теперь в коде определим новую функцию, которая будет генерить строку ответ и возвращать ее нам, для отправки пользователю.
server_address = (адрес_сервера, 27015)
server = valve.source.a2s.ServerQuerier(server_address)
def get_info():
info = server.get_info()
players = server.get_players()
answer = "Players: {player_count}/{max_players} \nServer name: {server_name}\nGame: {game}\n".format(**info)
for player in sorted(players["players"],
key=lambda p: p["score"], reverse=True):
answer += "{name} {score} Dur: {duration}\n".format(**player)
return answer
Код сам по себе не сложен, но давайте разберем. Сначала задаем адрес и порт сервера и создаем объект.
В функции достаем общую информацию и инфу об игроках. Заносим данные в строку answer и проходимся по массиву пользователей добавляя в новую строку данные о солдатах с количеством очков и временем проведенным на сервере. Возвращаем строку ответ. Названия полей (player_count, max_players и остальные можно посмотреть в коде модуля на github https://github.com/Holiverh/python-valve/blob/master/valve/source/a2s.py)
Осталось отправлять полученную строку сообщение пользователю. Для этого правим строку bot.send_message
...
bot.send_message(chat_id=chat_id, text=get_info(), reply_markup=kb)
...
Перезапускаем нашего бота.
sudo service csbot restart
Логи
В случае ошибок мы можем узнать что не так благодаря двум логам:
- Логи веб сервера находятся в /var/log/nginx/
- Логи нашего приложения и uwsgi в /var/log/uwsgi/
Код бота можно найти тут: https://github.com/mrZizik/CSBot
Написать ему можно тут: @csinfobot