Футбольный телеграм бот на Python (1/4): Подготовка и настройка бота
В этой серии статей мы напишем телеграм бота на python. Он работает с внешним API, запрашивает результаты футбольных матчей и выводить их в сообщении.
Когда локальная версия будет готова, разместим бота на сервере. Вместо Heroku, я выбрал отдельную виртуальную машину, что бы бот не засыпал. Это ближе к реальности.
Вся разработка разбита на этапы:
- Локальная установка библиотек и Redis.
- Регистрация и получение токена.
- Настройка , подключение к базам данных.
- Написание основной функциональности бота.
- Регистрации, выбор и настройка внешнего апи футбольных матчей.
- Добавление сбора результатов матчей и интеграция в бота.
- Деплой, публикация на сервере:
- Регистрация дешевого или бесплатного VPS.
- Запуск Редис-клиента.
- Запуск и настройка бота на сервере.
Рабочая версия бота запущена в телеграме до конца февраля @FonlineBOT.Бот отключен.Вводные данные
Материал рассчитан на уровень Начинающий+, нужно понимать как работают классы и функции, знать основы базы данных и async/await. Если знаний мало, крайне желательно писать код в Pycharm, бесплатная версия подходит.
Используйте указанные версии библиотек, что бы проект работал без изменений. При установке иных версий вы можете получать ошибки, связанные с совместимостью.
Версия Python - 3.8+ aiogram==2.11.2 emoji==1.1.0 redis==3.5.3 ujson==4.0.1 uvloop==0.14.0 # не работает и не требуется на Windows
Локальная установка библиотек для бота и Redis
Для начала нужно создать проект «fonlinebot» с виртуальным окружение. В Pycharm это делается так:
Затем установить библиотеки в виртуальном окружении. Сразу понадобятся 4: для бота, работы с redis, ускорения и emoji в сообщениях.
pip install aiogram==2.11.2 redis==3.5.3 ujson==4.0.1 emoji==1.1.0
Установка Redis локально
Redis — это резидентная база данных (такая, которая хранит записи прямо в оперативной памяти) в виде пар ключ-значение. Чтение и запись в память происходит намного быстрее, чем в случае с дисками, поэтому такой подход отлично подходит для хранения второстепенных данных.
Из недавней статьи — Redis для приложений на Python
Для установки Redis на Linux/Mac следуйте этим инструкциям: https://redis.io/download#from-source-code. Для запуска достаточно ввести src/redis-server .
Что бы установить на Windows скачайте и распакуйте архив отсюда. Для запуска откройте «redis-server.exe».
Теперь нужно убедиться, что все работает. Создайте файл «main.py» в корне проекта и выполните этот код:
# fonlinebot/main.py import redis r = redis.StrictRedis() print(r.ping())
Вывод будет True , в другом случае ошибка.
Регистрация бота и получение токена
Для регистрации напишем https://t.me/botfather команду /newbot . Далее он просит ввести имя и адрес бота. Если данные корректны, выдает токен. Учтите, что адрес должен быть уникальным, нельзя использовать «fonlinebot» снова.
На время разработки сохраним токен в файл. Создайте «config.py» в папке проекта для хранения настроек и запишите токен TOKEN = "ВАШ ТОКЕН"
Настройка бота
Теперь нужно связать бота с redis и базой данных, проверить работоспособность.
Создадим необходимые модули и файлы. В папке «fonlinebot» к созданным ранее «main.py» и «config.py» добавим: «database.py», «requirements.txt» и папку «app». В папку «app» добавьте: «bot.py», «dialogs.py», «service.py». Вот такая структура получится:
Разделив бот на модули, его удобнее поддерживать и дорабатывать.
- «main.py» — для запуска бота.
- «config.py» — хранит настройки, ключи доступов и другую статическую информацию.
- «database.py» — для работы с базой данных и кешем(redis).
- «requirements.txt» — хранит зависимости проекта, для запуска на сервере.
- «app» — папка самого бота.
- «bot.py» — для взаимодействия бота с юзерами, ответы на сообщения.
- «dialogs.py» — все текстовые ответы бота.
- «service.py» — бизнес логика, получение и обработка данных о матчах.
Пришло время перейти к программированию. Запишем в «requirements.txt» наши зависимости:
aiogram==2.11.2 emoji==1.1.0 redis==3.5.3 ujson==4.0.1 uvloop==0.14.0
Так как большая часть программирует на Windows, uvloop мы не устанавливали локально. Установим его на сервере.
В «config.py» к токену добавим данные бота и подключения к redis.
# fonlinebot/config.py import ujson import logging logging.basicConfig(level=logging.INFO) TOKEN = "здесь должен быть токен" BOT_VERSION = 0.1 # База данных хранит выбранные юзером лиги BOT_DB_NAME = "users_leagues" # Тестовые данные поддерживаемых лиг BOT_LEAGUES = < "1": "Бундеслига", "2": "Серия А", "3": "Ла Лига", "4": "Турецкая Суперлига", "5": "Чемпионат Нидерландов", "6": "Про-лига Бельгии", "7": "Английская Премьер-лига", "8": "Лига 1", ># Флаги для сообщений, emoji-код BOT_LEAGUE_FLAGS = < "1": ":Germany:", "2": ":Italy:", "3": ":Spain:", "4": ":Turkey:", "5": ":Netherlands:", "6": ":Belgium:", "7": ":England:", "8": ":France:", ># Данные redis-клиента REDIS_HOST = 'localhost' REDIS_PORT = 6379 # По умолчанию пароля нет. Он будет на сервере REDIS_PASSWORD = None
Информацию о лигах в будущем можно будет вынести в отдельный json файл. Эта версия бота будет поддерживать не более 10 вариантов, я явно их записал.
Добавление базы данных
Теперь добавим классы для работы с базой данных sqlite и redis. База данных нужна для сохранения предпочтений по лигам юзеров.
Юзер будет выбирать 3 чемпионата для отслеживания, бот сохранит их в БД и использует для запроса результатов.
Кеш(redis) будет сохранять результаты матчей, что бы уменьшить количество запросов к API и ускорить время ответов. Как правило, бесплатные API лимитирует запросы.
# fonlinebot/database.py import os import logging import sqlite3 import redis import ujson import config # класс наследуется от redis.StrictRedis class Cache(redis.StrictRedis): def __init__(self, host, port, password, charset="utf-8", decode_responses=True): super(Cache, self).__init__(host, port, password=password, charset=charset, decode_responses=decode_responses) logging.info("Redis start") def jset(self, name, value, ex=0): """функция конвертирует python-объект в Json и сохранит""" r = self.get(name) if r is None: return r return ujson.loads(r) def jget(self, name): """функция возвращает Json и конвертирует в python-объект""" return ujson.loads(self.get(name))
Класс Cache наследуется от StrictRedis . Мы добавляем 2 метода jset , jget для сохранения списков и словарей python в хранилище redis. Изначально он не работает с ними.
Теперь добавим класс, который будет создавать базы данных и выполнять функции CRUD.
# fonlinebot/database.py #. class Database: """ Класс работы с базой данных """ def __init__(self, name): self.name = name self._conn = self.connection() logging.info("Database connection established") def create_db(self): connection = sqlite3.connect(f".db") logging.info("Database created") cursor = connection.cursor() cursor.execute('''CREATE TABLE users (id INTEGER PRIMARY KEY, leagues VARCHAR NOT NULL);''') connection.commit() cursor.close() def connection(self): db_path = os.path.join(os.getcwd(), f".db") if not os.path.exists(db_path): self.create_db() return sqlite3.connect(f".db") def _execute_query(self, query, select=False): cursor = self._conn.cursor() cursor.execute(query) if select: records = cursor.fetchone() cursor.close() return records else: self._conn.commit() cursor.close() async def insert_users(self, user_id: int, leagues: str): insert_query = f"""INSERT INTO users (id, leagues) VALUES (, "")""" self._execute_query(insert_query) logging.info(f"Leagues for user added") async def select_users(self, user_id: int): select_query = f"""SELECT leagues from leagues where record = self._execute_query(select_query, select=True) return record async def update_users(self, user_id: int, leagues: str): update_query = f"""Update leagues set leagues = "" where self._execute_query(update_query) logging.info(f"Leagues for user updated") async def delete_users(self, user_id: int): delete_query = f"""DELETE FROM users WHERE self._execute_query(delete_query) logging.info(f"User deleted")
Sqlite подходит для тестовых проектов. В будущем потребуется переход на внешнюю базу данных и асинхронная работа. Что бы не переписывать всю логику работы с базой, я сразу добавил асинхронный синтаксис.
Файл базы данных будет создаваться один раз, автоматически. Теперь нужно создать экземпляры классов:
Как интегрировать словарь в json в телеграм бота на python
Выдает кучу ошибок, как исправить код, чтобы бот доставал ссылки из текстовика посредством json.
Остановка Бота в телеграм через python
Не могу понять как остановить бота в телеграм, У меня есть main.js от туда я его запускаю с.Разработка бота для Телеграм на Python
Напишу телеграм бота на языке Python 3 и быстром, современном, асинхронном движке Aiogram. Сферы.Разработка сложного Телеграм бота, Python или TypeScript?
Всем форумчанам ДВС! Вопрос у меня к обществу, скорее стратегический нежели технический. Я.Подскажите лучшую библиотеку python для разработки телеграм бота
Раньше использовал PyTelegramBotAPI или просто TeleBot, но ощущение что чего-то не хватает, а.Сообщение от pavlishka
секретных? или их можно привести?
почему не пошел сразу в документацию читать про жсон?
C:\Users\Павел\AppData\Local\Programs\Python\Python39\python.exe C:/Users/Павел/PycharmProjects/sa-bot/sa-bot/sa_bot.py
Traceback (most recent call last):
File "C:\Users\Павел\PycharmProjects\sa-bot\sa-bot\sa_bot.py", line 4, in
import links
File "C:\Users\Павел\PycharmProjects\sa-bot\sa-bot\links.py", line 3, in
ls = load(open('links.txt'))
File "C:\Users\Павел\AppData\Local\Programs\Python\Python39\lib\json\__init__.py" , line 293, in load
return loads(fp.read(),
File "C:\Users\Павел\AppData\Local\Programs\Python\Python39\lib\json\__init__.py" , line 346, in loads
return _default_decoder.decode(s)
File "C:\Users\Павел\AppData\Local\Programs\Python\Python39\lib\json\decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "C:\Users\Павел\AppData\Local\Programs\Python\Python39\lib\json\decoder.py", line 353, in raw_decode
obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Invalid control character at: line 4 column 147 (char 230)Process finished with exit code 1
у меня нет проблем с этим текстом
Добавлено через 45 секунд
Сообщение от pavlishka
там другой текст, в твоем примере 201 символ всего
Добавлено через 1 минуту
смотри что там в файле на этой позицииСообщение от pavlishka
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
#обработка сообщений @bot.message_handler(content_types=['text']) def main_menu(message): if message.text == 'Справочник': kb_sprav = telebot.types.InlineKeyboardMarkup(row_width=1) for key in sprav.spr: cur_but = telebot.types.InlineKeyboardButton(text=key, callback_data=key) kb_sprav.add(cur_but) bot.send_message(message.chat.id, 'Справочник:', reply_markup=kb_sprav) elif message.text == 'Ссылки': kb_links = telebot.types.InlineKeyboardMarkup() for text, url in json.load(open('links.txt')).items(): kb_links.add( telebot.types.InlineKeyboardButton( text=text, url=url, callback_data="linked") ) bot.send_message(message.chat.id, 'Ссылки:', reply_markup=kb_links) elif message.text == 'Программы': kb_prog = telebot.types.InlineKeyboardMarkup() for key in prog.pr: cur_but = telebot.types.InlineKeyboardButton(text=key, url=prog.pr[key], callback_data="pr") kb_prog.add(cur_but) bot.send_message(message.chat.id, 'Программы:', reply_markup=kb_prog) elif message.text == 'Расписание МИР-20-1Б': kb_raspis = telebot.types.InlineKeyboardMarkup() for key in raspis.ras: cur_but = telebot.types.InlineKeyboardButton(text=key, url=raspis.ras[key], callback_data="ras") kb_raspis.add(cur_but) file = open('2020-2021 Raspisanie ehkzamenov EHTF MIR -20-1b (vesennijj sessiya).xlsx', 'rb') bot.send_document(message.chat.id, file) elif message.text == 'Расписание МИР-20-2Б': kb_raspis = telebot.types.InlineKeyboardMarkup() for key in raspis.ras: cur_but = telebot.types.InlineKeyboardButton(text=key, url=raspis.ras[key], callback_data="ras") kb_raspis.add(cur_but) file = open('2020-2021 Raspisanie ehkzamenov EHTF MIR -20-2b (vesennijj sessiya).xlsx', 'rb') bot.send_document(message.chat.id, file) elif message.text == 'Тестирования': kb_raspis = telebot.types.InlineKeyboardMarkup() for key in raspis.ras: cur_but = telebot.types.InlineKeyboardButton(text=key, url=raspis.ras[key], callback_data="ras") kb_raspis.add(cur_but) file = open('Raspisanie_rubezhnogo_testir__2_sem_2020_21_uch_g_posle_smeny_ETF_redaktirovannoe.pdf', 'rb') bot.send_document(message.chat.id, file) @bot.callback_query_handler(func=lambda call: True) def callback_inline(call): if call.message.text == "Справочник:": bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.id, text=call.data + '\n' + sprav.spr[call.data], reply_markup=None) elif call.data == "linked": bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.id, text="Ссылки. ", reply_markup=None) #циклическая проверка на новые сообщения боту bot.polling()
это весь основной код бота
Добавлено через 2 минуты
Он цветом не выделяет модуль links