diff --git a/.env.dist b/.env.dist index eec173e..a221158 100644 --- a/.env.dist +++ b/.env.dist @@ -1,15 +1,18 @@ # This file is a template of which env vars need to be defined for your application # Copy this file to .env file for development, create environment variables when deploying to production -LOG_LEVEL= -LOG_FORMAT= +TOKEN= +PORT=80 +CHAT_ID= +ERRORS_CHAT_ID= +ERRORS_THREAD_ID= +TELEGRAM_SECRET= +BASE_URL=http://localhost:8000/ +WEBHOOK_PATH=webhook +API_ACCESS_TOKEN= -TOKEN=TOKEN -CHAT_ID=13173823 -TELEGRAM_SECRET=mysecretpassword -BASE_URL=URL -WEBHOOK_PATH=/webhook -NGROK_AUTHTOKEN= +API_URL= +FRONT_BASE_URL= -FRONT_BASE_URL=https://fictadvisor.com -API_URL=https://dev.fictadvisor.com/api/v2 \ No newline at end of file +LOG_LEVEL=info +LOG_FORMAT=info diff --git a/app/api/factory.py b/app/api/factory.py index ea82eb3..c752072 100644 --- a/app/api/factory.py +++ b/app/api/factory.py @@ -58,7 +58,12 @@ async def async_exception_handler(request: Request, exc: Exception) -> JSONRespo async def on_startup(*a: Any, **kw: Any) -> None: # pragma: no cover if settings.DEVELOPMENT: from ngrok import ngrok - port = int(sys.argv[sys.argv.index("--port") + 1]) if "--port" in sys.argv else 8000 + + port = ( + int(sys.argv[sys.argv.index("--port") + 1]) + if "--port" in sys.argv + else 8000 + ) ngrok.set_auth_token(settings.NGROK_AUTHTOKEN.get_secret_value() if settings.NGROK_AUTHTOKEN else None) # type: ignore tunnel = await ngrok.connect(port) # type: ignore[misc] public_url = tunnel.url() @@ -68,11 +73,12 @@ async def on_startup(*a: Any, **kw: Any) -> None: # pragma: no cover async def on_shutdown(*a: Any, **kw: Any) -> None: # pragma: no cover if settings.DEVELOPMENT: from ngrok import ngrok + ngrok.disconnect() await dispatcher.emit_shutdown(**workflow_data) - app.add_event_handler('startup', on_startup) - app.add_event_handler('shutdown', on_shutdown) + app.add_event_handler("startup", on_startup) + app.add_event_handler("shutdown", on_shutdown) app.include_router(api) diff --git a/app/bot/filters/is_captain_or_deputy.py b/app/bot/filters/is_captain_or_deputy.py index d426ea4..96196d9 100644 --- a/app/bot/filters/is_captain_or_deputy.py +++ b/app/bot/filters/is_captain_or_deputy.py @@ -19,7 +19,7 @@ async def __call__(self, update: Union[Message, CallbackQuery]) -> Union[bool, D if user.group.role in (Role.CAPTAIN, Role.MODERATOR): return {"user": user} except ResponseException as e: - await send_answer(update, "Прив'яжіть телеграм до аккаунта FictAdvisor") + await send_answer(update, "Прив'яжіть телеграм до аккаунта FICE Advisor") logging.error(e) else: await send_answer(update, "Ця команда лише для старости або заступників") diff --git a/app/bot/handlers/private/enable.py b/app/bot/handlers/private/enable.py index 9715419..5eeedc3 100644 --- a/app/bot/handlers/private/enable.py +++ b/app/bot/handlers/private/enable.py @@ -1,3 +1,4 @@ +import logging from typing import Optional from aiogram.types import Message @@ -14,11 +15,18 @@ UpdateTelegramGroup, ) from app.services.user_api import UserAPI +from app.utils.telegram import send_answer async def enable(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with TelegramGroupAPI() as telegram_group_api: try: telegram_groups: TelegramGroups = await telegram_group_api.get_telegram_groups(user.group.id) diff --git a/app/bot/handlers/private/fortnight.py b/app/bot/handlers/private/fortnight.py index 700825a..055db71 100644 --- a/app/bot/handlers/private/fortnight.py +++ b/app/bot/handlers/private/fortnight.py @@ -1,17 +1,27 @@ +import logging + from aiogram.types import CallbackQuery, Message from app.bot.keyboards.types.select_week import SelectWeek from app.bot.keyboards.week_keyboard import get_week_keyboard from app.messages.events import WEEK_EVENT_LIST +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.user_api import UserAPI from app.utils.date_service import DateService from app.utils.events import check_odd +from app.utils.telegram import send_answer async def fortnight(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_fortnight(user.group.id, user_id=user.id) @@ -28,8 +38,14 @@ async def fortnight(message: Message) -> None: async def select_week(callback: CallbackQuery, callback_data: SelectWeek) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(callback.from_user.id) + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(callback.from_user.id) + except ResponseException as e: + await send_answer(callback, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_fortnight(callback_data.group_id, user_id=user.id) week = callback_data.week diff --git a/app/bot/handlers/private/left.py b/app/bot/handlers/private/left.py index 053ec8c..9083212 100644 --- a/app/bot/handlers/private/left.py +++ b/app/bot/handlers/private/left.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime, timedelta from typing import Optional @@ -5,14 +6,22 @@ from aiogram.types import Message from app.messages.events import LEFT_EVENT +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.user_api import UserAPI from app.utils.events import group_by_time +from app.utils.telegram import send_answer async def left_command(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_day(user.group.id, user_id=user.id) diff --git a/app/bot/handlers/private/next.py b/app/bot/handlers/private/next.py index 810b49a..fd8f546 100644 --- a/app/bot/handlers/private/next.py +++ b/app/bot/handlers/private/next.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime from typing import Iterable, Optional, Tuple @@ -5,15 +6,23 @@ from aiogram.types import Message from app.messages.events import NEXT_EVENT +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.types.general_event import GeneralEvent from app.services.user_api import UserAPI from app.utils.events import group_by_time +from app.utils.telegram import send_answer async def next_command(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_day(user.group.id, user_id=user.id) diff --git a/app/bot/handlers/private/now.py b/app/bot/handlers/private/now.py index 567b21a..930bc38 100644 --- a/app/bot/handlers/private/now.py +++ b/app/bot/handlers/private/now.py @@ -1,3 +1,4 @@ +import logging from datetime import datetime, timedelta from typing import Iterable, Optional, Tuple @@ -5,15 +6,23 @@ from aiogram.types import Message from app.messages.events import NOW_EVENT +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.types.general_event import GeneralEvent from app.services.user_api import UserAPI from app.utils.events import group_by_time +from app.utils.telegram import send_answer async def now_command(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_day(user.group.id, user_id=user.id) diff --git a/app/bot/handlers/private/poll.py b/app/bot/handlers/private/poll.py index d1ca058..8ad5feb 100644 --- a/app/bot/handlers/private/poll.py +++ b/app/bot/handlers/private/poll.py @@ -1,3 +1,5 @@ +import logging + from aiogram.fsm.context import FSMContext from aiogram.fsm.scene import ScenesManager from aiogram.types import CallbackQuery, Message @@ -7,16 +9,24 @@ ) from app.bot.keyboards.types.select_teacher import SelectTeacher from app.bot.scenes.poll import PollScene +from app.services.exceptions.response_exception import ResponseException from app.services.poll_api import PollAPI from app.services.types.student import Student from app.services.types.users_teachers import UsersTeachers from app.services.user_api import UserAPI +from app.utils.telegram import send_answer async def poll_command(message: Message, state: FSMContext) -> None: - async with UserAPI() as user_api: - user: Student = await user_api.get_user_by_telegram_id(message.from_user.id) #type: ignore[union-attr] - await state.update_data({"user": user}) + try: + async with UserAPI() as user_api: + user: Student = await user_api.get_user_by_telegram_id(message.from_user.id) #type: ignore[union-attr] + await state.update_data({"user": user}) + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with PollAPI() as poll_api: users_teachers: UsersTeachers = await poll_api.get_users_teachers(user_id=user.id) @@ -30,5 +40,3 @@ async def select_teacher(callback: CallbackQuery, callback_data: SelectTeacher, await callback.message.delete() await scenes.enter(PollScene, discipline_teacher_id=callback_data.discipline_teacher_id) await callback.answer() - - diff --git a/app/bot/handlers/private/today.py b/app/bot/handlers/private/today.py index 6afdf83..91081bb 100644 --- a/app/bot/handlers/private/today.py +++ b/app/bot/handlers/private/today.py @@ -1,14 +1,24 @@ +import logging + from aiogram.types import Message from app.messages.events import EVENT_LIST +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.user_api import UserAPI from app.utils.date_service import DateService +from app.utils.telegram import send_answer async def today(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_day(user.group.id, user_id=user.id, day=DateService.get_current_day()) diff --git a/app/bot/handlers/private/tomorrow.py b/app/bot/handlers/private/tomorrow.py index 344fced..32a334d 100644 --- a/app/bot/handlers/private/tomorrow.py +++ b/app/bot/handlers/private/tomorrow.py @@ -1,14 +1,24 @@ +import logging + from aiogram.types import Message from app.messages.events import EVENT_LIST +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.user_api import UserAPI from app.utils.date_service import DateService +from app.utils.telegram import send_answer async def tomorrow(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_day( user.group.id, user_id=user.id, day=DateService.get_current_day() + 1 diff --git a/app/bot/handlers/private/week.py b/app/bot/handlers/private/week.py index eab1550..2b57f2b 100644 --- a/app/bot/handlers/private/week.py +++ b/app/bot/handlers/private/week.py @@ -1,13 +1,23 @@ +import logging + from aiogram.types import Message from app.messages.events import WEEK_EVENT_LIST +from app.services.exceptions.response_exception import ResponseException from app.services.schedule_api import ScheduleAPI from app.services.user_api import UserAPI +from app.utils.telegram import send_answer async def week(message: Message) -> None: - async with UserAPI() as user_api: - user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + try: + async with UserAPI() as user_api: + user = await user_api.get_user_by_telegram_id(message.from_user.id) # type: ignore[union-attr] + except ResponseException as e: + await send_answer(message, "Прив'яжіть телеграм до аккаунта FICE Advisor") + logging.error(e) + return + async with ScheduleAPI() as schedule_api: general_events = await schedule_api.get_general_group_events_by_week(user.group.id, user_id=user.id) diff --git a/app/bot/middlewares/throttling.py b/app/bot/middlewares/throttling.py index a247791..4b44b07 100644 --- a/app/bot/middlewares/throttling.py +++ b/app/bot/middlewares/throttling.py @@ -1,6 +1,7 @@ from typing import Any, Awaitable, Callable, Dict, MutableMapping, Optional from aiogram import BaseMiddleware +from aiogram.exceptions import TelegramBadRequest from aiogram.types import TelegramObject, User from cachetools import TTLCache @@ -9,19 +10,28 @@ class ThrottlingMiddleware(BaseMiddleware): RATE_LIMIT = 0.7 def __init__(self, rate_limit: float = RATE_LIMIT) -> None: - self._cache: MutableMapping[int, None] = TTLCache(maxsize=10_000, ttl=rate_limit) + self._cache: MutableMapping[int, None] = TTLCache( + maxsize=10_000, ttl=rate_limit + ) async def __call__( - self, - handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], - event: TelegramObject, - data: Dict[str, Any], + self, + handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: Dict[str, Any], ) -> Optional[Any]: user: Optional[User] = data.get("event_from_user", None) if user is not None: if user.id in self._cache: return None + self._cache[user.id] = None - return await handler(event, data) + try: + return await handler(event, data) + except TelegramBadRequest as e: + if "message is not modified" in str(e): + return None + + raise