Представьте: вам как Python-разработчику доверили написать бота, но с чего начать?
Это статья из цикла «5 ETL для зоопарка ботов». В нём я пошагово разбираю, как наладить потоки данных из разных библиотек и конструкторов ботов на разных языках и стеках. В основе лежат Python и его библиотеки. Вот предыдущие статьи цикла:
Если Вы уже знаете, как исполнять в коде SQL-запросы, понимаете основы ЯП достаточно, чтобы писать несложные программы, то бот может стать вашим следующим этапом профессионального развития: он позволит закрепить основы и связать их воедино. В дополнение ко всему, автоматизация рабочего процесса станет бонусом вам или вашим коллегам.
Мы получим довольно простого бота, который задаёт будущим студентам курса вовлекающие вопросы и оценивает навык управления проектами. И выводит:
Подключаем aiogram. Это фреймворк, а значит, взаимосвязанных скриптов в проекте немало:
Чтобы сервер понимал, с какого именно файла начать, в main.py добавляем условие if __name__ == ‘__main__’:
main.py:
from aiogram import executor import handlers from loader import dp if __name__ == ‘__main__’: executor.start_polling(dp) Поллинг (start_polling) — базовый способ «выпустить» бота в мир. Существует еще и вебхук-бот, но для его развёртывания требуется доменное имя, которому можно добавить A-запись, так что пока обойдемся простейшим решением.
В loader.py прописываем основные конфиденциальные настройки, которые будет брать бот:
import logging from aiogram import Bot, Dispatcher, types from aiogram.contrib.fsm_storage.memory import MemoryStorage from core import settings bot = Bot(token=settings.tg_token, parse_mode=types.ParseMode.MARKDOWN_V2) storage = MemoryStorage() dp = Dispatcher(bot, storage=storage) logging.basicConfig(level=logging.INFO)
Здесь указано, где брать токен, какой режим парсинга текста использовать и так далее.
Кстати, если вы еще не взаимодействовали с @BotFather и не создали своего бота, то отправьте ему /start, затем /newbot и проследуйте стандартным шагам. Подробнее в этом гайде.
Ведущий аналитик/программист (Отдел планирования и управленческой отчетности) МТС, , можно удалённо, По итогам собеседования tproger.ru Вакансии на tproger.ru
Пишем модуль settings, из которого потом будем брать токен:
settings.py
import os from dotenv import load_dotenv load_dotenv() tg_token = str(os.getenv(‘TELEGRAM_TOKEN’)) TELEGRAM_BOT_NAME = str(os.getenv(‘TELEGRAM_BOT_NAME’)) ADMINS = str(os.getenv(‘BOT_ADMINS’)) DATABASE = str(os.getenv(‘DATABASE’)) SKIP_CHECK_TIME = bool(os.getenv(‘SKIP_CHECK_TIME’))
os.getenv() ссылается на некоторые переменные вроде TELEGRAM TOKEN и DATABASE. Их мы поместим в отдельный локальный файл .env. Разработка и деплой бота ведутся на разных машинах, и это поможет не переписывать его при переходе, например, с macOS на Linux SYSTEM_PATH:
.env
TELEGRAM_TOKEN=<TOKEN> BOT_ADMINS=<ID АДМИНА> DATABASE=./data/db.sqlite3 # Путь до файлов с графиками SYSTEM_PATH=/Users/elenakapatsa/Repositories/tripwire/radar_charts/
Пишем команду /start. Через неё пользователь будет попадать к первому одноименному обработчику, который проверит наличие человека в базе данных и запросит телефон, если человек новый:
start.py
@dp.message_handler(commands=[‘start’], state=None) async def start(message: types.Message): tg_id = message.chat.id # Определяем ID юзера для записи в БД # Если юзер новый, то запрашиваем телефон if not bd.check_user(tg_id): await ask_phone(tg_id, message.chat.first_name) # Если юзер не новый, отправляем его на первый вопрос else: await message.answer(texts.initialize_questionnaire, reply_markup=kb.add_step_keyboard(texts_btn.start_menu))
Функция ask_phone выглядит следующим образом:
start.py
async def ask_phone(tg_id, first_name): phone = kb.send_phone() # Рендерим кнопку “Отправить телефон” name = md.bold(first_name) # Выделяем имя из профиля # Просим юзера показать телефон await bot.send_message(tg_id, f»’Привет, {name}! Для регистрации нажми кнопку Передать номер телефона»’, reply_markup=phone)
Функция get_phone() захватит телефон, имя и Telegram ID и запишет это в базу SQLite:
start.py
@dp.message_handler(content_types=types.ContentTypes.CONTACT) async def get_phone(message: types.Message): phone = message.contact.phone_number name = message.contact.first_name tg_id = message.chat.id # Запишем в базу данные юзера bd.registration(tg_id, phone, name) # Запустим опрос await message.answer(md.text( # Зададим вопрос texts.initialize_questionnaire, sep=’n’), reply_markup=kb.add_step_keyboard(texts_btn.start_menu))
Импортируем необходимые библиотеки. По мере раскомментирования кода станет понятно, для чего нужна каждая из них:
questionnaire.py
from aiogram import md, types from aiogram.dispatcher import FSMContext from aiogram.dispatcher.filters import Text from keyboards import keyboards as kb from loader import dp from states import states as st from texts import numeric_keyboard as nk from texts import text_buttons as tb from texts import texts from texts import questions from utils import bd from plotter_interpreter import * from level_counter import *
Чтобы попасть в саму анкету, пользователь будет нажимать клавишу «Поехали» (первую в списке, то есть нулевую по канонам ЯП) и хэндлер analyst_start примет «это на свой счёт»:
@dp.message_handler(Text(equals=tb.start_menu[0], ignore_case=True), state=’*’) async def analyst_start(message: types.Message): await st.StateQuestionnaire.question1.set() await message.answer(questions.team_problems, reply_markup=kb.add_step_keyboard(nk.numeric_keyboard[:6]))
База SQLite выглядит так (для просмотра пользуюсь DB Browser for SQLite):
Чтобы сохранить простоту восприятия, я разделила разбор кода на части. В этой мы узнали, как запустить бота в режиме разработки, как общаться с BotFather, как запрашивать пользовательские данные и записывать их. В следующей статье я расскажу, как рассчитываются паутинка, итоговое количество очков и рекомендация курса. И покажу, как выгружать базу игроков в Google Таблицы.
Если вы захотите форкнуть такого бота, то вот ссылка на репозиторий.
Это статья из цикла «5 ETL для зоопарка ботов». В нём я пошагово разбираю, как наладить потоки данных из разных библиотек и конструкторов ботов на разных языках и стеках. В основе лежат Python и его библиотеки. Вот предыдущие статьи цикла:
- Анонс цикла с перечнем технологий
- Настройка потока логов «Из Dialogflow в BigQuery»
- Python для аналитики ad hoc из BigQuery
- Развертывание Airflow
- Настройка первого DAG
Если Вы уже знаете, как исполнять в коде SQL-запросы, понимаете основы ЯП достаточно, чтобы писать несложные программы, то бот может стать вашим следующим этапом профессионального развития: он позволит закрепить основы и связать их воедино. В дополнение ко всему, автоматизация рабочего процесса станет бонусом вам или вашим коллегам.
Мы получим довольно простого бота, который задаёт будущим студентам курса вовлекающие вопросы и оценивает навык управления проектами. И выводит:
- количество очков (рассчитывается по нелинейной формуле);
- график-паутинку на базе очков;
- индивидуальную рекомендацию курсов.
Запуск бота
Подключаем aiogram. Это фреймворк, а значит, взаимосвязанных скриптов в проекте немало:
Чтобы сервер понимал, с какого именно файла начать, в main.py добавляем условие if __name__ == ‘__main__’:
main.py:
from aiogram import executor import handlers from loader import dp if __name__ == ‘__main__’: executor.start_polling(dp) Поллинг (start_polling) — базовый способ «выпустить» бота в мир. Существует еще и вебхук-бот, но для его развёртывания требуется доменное имя, которому можно добавить A-запись, так что пока обойдемся простейшим решением.
В loader.py прописываем основные конфиденциальные настройки, которые будет брать бот:
import logging from aiogram import Bot, Dispatcher, types from aiogram.contrib.fsm_storage.memory import MemoryStorage from core import settings bot = Bot(token=settings.tg_token, parse_mode=types.ParseMode.MARKDOWN_V2) storage = MemoryStorage() dp = Dispatcher(bot, storage=storage) logging.basicConfig(level=logging.INFO)
Здесь указано, где брать токен, какой режим парсинга текста использовать и так далее.
Кстати, если вы еще не взаимодействовали с @BotFather и не создали своего бота, то отправьте ему /start, затем /newbot и проследуйте стандартным шагам. Подробнее в этом гайде.
Ведущий аналитик/программист (Отдел планирования и управленческой отчетности) МТС, , можно удалённо, По итогам собеседования tproger.ru Вакансии на tproger.ru
Пишем модуль settings, из которого потом будем брать токен:
settings.py
import os from dotenv import load_dotenv load_dotenv() tg_token = str(os.getenv(‘TELEGRAM_TOKEN’)) TELEGRAM_BOT_NAME = str(os.getenv(‘TELEGRAM_BOT_NAME’)) ADMINS = str(os.getenv(‘BOT_ADMINS’)) DATABASE = str(os.getenv(‘DATABASE’)) SKIP_CHECK_TIME = bool(os.getenv(‘SKIP_CHECK_TIME’))
os.getenv() ссылается на некоторые переменные вроде TELEGRAM TOKEN и DATABASE. Их мы поместим в отдельный локальный файл .env. Разработка и деплой бота ведутся на разных машинах, и это поможет не переписывать его при переходе, например, с macOS на Linux SYSTEM_PATH:
.env
TELEGRAM_TOKEN=<TOKEN> BOT_ADMINS=<ID АДМИНА> DATABASE=./data/db.sqlite3 # Путь до файлов с графиками SYSTEM_PATH=/Users/elenakapatsa/Repositories/tripwire/radar_charts/
Пишем команду /start. Через неё пользователь будет попадать к первому одноименному обработчику, который проверит наличие человека в базе данных и запросит телефон, если человек новый:
start.py
@dp.message_handler(commands=[‘start’], state=None) async def start(message: types.Message): tg_id = message.chat.id # Определяем ID юзера для записи в БД # Если юзер новый, то запрашиваем телефон if not bd.check_user(tg_id): await ask_phone(tg_id, message.chat.first_name) # Если юзер не новый, отправляем его на первый вопрос else: await message.answer(texts.initialize_questionnaire, reply_markup=kb.add_step_keyboard(texts_btn.start_menu))
Функция ask_phone выглядит следующим образом:
start.py
async def ask_phone(tg_id, first_name): phone = kb.send_phone() # Рендерим кнопку “Отправить телефон” name = md.bold(first_name) # Выделяем имя из профиля # Просим юзера показать телефон await bot.send_message(tg_id, f»’Привет, {name}! Для регистрации нажми кнопку Передать номер телефона»’, reply_markup=phone)
Функция get_phone() захватит телефон, имя и Telegram ID и запишет это в базу SQLite:
start.py
@dp.message_handler(content_types=types.ContentTypes.CONTACT) async def get_phone(message: types.Message): phone = message.contact.phone_number name = message.contact.first_name tg_id = message.chat.id # Запишем в базу данные юзера bd.registration(tg_id, phone, name) # Запустим опрос await message.answer(md.text( # Зададим вопрос texts.initialize_questionnaire, sep=’n’), reply_markup=kb.add_step_keyboard(texts_btn.start_menu))
Опросник
Импортируем необходимые библиотеки. По мере раскомментирования кода станет понятно, для чего нужна каждая из них:
questionnaire.py
from aiogram import md, types from aiogram.dispatcher import FSMContext from aiogram.dispatcher.filters import Text from keyboards import keyboards as kb from loader import dp from states import states as st from texts import numeric_keyboard as nk from texts import text_buttons as tb from texts import texts from texts import questions from utils import bd from plotter_interpreter import * from level_counter import *
Чтобы попасть в саму анкету, пользователь будет нажимать клавишу «Поехали» (первую в списке, то есть нулевую по канонам ЯП) и хэндлер analyst_start примет «это на свой счёт»:
@dp.message_handler(Text(equals=tb.start_menu[0], ignore_case=True), state=’*’) async def analyst_start(message: types.Message): await st.StateQuestionnaire.question1.set() await message.answer(questions.team_problems, reply_markup=kb.add_step_keyboard(nk.numeric_keyboard[:6]))
База SQLite выглядит так (для просмотра пользуюсь DB Browser for SQLite):
Заключение
Чтобы сохранить простоту восприятия, я разделила разбор кода на части. В этой мы узнали, как запустить бота в режиме разработки, как общаться с BotFather, как запрашивать пользовательские данные и записывать их. В следующей статье я расскажу, как рассчитываются паутинка, итоговое количество очков и рекомендация курса. И покажу, как выгружать базу игроков в Google Таблицы.
Если вы захотите форкнуть такого бота, то вот ссылка на репозиторий.