bot reworked to class

This commit is contained in:
Dmitry Afanasyev 2022-08-28 06:38:00 +03:00
parent 2304955212
commit 591d5ea01a
6 changed files with 139 additions and 123 deletions

View File

@ -4,8 +4,14 @@ PY_TARGET_FILES=
PORT=8000
.PHONY: app
app:
poetry run python app/main.py
app-up:
docker-compose up -d --build
app-down:
docker-compose down -v
app-clean:
docker-compose down -v && docker-clean run
# standard commands to run on every commit
format:

View File

@ -30,7 +30,7 @@ killall python
## TODO
- [ ] Добавить очередь сообщений
- [x] Добавить очередь сообщений
- [x] Исправить запуск локально
- [ ] Добавить тестов
- [ ] Close connection
- [x] Close connection

View File

@ -4,7 +4,7 @@ from dataclasses import dataclass
from aiogram import Dispatcher
from aiogram.utils.executor import start_polling
from aiohttp import web
from app.core.bot import bot, dispatcher
from app.core.bot import TransportBot
from app.core.routes import Handler
from app.core.scheduler import BotScheduler, bot_scheduler
from app.core.utils import logger
@ -18,7 +18,7 @@ class Application:
async def _on_startup(self, dp: Dispatcher) -> None:
logger.info("Start bot with webhook")
await bot.set_webhook(WEBHOOK_URL)
await TransportBot.bot.set_webhook(WEBHOOK_URL)
loop = asyncio.get_running_loop()
loop.create_task(self.handler.get_updates_from_queue())
logger.info(
@ -32,9 +32,9 @@ class Application:
logger.warning('Shutting down..')
# Remove webhook (not acceptable in some cases)
await bot.delete_webhook()
await TransportBot.bot.delete_webhook()
session = await bot.get_session()
session = await TransportBot.bot.get_session()
if session and not session.closed:
await session.close()
await asyncio.sleep(0.2)
@ -45,7 +45,7 @@ class Application:
def bot_polling() -> None:
logger.info("Start bot in polling mode")
start_polling(
dispatcher=dispatcher,
dispatcher=TransportBot.dispatcher,
skip_updates=True,
)

View File

@ -1,4 +1,5 @@
import asyncio
from dataclasses import dataclass
from aiogram import Bot, types
from aiogram.contrib.middlewares.logging import LoggingMiddleware
@ -7,87 +8,96 @@ from aiogram.utils.callback_data import CallbackData
from app.core.parse_web import get_driver, parse_yandex_maps
from app.settings import TELEGRAM_API_TOKEN
bot = Bot(token=TELEGRAM_API_TOKEN)
dispatcher = Dispatcher(bot)
dispatcher.middleware.setup(LoggingMiddleware())
@dataclass
class TransportBot:
bot: Bot = Bot(TELEGRAM_API_TOKEN)
dispatcher: Dispatcher = Dispatcher(bot)
dispatcher.middleware.setup(LoggingMiddleware())
stations_cb = CallbackData('station', 'direction')
stations_cb: CallbackData = CallbackData('station', 'direction')
@staticmethod
@dispatcher.message_handler(commands=['chatid'])
async def chat_id(message: types.Message) -> types.Message:
return await TransportBot.bot.send_message(message.chat.id, message.chat.id)
def get_keyboard() -> types.InlineKeyboardMarkup:
"""
Generate keyboard with list of posts
"""
markup = types.InlineKeyboardMarkup()
@staticmethod
def get_keyboard() -> types.InlineKeyboardMarkup:
"""
Generate keyboard with list of posts
"""
markup = types.InlineKeyboardMarkup()
markup.row(
types.InlineKeyboardButton(
'Дом -> Офис', callback_data=stations_cb.new(direction='home->office')
),
types.InlineKeyboardButton(
'Офис -> Дом', callback_data=stations_cb.new(direction='office->home')
),
)
return markup
markup.row(
types.InlineKeyboardButton(
'Дом -> Офис',
callback_data=TransportBot.stations_cb.new(direction='home->office'),
),
types.InlineKeyboardButton(
'Офис -> Дом',
callback_data=TransportBot.stations_cb.new(direction='office->home'),
),
)
return markup
@staticmethod
@dispatcher.callback_query_handler(stations_cb.filter(direction='home->office'))
async def home_office(
query: types.CallbackQuery, callback_data: dict[str, str]
) -> types.Message:
driver = get_driver()
text = parse_yandex_maps(
driver=driver,
url='https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21',
message='Остановка Б. Академическая ул, д. 15',
)
@dispatcher.callback_query_handler(stations_cb.filter(direction='home->office'))
async def home_office(
query: types.CallbackQuery, callback_data: dict[str, str]
) -> types.Message:
driver = get_driver()
text = parse_yandex_maps(
driver=driver,
url='https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21',
message='Остановка Б. Академическая ул, д. 15',
)
return await TransportBot.bot.send_message(
query.message.chat.id, text, reply_markup=TransportBot.get_keyboard()
)
return await bot.send_message(
query.message.chat.id, text, reply_markup=get_keyboard()
)
@staticmethod
@dispatcher.callback_query_handler(stations_cb.filter(direction='office->home'))
async def office_home(
query: types.CallbackQuery, callback_data: dict[str, str]
) -> types.Message:
driver = get_driver()
text = parse_yandex_maps(
driver=driver,
url='https://yandex.ru/maps/213/moscow/stops/stop__9640288/?ll=37.505402%2C55.800214&tab=overview&z=21',
message='Остановка Улица Алабяна',
)
return await TransportBot.bot.send_message(
query.message.chat.id, text, reply_markup=TransportBot.get_keyboard()
)
@dispatcher.callback_query_handler(stations_cb.filter(direction='office->home'))
async def office_home(
query: types.CallbackQuery, callback_data: dict[str, str]
) -> types.Message:
driver = get_driver()
text = parse_yandex_maps(
driver=driver,
url='https://yandex.ru/maps/213/moscow/stops/stop__9640288/?ll=37.505402%2C55.800214&tab=overview&z=21',
message='Остановка Улица Алабяна',
)
@staticmethod
@dispatcher.message_handler()
async def echo(message: types.Message) -> types.Message:
return await TransportBot.bot.send_message(
message.chat.id,
'Выбери остановку',
reply_markup=TransportBot.get_keyboard(),
)
return await bot.send_message(
query.message.chat.id, text, reply_markup=get_keyboard()
)
@staticmethod
async def morning_bus_mailing(chat_ids: list[int] | None) -> None:
if not chat_ids:
return None
@dispatcher.message_handler(commands=['chatid'])
async def chat_id(message: types.Message) -> types.Message:
return await bot.send_message(message.chat.id, message.chat.id)
@dispatcher.message_handler()
async def echo(message: types.Message) -> types.Message:
return await bot.send_message(
message.chat.id, 'Выбери остановку', reply_markup=get_keyboard()
)
async def morning_bus_mailing(chat_ids: list[int]) -> None:
driver = get_driver()
text = parse_yandex_maps(
driver=driver,
url='https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21',
message='Остановка Б. Академическая ул, д. 15',
)
await asyncio.gather(
*[
bot.send_message(
chat_id=chat_id, text=text, parse_mode=types.ParseMode.HTML
)
for chat_id in chat_ids
]
)
driver = get_driver()
text = parse_yandex_maps(
driver=driver,
url='https://yandex.ru/maps/213/moscow/stops/stop__9640740/?ll=37.527924%2C55.823470&tab=overview&z=21',
message='Остановка Б. Академическая ул, д. 15',
)
await asyncio.gather(
*[
TransportBot.bot.send_message(
chat_id=chat_id, text=text, parse_mode=types.ParseMode.HTML
)
for chat_id in chat_ids
]
)

View File

@ -3,7 +3,7 @@ from http import HTTPStatus
from aiogram.types import Update
from aiohttp import web
from app.core.bot import dispatcher
from app.core.bot import TransportBot
class Handler:
@ -16,10 +16,7 @@ class Handler:
async def put_updates_on_queue(self, request: web.Request) -> web.Response:
"""
Listen {WEBHOOK_PATH} and proxy post request to bot
:param request:
:return:
Listen {WEBHOOK_PATH}/{TELEGRAM_WEB_TOKEN} path and proxy post request to bot
"""
data = await request.json()
tg_update = Update(**data)
@ -30,5 +27,5 @@ class Handler:
async def get_updates_from_queue(self) -> None:
while True:
update = await self.queue.get()
await dispatcher.process_update(update)
await TransportBot.dispatcher.process_update(update)
await asyncio.sleep(0.1)

View File

@ -1,64 +1,67 @@
from typing import Any
from app.core.bot import morning_bus_mailing
from app.core.bot import TransportBot
from app.core.utils import logger
from apscheduler.schedulers.asyncio import AsyncIOScheduler
bot_cron_jobs = {
'morning_home->work_bus': [
{
'trigger': 'cron',
'day_of_week': 'mon-fri',
'hour': 8,
'minute': 59,
'second': 0,
'morning_home->work_bus': {
'job': TransportBot.morning_bus_mailing,
'cron': [
{
'trigger': 'cron',
'day_of_week': 'mon-fri',
'hour': 8,
'minute': 59,
'second': 0,
},
{
'trigger': 'cron',
'day_of_week': 'mon-fri',
'hour': 9,
'minute': 4,
'second': 0,
},
{
'trigger': 'cron',
'day_of_week': 'mon-fri',
'hour': 9,
'minute': 9,
'second': 0,
},
],
'func_kwargs': {
'chat_ids': [
417070387, # me
# 431571617, # Lenok
]
},
{
'trigger': 'cron',
'day_of_week': 'mon-fri',
'hour': 9,
'minute': 4,
'second': 0,
},
{
'trigger': 'cron',
'day_of_week': 'mon-fri',
'hour': 9,
'minute': 9,
'second': 0,
},
]
}
user_chat_ids = {
'chat_ids': [
417070387, # me
# 431571617, # Lenok
]
}
}
class BotScheduler:
def __init__(
self,
cron_jobs: dict[str, list[dict[str, Any]]],
chat_ids: dict[str, list[int]] | None = None,
cron_jobs: dict[str, dict[str, Any]],
):
self.cron_jobs = cron_jobs
self.chat_ids = chat_ids
self.scheduler = AsyncIOScheduler()
def add_scheduler_jobs(self, jobs_name: str) -> None:
cron_jobs = self.cron_jobs.get(jobs_name)
if not cron_jobs:
return None
for cron in cron_jobs:
self.scheduler.add_job(morning_bus_mailing, kwargs=user_chat_ids, **cron)
logger.info(f'Added scheduled job {cron}')
for cron in cron_jobs['cron']:
self.scheduler.add_job(
cron_jobs['job'], kwargs=cron_jobs.get('func_kwargs'), **cron
)
logger.info(f'Added scheduled job: {cron_jobs["job"].__name__} {cron}')
def start(self) -> None:
self.scheduler.start()
logger.info('Scheduler started')
bot_scheduler = BotScheduler(cron_jobs=bot_cron_jobs, chat_ids=user_chat_ids)
bot_scheduler = BotScheduler(cron_jobs=bot_cron_jobs)
bot_scheduler.add_scheduler_jobs(jobs_name='morning_home->work_bus')