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 PORT=8000
.PHONY: app .PHONY: app
app: app-up:
poetry run python app/main.py 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 # standard commands to run on every commit
format: format:

View File

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

View File

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

View File

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

View File

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

View File

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