From cb8584d5d8818da362091ce7476224d950a63201 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:52:46 +0300 Subject: [PATCH 001/148] =?UTF-8?q?=F0=9F=9A=9A=20Create=20src=20folder=20?= =?UTF-8?q?and=20move=20all=20the=20code=20into=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__init__.py | 5 + .../infrastructure/NudeNet}/__init__.py | 0 .../infrastructure}/NudeNet/predictor.py | 4 +- .../infrastructure/YandexMap}/__init__.py | 0 .../infrastructure}/YandexMap/api.py | 2 +- .../infrastructure}/YandexMap/exceptions.py | 0 src/infrastructure/__init__.py | 3 + .../infrastructure/db_api}/__init__.py | 0 .../infrastructure}/db_api/db_commands.py | 5 +- .../infrastructure/jobs}/__init__.py | 0 .../infrastructure}/jobs/autobackup.py | 0 .../infrastructure}/jobs/autoupdate.py | 0 .../infrastructure}/yoomoney/__init__.py | 0 .../infrastructure}/yoomoney/authorization.py | 2 +- .../infrastructure}/yoomoney/exceptions.py | 0 .../infrastructure}/yoomoney/get_token.py | 4 +- .../infrastructure}/yoomoney/request.py | 2 +- .../yoomoney/types/__init__.py | 0 .../yoomoney/types/account_info.py | 0 .../yoomoney/types/operation_details.py | 0 .../yoomoney/types/operation_history.py | 0 .../infrastructure}/yoomoney/types/payment.py | 0 .../infrastructure}/yoomoney/wallet.py | 4 +- src/tgbot/__init__.py | 1 + {data => src/tgbot}/config.py | 7 +- {filters => src/tgbot/filters}/FiltersChat.py | 0 .../tgbot/filters}/IsAdminFilter.py | 2 +- src/tgbot/filters/__init__.py | 23 +++ {handlers => src/tgbot/handlers}/__init__.py | 2 +- .../tgbot/handlers}/admins/__init__.py | 0 .../tgbot/handlers}/admins/advert/__init__.py | 0 .../handlers}/admins/advert/advertisement.py | 14 +- .../admins/advert/mailing/__init__.py | 0 .../handlers}/admins/advert/mailing/create.py | 16 +- .../handlers}/admins/advert/ref/__init__.py | 0 .../admins/advert/requiredsub/__init__.py | 0 .../handlers}/admins/customers/__init__.py | 0 .../tgbot/handlers}/admins/customers/users.py | 18 +- .../tgbot/handlers}/admins/monitoring.py | 22 +-- .../handlers}/admins/settings/__init__.py | 0 .../tgbot/handlers}/admins/settings/admins.py | 24 ++- .../handlers}/admins/settings/logs_user.py | 18 +- .../handlers}/admins/settings/setting.py | 10 +- .../handlers}/admins/settings/tech_works.py | 22 +-- .../tgbot/handlers}/echo_handler.py | 6 +- .../tgbot/handlers}/errors/__init__.py | 0 .../tgbot/handlers}/errors/error_handler.py | 0 .../tgbot/handlers}/groups/__init__.py | 0 .../tgbot/handlers}/groups/event_moderate.py | 24 +-- .../tgbot/handlers}/groups/start.py | 12 +- .../tgbot/handlers}/users/__init__.py | 0 .../tgbot/handlers}/users/back.py | 30 ++-- .../tgbot/handlers}/users/brandbook.py | 18 +- .../tgbot/handlers}/users/buy_unban.py | 24 +-- .../tgbot/handlers}/users/change_datas.py | 52 +++--- .../handlers}/users/change_event_datas.py | 14 +- .../tgbot/handlers}/users/event.py | 40 ++--- .../tgbot/handlers}/users/event_list.py | 10 +- .../tgbot/handlers}/users/filters.py | 36 ++-- .../tgbot/handlers}/users/registration.py | 61 +++---- .../tgbot/handlers}/users/start.py | 24 +-- .../tgbot/handlers}/users/support.py | 16 +- .../tgbot/handlers}/users/user_profile.py | 20 +-- .../tgbot/handlers}/users/verification.py | 20 +-- .../tgbot/handlers}/users/view_event.py | 18 +- .../tgbot/handlers}/users/view_ques.py | 40 ++--- .../tgbot/keyboards}/__init__.py | 0 .../tgbot/keyboards}/admin/__init__.py | 0 .../tgbot/keyboards}/admin/inline/__init__.py | 0 .../keyboards}/admin/inline/customers.py | 0 .../tgbot/keyboards}/admin/inline/mailing.py | 0 .../tgbot/keyboards}/admin/inline/payments.py | 0 .../tgbot/keyboards}/admin/inline/ref.py | 0 .../keyboards}/admin/inline/reply_menu.py | 0 .../tgbot/keyboards}/admin/inline/setting.py | 0 .../tgbot/keyboards}/admin/main_menu.py | 0 .../tgbot/keyboards}/default/__init__.py | 0 .../tgbot/keyboards}/default/admin_default.py | 0 .../keyboards}/default/get_contact_default.py | 0 .../default/get_location_default.py | 0 .../tgbot/keyboards}/default/get_photo.py | 0 .../tgbot/keyboards}/inline/__init__.py | 0 .../tgbot/keyboards}/inline/admin_inline.py | 0 .../tgbot/keyboards}/inline/back_inline.py | 0 .../tgbot/keyboards}/inline/calendar.py | 0 .../tgbot/keyboards}/inline/cancel_inline.py | 0 .../inline/change_data_profile_inline.py | 0 .../tgbot/keyboards}/inline/filters_inline.py | 0 .../tgbot/keyboards}/inline/guide_inline.py | 0 .../keyboards}/inline/language_inline.py | 0 .../keyboards}/inline/main_menu_inline.py | 14 +- .../keyboards}/inline/menu_profile_inline.py | 0 .../inline/necessary_links_inline.py | 0 .../keyboards}/inline/payments_inline.py | 0 .../tgbot/keyboards}/inline/poster_inline.py | 4 +- .../inline/questionnaires_inline.py | 0 .../keyboards}/inline/registration_inline.py | 0 .../tgbot/keyboards}/inline/settings_menu.py | 0 .../tgbot/keyboards}/inline/support_inline.py | 12 +- .../tgbot/middlewares}/AgentSupport.py | 0 .../tgbot/middlewares}/BanCheck.py | 11 +- .../tgbot/middlewares}/IsMaintenanceCheck.py | 11 +- .../tgbot/middlewares}/LinkCheck.py | 18 +- {middlewares => src/tgbot/middlewares}/Log.py | 0 .../tgbot/middlewares}/SchedulerWare.py | 4 +- .../tgbot/middlewares}/Throttling.py | 0 src/tgbot/middlewares/__init__.py | 48 ++++++ {states => src/tgbot/services}/__init__.py | 0 .../tgbot/services/app}/AsyncObj.py | 0 .../tgbot/services/app}/__init__.py | 0 .../tgbot/services/app}/app_scheduler.py | 8 +- src/tgbot/services/app/data_operations.py | 24 +++ .../tgbot/services/app}/determin_location.py | 12 +- .../tgbot/services/app}/ds_name.py | 0 .../tgbot/services/app}/language_ware.py | 14 +- {utils => src/tgbot/services/app}/logger.py | 1 + .../tgbot/services/app/message_operations.py | 163 +++--------------- .../tgbot/services/app}/notify_admins.py | 6 +- src/tgbot/services/app/photo_operations.py | 117 +++++++++++++ .../tgbot/services/app}/profanityFilter.py | 0 .../tgbot/services/app}/set_bot_commands.py | 2 +- .../tgbot/services/app/states.py | 22 +++ .../tgbot/services/app}/statistics.py | 2 +- .../tgbot/services/app}/throttling.py | 0 .../tgbot/services}/dating/__init__.py | 0 .../services}/dating/create_forms_funcs.py | 18 +- .../services}/dating/get_next_user_func.py | 2 +- .../services}/dating/reaction_strategies.py | 36 ++-- .../tgbot/services}/dating/send_form_func.py | 22 +-- .../tgbot/services/event}/__init__.py | 0 .../tgbot/services}/event/extra_features.py | 14 +- .../services}/event/templates_messages.py | 14 +- utils/db_api/__init__.py | 0 133 files changed, 689 insertions(+), 585 deletions(-) create mode 100644 src/__init__.py rename {data => src/infrastructure/NudeNet}/__init__.py (100%) rename {utils => src/infrastructure}/NudeNet/predictor.py (61%) rename {functions => src/infrastructure/YandexMap}/__init__.py (100%) rename {utils => src/infrastructure}/YandexMap/api.py (97%) rename {utils => src/infrastructure}/YandexMap/exceptions.py (100%) create mode 100644 src/infrastructure/__init__.py rename {functions/event => src/infrastructure/db_api}/__init__.py (100%) rename {utils => src/infrastructure}/db_api/db_commands.py (99%) rename {functions/main_app => src/infrastructure/jobs}/__init__.py (100%) rename {utils => src/infrastructure}/jobs/autobackup.py (100%) rename {utils => src/infrastructure}/jobs/autoupdate.py (100%) rename {utils => src/infrastructure}/yoomoney/__init__.py (100%) rename {utils => src/infrastructure}/yoomoney/authorization.py (96%) rename {utils => src/infrastructure}/yoomoney/exceptions.py (100%) rename {utils => src/infrastructure}/yoomoney/get_token.py (87%) rename {utils => src/infrastructure}/yoomoney/request.py (96%) rename {utils => src/infrastructure}/yoomoney/types/__init__.py (100%) rename {utils => src/infrastructure}/yoomoney/types/account_info.py (100%) rename {utils => src/infrastructure}/yoomoney/types/operation_details.py (100%) rename {utils => src/infrastructure}/yoomoney/types/operation_history.py (100%) rename {utils => src/infrastructure}/yoomoney/types/payment.py (100%) rename {utils => src/infrastructure}/yoomoney/wallet.py (97%) create mode 100644 src/tgbot/__init__.py rename {data => src/tgbot}/config.py (93%) rename {filters => src/tgbot/filters}/FiltersChat.py (100%) rename {filters => src/tgbot/filters}/IsAdminFilter.py (89%) create mode 100644 src/tgbot/filters/__init__.py rename {handlers => src/tgbot/handlers}/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/advert/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/advert/advertisement.py (87%) rename {handlers => src/tgbot/handlers}/admins/advert/mailing/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/advert/mailing/create.py (98%) rename {handlers => src/tgbot/handlers}/admins/advert/ref/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/advert/requiredsub/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/customers/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/customers/users.py (93%) rename {handlers => src/tgbot/handlers}/admins/monitoring.py (83%) rename {handlers => src/tgbot/handlers}/admins/settings/__init__.py (100%) rename {handlers => src/tgbot/handlers}/admins/settings/admins.py (91%) rename {handlers => src/tgbot/handlers}/admins/settings/logs_user.py (85%) rename {handlers => src/tgbot/handlers}/admins/settings/setting.py (80%) rename {handlers => src/tgbot/handlers}/admins/settings/tech_works.py (87%) rename {handlers => src/tgbot/handlers}/echo_handler.py (95%) rename {handlers => src/tgbot/handlers}/errors/__init__.py (100%) rename {handlers => src/tgbot/handlers}/errors/error_handler.py (100%) rename {handlers => src/tgbot/handlers}/groups/__init__.py (100%) rename {handlers => src/tgbot/handlers}/groups/event_moderate.py (87%) rename {handlers => src/tgbot/handlers}/groups/start.py (84%) rename {handlers => src/tgbot/handlers}/users/__init__.py (100%) rename {handlers => src/tgbot/handlers}/users/back.py (90%) rename {handlers => src/tgbot/handlers}/users/brandbook.py (88%) rename {handlers => src/tgbot/handlers}/users/buy_unban.py (92%) rename {handlers => src/tgbot/handlers}/users/change_datas.py (95%) rename {handlers => src/tgbot/handlers}/users/change_event_datas.py (93%) rename {handlers => src/tgbot/handlers}/users/event.py (95%) rename {handlers => src/tgbot/handlers}/users/event_list.py (92%) rename {handlers => src/tgbot/handlers}/users/filters.py (92%) rename {handlers => src/tgbot/handlers}/users/registration.py (93%) rename {handlers => src/tgbot/handlers}/users/start.py (94%) rename {handlers => src/tgbot/handlers}/users/support.py (96%) rename {handlers => src/tgbot/handlers}/users/user_profile.py (81%) rename {handlers => src/tgbot/handlers}/users/verification.py (90%) rename {handlers => src/tgbot/handlers}/users/view_event.py (95%) rename {handlers => src/tgbot/handlers}/users/view_ques.py (94%) rename {keyboards => src/tgbot/keyboards}/__init__.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/__init__.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/__init__.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/customers.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/mailing.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/payments.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/ref.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/reply_menu.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/inline/setting.py (100%) rename {keyboards => src/tgbot/keyboards}/admin/main_menu.py (100%) rename {keyboards => src/tgbot/keyboards}/default/__init__.py (100%) rename {keyboards => src/tgbot/keyboards}/default/admin_default.py (100%) rename {keyboards => src/tgbot/keyboards}/default/get_contact_default.py (100%) rename {keyboards => src/tgbot/keyboards}/default/get_location_default.py (100%) rename {keyboards => src/tgbot/keyboards}/default/get_photo.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/__init__.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/admin_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/back_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/calendar.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/cancel_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/change_data_profile_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/filters_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/guide_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/language_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/main_menu_inline.py (93%) rename {keyboards => src/tgbot/keyboards}/inline/menu_profile_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/necessary_links_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/payments_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/poster_inline.py (97%) rename {keyboards => src/tgbot/keyboards}/inline/questionnaires_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/registration_inline.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/settings_menu.py (100%) rename {keyboards => src/tgbot/keyboards}/inline/support_inline.py (90%) rename {middlewares => src/tgbot/middlewares}/AgentSupport.py (100%) rename {middlewares => src/tgbot/middlewares}/BanCheck.py (91%) rename {middlewares => src/tgbot/middlewares}/IsMaintenanceCheck.py (91%) rename {middlewares => src/tgbot/middlewares}/LinkCheck.py (88%) rename {middlewares => src/tgbot/middlewares}/Log.py (100%) rename {middlewares => src/tgbot/middlewares}/SchedulerWare.py (86%) rename {middlewares => src/tgbot/middlewares}/Throttling.py (100%) create mode 100644 src/tgbot/middlewares/__init__.py rename {states => src/tgbot/services}/__init__.py (100%) rename {utils/misc => src/tgbot/services/app}/AsyncObj.py (100%) rename {utils/NudeNet => src/tgbot/services/app}/__init__.py (100%) rename {functions/main_app => src/tgbot/services/app}/app_scheduler.py (86%) create mode 100644 src/tgbot/services/app/data_operations.py rename {functions/main_app => src/tgbot/services/app}/determin_location.py (92%) rename {utils/misc => src/tgbot/services/app}/ds_name.py (100%) rename {functions/main_app => src/tgbot/services/app}/language_ware.py (75%) rename {utils => src/tgbot/services/app}/logger.py (75%) rename functions/main_app/auxiliary_tools.py => src/tgbot/services/app/message_operations.py (64%) rename {utils => src/tgbot/services/app}/notify_admins.py (97%) create mode 100644 src/tgbot/services/app/photo_operations.py rename {utils/misc => src/tgbot/services/app}/profanityFilter.py (100%) rename {utils => src/tgbot/services/app}/set_bot_commands.py (97%) rename states/new_data_state.py => src/tgbot/services/app/states.py (51%) rename {utils => src/tgbot/services/app}/statistics.py (97%) rename {utils/misc => src/tgbot/services/app}/throttling.py (100%) rename {functions => src/tgbot/services}/dating/__init__.py (100%) rename {functions => src/tgbot/services}/dating/create_forms_funcs.py (89%) rename {functions => src/tgbot/services}/dating/get_next_user_func.py (97%) rename {functions => src/tgbot/services}/dating/reaction_strategies.py (95%) rename {functions => src/tgbot/services}/dating/send_form_func.py (92%) rename {utils/YandexMap => src/tgbot/services/event}/__init__.py (100%) rename {functions => src/tgbot/services}/event/extra_features.py (93%) rename {functions => src/tgbot/services}/event/templates_messages.py (92%) delete mode 100644 utils/db_api/__init__.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..5086911 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,5 @@ +from . import tgbot + +__all__ = ( + "tgbot", +) diff --git a/data/__init__.py b/src/infrastructure/NudeNet/__init__.py similarity index 100% rename from data/__init__.py rename to src/infrastructure/NudeNet/__init__.py diff --git a/utils/NudeNet/predictor.py b/src/infrastructure/NudeNet/predictor.py similarity index 61% rename from utils/NudeNet/predictor.py rename to src/infrastructure/NudeNet/predictor.py index 165da57..0a8389f 100644 --- a/utils/NudeNet/predictor.py +++ b/src/infrastructure/NudeNet/predictor.py @@ -8,12 +8,12 @@ ) -async def classification_image(image_path: Union[str, pathlib.Path]) -> list[dict]: +async def classification_image(image_path: str | pathlib.Path) -> list[dict]: return detector.detect(image_path) async def generate_censored_image( - image_path: Union[str, pathlib.Path], out_path: Union[str, pathlib.Path] + image_path: str | pathlib.Path, out_path: str | pathlib.Path ) -> None: detector.censor( image_path=image_path, diff --git a/functions/__init__.py b/src/infrastructure/YandexMap/__init__.py similarity index 100% rename from functions/__init__.py rename to src/infrastructure/YandexMap/__init__.py diff --git a/utils/YandexMap/api.py b/src/infrastructure/YandexMap/api.py similarity index 97% rename from utils/YandexMap/api.py rename to src/infrastructure/YandexMap/api.py index 2ee55a2..4d597cb 100644 --- a/utils/YandexMap/api.py +++ b/src/infrastructure/YandexMap/api.py @@ -5,7 +5,7 @@ import aiohttp -from utils.YandexMap.exceptions import ( +from src.infrastructure.YandexMap.exceptions import ( InvalidKey, NothingFound, UnexpectedResponse, diff --git a/utils/YandexMap/exceptions.py b/src/infrastructure/YandexMap/exceptions.py similarity index 100% rename from utils/YandexMap/exceptions.py rename to src/infrastructure/YandexMap/exceptions.py diff --git a/src/infrastructure/__init__.py b/src/infrastructure/__init__.py new file mode 100644 index 0000000..cc09a16 --- /dev/null +++ b/src/infrastructure/__init__.py @@ -0,0 +1,3 @@ +from . import ( + db_api, +) diff --git a/functions/event/__init__.py b/src/infrastructure/db_api/__init__.py similarity index 100% rename from functions/event/__init__.py rename to src/infrastructure/db_api/__init__.py diff --git a/utils/db_api/db_commands.py b/src/infrastructure/db_api/db_commands.py similarity index 99% rename from utils/db_api/db_commands.py rename to src/infrastructure/db_api/db_commands.py index 622f171..47c1818 100644 --- a/utils/db_api/db_commands.py +++ b/src/infrastructure/db_api/db_commands.py @@ -4,6 +4,7 @@ import django django.setup() + from asgiref.sync import ( sync_to_async, ) @@ -11,10 +12,6 @@ F, Q, ) - - - - from django.db.models.expressions import ( CombinedExpression, Value, diff --git a/functions/main_app/__init__.py b/src/infrastructure/jobs/__init__.py similarity index 100% rename from functions/main_app/__init__.py rename to src/infrastructure/jobs/__init__.py diff --git a/utils/jobs/autobackup.py b/src/infrastructure/jobs/autobackup.py similarity index 100% rename from utils/jobs/autobackup.py rename to src/infrastructure/jobs/autobackup.py diff --git a/utils/jobs/autoupdate.py b/src/infrastructure/jobs/autoupdate.py similarity index 100% rename from utils/jobs/autoupdate.py rename to src/infrastructure/jobs/autoupdate.py diff --git a/utils/yoomoney/__init__.py b/src/infrastructure/yoomoney/__init__.py similarity index 100% rename from utils/yoomoney/__init__.py rename to src/infrastructure/yoomoney/__init__.py diff --git a/utils/yoomoney/authorization.py b/src/infrastructure/yoomoney/authorization.py similarity index 96% rename from utils/yoomoney/authorization.py rename to src/infrastructure/yoomoney/authorization.py index b1af35c..0582e15 100644 --- a/utils/yoomoney/authorization.py +++ b/src/infrastructure/yoomoney/authorization.py @@ -1,4 +1,4 @@ -from utils.yoomoney.request import ( +from src.infrastructure.yoomoney.request import ( send_request, ) diff --git a/utils/yoomoney/exceptions.py b/src/infrastructure/yoomoney/exceptions.py similarity index 100% rename from utils/yoomoney/exceptions.py rename to src/infrastructure/yoomoney/exceptions.py diff --git a/utils/yoomoney/get_token.py b/src/infrastructure/yoomoney/get_token.py similarity index 87% rename from utils/yoomoney/get_token.py rename to src/infrastructure/yoomoney/get_token.py index 5437a1b..6b73ff0 100644 --- a/utils/yoomoney/get_token.py +++ b/src/infrastructure/yoomoney/get_token.py @@ -1,9 +1,9 @@ import asyncio -from data.config import ( +from src.tgbot.config import ( load_config, ) -from utils.yoomoney import ( +from src.tgbot.utils.yoomoney import ( authorize_app, ) diff --git a/utils/yoomoney/request.py b/src/infrastructure/yoomoney/request.py similarity index 96% rename from utils/yoomoney/request.py rename to src/infrastructure/yoomoney/request.py index f690eab..d727078 100644 --- a/utils/yoomoney/request.py +++ b/src/infrastructure/yoomoney/request.py @@ -6,7 +6,7 @@ ContentTypeError, ) -from utils.yoomoney.exceptions import ( +from src.infrastructure.yoomoney.exceptions import ( BadResponse, UnresolvedRequestMethod, ) diff --git a/utils/yoomoney/types/__init__.py b/src/infrastructure/yoomoney/types/__init__.py similarity index 100% rename from utils/yoomoney/types/__init__.py rename to src/infrastructure/yoomoney/types/__init__.py diff --git a/utils/yoomoney/types/account_info.py b/src/infrastructure/yoomoney/types/account_info.py similarity index 100% rename from utils/yoomoney/types/account_info.py rename to src/infrastructure/yoomoney/types/account_info.py diff --git a/utils/yoomoney/types/operation_details.py b/src/infrastructure/yoomoney/types/operation_details.py similarity index 100% rename from utils/yoomoney/types/operation_details.py rename to src/infrastructure/yoomoney/types/operation_details.py diff --git a/utils/yoomoney/types/operation_history.py b/src/infrastructure/yoomoney/types/operation_history.py similarity index 100% rename from utils/yoomoney/types/operation_history.py rename to src/infrastructure/yoomoney/types/operation_history.py diff --git a/utils/yoomoney/types/payment.py b/src/infrastructure/yoomoney/types/payment.py similarity index 100% rename from utils/yoomoney/types/payment.py rename to src/infrastructure/yoomoney/types/payment.py diff --git a/utils/yoomoney/wallet.py b/src/infrastructure/yoomoney/wallet.py similarity index 97% rename from utils/yoomoney/wallet.py rename to src/infrastructure/yoomoney/wallet.py index b7603d4..fbe28b7 100644 --- a/utils/yoomoney/wallet.py +++ b/src/infrastructure/yoomoney/wallet.py @@ -1,7 +1,7 @@ -from utils.yoomoney.request import ( +from src.infrastructure.yoomoney.request import ( send_request, ) -from utils.yoomoney.types import ( +from src.infrastructure.yoomoney.types import ( AccountInfo, Operation, OperationDetails, diff --git a/src/tgbot/__init__.py b/src/tgbot/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/tgbot/__init__.py @@ -0,0 +1 @@ + diff --git a/data/config.py b/src/tgbot/config.py similarity index 93% rename from data/config.py rename to src/tgbot/config.py index 1e42f74..8bee98b 100644 --- a/data/config.py +++ b/src/tgbot/config.py @@ -23,7 +23,6 @@ env.read_env() -# The frozen=True arg protects instances of the class from accidental modification @dataclass(frozen=True, slots=True) class DataBaseConfig: user: str @@ -36,8 +35,8 @@ class DataBaseConfig: @dataclass(frozen=True, slots=True) class TgBot: token: str - admin_ids: List[int] - support_ids: List[int] + admin_ids: list[int] + support_ids: list[int] timezone: str ip: str I18N_DOMAIN: str @@ -117,5 +116,5 @@ def load_config() -> Config: # TODO: Move to dataclass -BASE_DIR = Path(__file__).parent.parent +BASE_DIR = Path(__file__).parent.parent.parent LOCALES_DIR = BASE_DIR / "locales" diff --git a/filters/FiltersChat.py b/src/tgbot/filters/FiltersChat.py similarity index 100% rename from filters/FiltersChat.py rename to src/tgbot/filters/FiltersChat.py diff --git a/filters/IsAdminFilter.py b/src/tgbot/filters/IsAdminFilter.py similarity index 89% rename from filters/IsAdminFilter.py rename to src/tgbot/filters/IsAdminFilter.py index c3191a8..480629d 100644 --- a/filters/IsAdminFilter.py +++ b/src/tgbot/filters/IsAdminFilter.py @@ -5,7 +5,7 @@ BoundFilter, ) -from data.config import ( +from src.tgbot.config import ( load_config, ) diff --git a/src/tgbot/filters/__init__.py b/src/tgbot/filters/__init__.py new file mode 100644 index 0000000..c53cc1c --- /dev/null +++ b/src/tgbot/filters/__init__.py @@ -0,0 +1,23 @@ +from src.tgbot.filters.FiltersChat import ( + IsPrivate, + IsGroup, +) + +from src.tgbot.filters.IsAdminFilter import ( + IsAdmin +) + +__all__ = ( + "IsGroup", + "IsPrivate", + "IsAdmin", +) + +# def setup(dp: Dispatcher): +# logger.info("Подключение filters...") +# text_messages = [ +# dp.message_handlers, +# dp.edited_message_handlers, +# ] +# logger.info(text_messages) +# dp.filters_factory.bind(IsPrivate) diff --git a/handlers/__init__.py b/src/tgbot/handlers/__init__.py similarity index 100% rename from handlers/__init__.py rename to src/tgbot/handlers/__init__.py index 5ca811f..351e76a 100644 --- a/handlers/__init__.py +++ b/src/tgbot/handlers/__init__.py @@ -1,6 +1,6 @@ from . import ( - errors, admins, + errors, groups, users, echo_handler, diff --git a/handlers/admins/__init__.py b/src/tgbot/handlers/admins/__init__.py similarity index 100% rename from handlers/admins/__init__.py rename to src/tgbot/handlers/admins/__init__.py diff --git a/handlers/admins/advert/__init__.py b/src/tgbot/handlers/admins/advert/__init__.py similarity index 100% rename from handlers/admins/advert/__init__.py rename to src/tgbot/handlers/admins/advert/__init__.py diff --git a/handlers/admins/advert/advertisement.py b/src/tgbot/handlers/admins/advert/advertisement.py similarity index 87% rename from handlers/admins/advert/advertisement.py rename to src/tgbot/handlers/admins/advert/advertisement.py index 1db9d64..f77816c 100644 --- a/handlers/admins/advert/advertisement.py +++ b/src/tgbot/handlers/admins/advert/advertisement.py @@ -6,19 +6,19 @@ Message, ) -from filters.IsAdminFilter import ( +from loader import ( + _, + dp, +) +from src.tgbot.filters.IsAdminFilter import ( IsAdmin, ) -from keyboards.admin.inline.mailing import ( +from src.tgbot.keyboards.admin.inline.mailing import ( mailing_menu, ) -from keyboards.inline.cancel_inline import ( +from src.tgbot.keyboards.inline.cancel_inline import ( cancel_keyboard, ) -from loader import ( - _, - dp, -) @dp.message_handler(IsAdmin(), commands="ad", state="*") diff --git a/handlers/admins/advert/mailing/__init__.py b/src/tgbot/handlers/admins/advert/mailing/__init__.py similarity index 100% rename from handlers/admins/advert/mailing/__init__.py rename to src/tgbot/handlers/admins/advert/mailing/__init__.py diff --git a/handlers/admins/advert/mailing/create.py b/src/tgbot/handlers/admins/advert/mailing/create.py similarity index 98% rename from handlers/admins/advert/mailing/create.py rename to src/tgbot/handlers/admins/advert/mailing/create.py index b549471..1b07c29 100644 --- a/handlers/admins/advert/mailing/create.py +++ b/src/tgbot/handlers/admins/advert/mailing/create.py @@ -16,22 +16,22 @@ quote_html, ) -from filters.IsAdminFilter import ( - IsAdmin, -) -from keyboards.admin.inline.mailing import ( - add_buttons_keyboard, - confirm_with_button_keyboard, -) from loader import ( _, bot, dp, logger, ) -from utils.db_api import ( +from src.tgbot.filters.IsAdminFilter import ( + IsAdmin, +) +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.admin.inline.mailing import ( + add_buttons_keyboard, + confirm_with_button_keyboard, +) @dp.message_handler(IsAdmin(), content_types=["text"], state="broadcast_get_content") diff --git a/handlers/admins/advert/ref/__init__.py b/src/tgbot/handlers/admins/advert/ref/__init__.py similarity index 100% rename from handlers/admins/advert/ref/__init__.py rename to src/tgbot/handlers/admins/advert/ref/__init__.py diff --git a/handlers/admins/advert/requiredsub/__init__.py b/src/tgbot/handlers/admins/advert/requiredsub/__init__.py similarity index 100% rename from handlers/admins/advert/requiredsub/__init__.py rename to src/tgbot/handlers/admins/advert/requiredsub/__init__.py diff --git a/handlers/admins/customers/__init__.py b/src/tgbot/handlers/admins/customers/__init__.py similarity index 100% rename from handlers/admins/customers/__init__.py rename to src/tgbot/handlers/admins/customers/__init__.py diff --git a/handlers/admins/customers/users.py b/src/tgbot/handlers/admins/customers/users.py similarity index 93% rename from handlers/admins/customers/users.py rename to src/tgbot/handlers/admins/customers/users.py index dd97404..46ef3e7 100644 --- a/handlers/admins/customers/users.py +++ b/src/tgbot/handlers/admins/customers/users.py @@ -6,23 +6,23 @@ Message, ) -from filters.IsAdminFilter import ( +from loader import ( + dp, +) +from src.tgbot.filters.IsAdminFilter import ( IsAdmin, ) -from keyboards.admin.inline.customers import ( +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.keyboards.admin.inline.customers import ( manipulation_callback, user_blocking_keyboard, user_manipulation, ) -from keyboards.admin.inline.reply_menu import ( +from src.tgbot.keyboards.admin.inline.reply_menu import ( admin_cancel_keyboard, ) -from loader import ( - dp, -) -from utils.db_api import ( - db_commands, -) @dp.message_handler(IsAdmin(), commands="users", state="*") diff --git a/handlers/admins/monitoring.py b/src/tgbot/handlers/admins/monitoring.py similarity index 83% rename from handlers/admins/monitoring.py rename to src/tgbot/handlers/admins/monitoring.py index 3f874bb..96d8b38 100644 --- a/handlers/admins/monitoring.py +++ b/src/tgbot/handlers/admins/monitoring.py @@ -2,24 +2,24 @@ types, ) -from filters.IsAdminFilter import ( +from loader import ( + _, + dp, +) +from src.tgbot.filters.IsAdminFilter import ( IsAdmin, ) -from functions.dating.create_forms_funcs import ( - monitoring_questionnaire, +from src.infrastructure.db_api import ( + db_commands, ) -from keyboards.inline.admin_inline import ( +from src.tgbot.keyboards.inline.admin_inline import ( start_monitoring_keyboard, ) -from keyboards.inline.questionnaires_inline import ( +from src.tgbot.keyboards.inline.questionnaires_inline import ( action_keyboard_monitoring, ) -from loader import ( - _, - dp, -) -from utils.db_api import ( - db_commands, +from src.tgbot.services.dating.create_forms_funcs import ( + monitoring_questionnaire, ) diff --git a/handlers/admins/settings/__init__.py b/src/tgbot/handlers/admins/settings/__init__.py similarity index 100% rename from handlers/admins/settings/__init__.py rename to src/tgbot/handlers/admins/settings/__init__.py diff --git a/handlers/admins/settings/admins.py b/src/tgbot/handlers/admins/settings/admins.py similarity index 91% rename from handlers/admins/settings/admins.py rename to src/tgbot/handlers/admins/settings/admins.py index 9b1ef49..b825dd6 100644 --- a/handlers/admins/settings/admins.py +++ b/src/tgbot/handlers/admins/settings/admins.py @@ -6,29 +6,27 @@ Message, ) -from data.config import ( +from loader import ( + dp, +) +from src.tgbot.config import ( change_env, load_config, ) -from filters.IsAdminFilter import ( +from src.tgbot.filters.IsAdminFilter import ( IsAdmin, ) -from keyboards.admin.inline.reply_menu import ( +from src.tgbot.services.app.set_bot_commands import ( + set_default_commands, +) +from src.tgbot.keyboards.admin.inline.reply_menu import ( admin_cancel_keyboard, settings_keyboard, ) -from keyboards.admin.inline.setting import ( +from src.tgbot.keyboards.admin.inline.setting import ( add_admins_keyboard, ) -from loader import ( - dp, -) -from states.admins import ( - AdminsActions, -) -from utils.set_bot_commands import ( - set_default_commands, -) +from src.tgbot.services.app.states import AdminsActions @dp.callback_query_handler(IsAdmin(), text="admin:admins") diff --git a/handlers/admins/settings/logs_user.py b/src/tgbot/handlers/admins/settings/logs_user.py similarity index 85% rename from handlers/admins/settings/logs_user.py rename to src/tgbot/handlers/admins/settings/logs_user.py index ae9d525..2220e38 100644 --- a/handlers/admins/settings/logs_user.py +++ b/src/tgbot/handlers/admins/settings/logs_user.py @@ -7,21 +7,21 @@ Message, ) -from filters.IsAdminFilter import ( - IsAdmin, +from loader import ( + dp, ) -from functions.main_app.auxiliary_tools import ( - backup_configs, - dump_users_to_file, +from src.tgbot.filters.IsAdminFilter import ( + IsAdmin, ) -from handlers.users.back import ( +from src.tgbot.handlers.users.back import ( delete_message, ) -from keyboards.admin.inline.reply_menu import ( +from src.tgbot.keyboards.admin.inline.reply_menu import ( logs_keyboard, ) -from loader import ( - dp, +from src.tgbot.services.app.data_operations import ( + backup_configs, + dump_users_to_file, ) diff --git a/handlers/admins/settings/setting.py b/src/tgbot/handlers/admins/settings/setting.py similarity index 80% rename from handlers/admins/settings/setting.py rename to src/tgbot/handlers/admins/settings/setting.py index c923d43..17f9a5c 100644 --- a/handlers/admins/settings/setting.py +++ b/src/tgbot/handlers/admins/settings/setting.py @@ -2,15 +2,15 @@ Message, ) -from filters.IsAdminFilter import ( +from loader import ( + dp, +) +from src.tgbot.filters.IsAdminFilter import ( IsAdmin, ) -from keyboards.admin.inline.reply_menu import ( +from src.tgbot.keyboards.admin.inline.reply_menu import ( settings_keyboard, ) -from loader import ( - dp, -) @dp.message_handler(IsAdmin(), commands="settings", state="*") diff --git a/handlers/admins/settings/tech_works.py b/src/tgbot/handlers/admins/settings/tech_works.py similarity index 87% rename from handlers/admins/settings/tech_works.py rename to src/tgbot/handlers/admins/settings/tech_works.py index 1b08562..86482ef 100644 --- a/handlers/admins/settings/tech_works.py +++ b/src/tgbot/handlers/admins/settings/tech_works.py @@ -6,23 +6,23 @@ Message, ) -from filters.IsAdminFilter import ( - IsAdmin, -) -from keyboards.admin.main_menu import ( - admin_keyboard, -) -from keyboards.inline.admin_inline import ( - tech_works_keyboard, -) from loader import ( _, dp, ) -from utils.db_api import ( +from src.tgbot.filters.IsAdminFilter import ( + IsAdmin, +) +from src.infrastructure.db_api import ( db_commands, ) -from utils.statistics import ( +from src.tgbot.keyboards.admin.main_menu import ( + admin_keyboard, +) +from src.tgbot.keyboards.inline.admin_inline import ( + tech_works_keyboard, +) +from src.tgbot.services.app.statistics import ( get_statistics, ) diff --git a/handlers/echo_handler.py b/src/tgbot/handlers/echo_handler.py similarity index 95% rename from handlers/echo_handler.py rename to src/tgbot/handlers/echo_handler.py index 2e21b4a..42a8a2a 100644 --- a/handlers/echo_handler.py +++ b/src/tgbot/handlers/echo_handler.py @@ -11,14 +11,14 @@ hcode, ) -from keyboards.inline.main_menu_inline import ( - start_keyboard, -) from loader import ( _, dp, logger, ) +from src.tgbot.keyboards.inline.main_menu_inline import ( + start_keyboard, +) @dp.message_handler(state=None) diff --git a/handlers/errors/__init__.py b/src/tgbot/handlers/errors/__init__.py similarity index 100% rename from handlers/errors/__init__.py rename to src/tgbot/handlers/errors/__init__.py diff --git a/handlers/errors/error_handler.py b/src/tgbot/handlers/errors/error_handler.py similarity index 100% rename from handlers/errors/error_handler.py rename to src/tgbot/handlers/errors/error_handler.py diff --git a/handlers/groups/__init__.py b/src/tgbot/handlers/groups/__init__.py similarity index 100% rename from handlers/groups/__init__.py rename to src/tgbot/handlers/groups/__init__.py diff --git a/handlers/groups/event_moderate.py b/src/tgbot/handlers/groups/event_moderate.py similarity index 87% rename from handlers/groups/event_moderate.py rename to src/tgbot/handlers/groups/event_moderate.py index d9fff25..4cb0038 100644 --- a/handlers/groups/event_moderate.py +++ b/src/tgbot/handlers/groups/event_moderate.py @@ -4,26 +4,26 @@ CallbackQuery, ) -from data.config import ( +from loader import ( + _, + bot, + dp, +) +from src.tgbot.config import ( load_config, ) -from filters.IsAdminFilter import ( +from src.tgbot.filters.IsAdminFilter import ( IsAdmin, ) -from keyboards.inline.main_menu_inline import ( +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from keyboards.inline.poster_inline import ( +from src.tgbot.keyboards.inline.poster_inline import ( poster_keyboard, ) -from loader import ( - _, - bot, - dp, -) -from utils.db_api import ( - db_commands, -) # FIXME: Broken handler diff --git a/handlers/groups/start.py b/src/tgbot/handlers/groups/start.py similarity index 84% rename from handlers/groups/start.py rename to src/tgbot/handlers/groups/start.py index 3970545..ac7a9ca 100644 --- a/handlers/groups/start.py +++ b/src/tgbot/handlers/groups/start.py @@ -5,16 +5,16 @@ Message, ) -from filters.FiltersChat import ( - IsGroup, -) -from filters.IsAdminFilter import ( - IsAdmin, -) from loader import ( _, dp, ) +from src.tgbot.filters.FiltersChat import ( + IsGroup, +) +from src.tgbot.filters.IsAdminFilter import ( + IsAdmin, +) @dp.message_handler(IsGroup(), IsAdmin(), Command("start")) diff --git a/handlers/users/__init__.py b/src/tgbot/handlers/users/__init__.py similarity index 100% rename from handlers/users/__init__.py rename to src/tgbot/handlers/users/__init__.py diff --git a/handlers/users/back.py b/src/tgbot/handlers/users/back.py similarity index 90% rename from handlers/users/back.py rename to src/tgbot/handlers/users/back.py index 0f0e79e..a52d084 100644 --- a/handlers/users/back.py +++ b/src/tgbot/handlers/users/back.py @@ -10,31 +10,31 @@ CallbackQuery, ) -from functions.main_app.auxiliary_tools import ( - delete_message, - display_profile, - information_menu, - registration_menu, +from loader import ( + _, + dp, ) -from handlers.users.event import ( +from src.tgbot.handlers.users.event import ( view_meetings_handler, view_own_event, ) -from keyboards.inline.admin_inline import ( +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.keyboards.inline.admin_inline import ( unban_user_keyboard, ) -from keyboards.inline.filters_inline import ( +from src.tgbot.keyboards.inline.filters_inline import ( filters_keyboard, ) -from keyboards.inline.menu_profile_inline import ( +from src.tgbot.keyboards.inline.menu_profile_inline import ( get_profile_keyboard, ) -from loader import ( - _, - dp, -) -from utils.db_api import ( - db_commands, +from src.tgbot.services.app.message_operations import ( + delete_message, + display_profile, + information_menu, + registration_menu, ) diff --git a/handlers/users/brandbook.py b/src/tgbot/handlers/users/brandbook.py similarity index 88% rename from handlers/users/brandbook.py rename to src/tgbot/handlers/users/brandbook.py index c4d1a74..511839e 100644 --- a/handlers/users/brandbook.py +++ b/src/tgbot/handlers/users/brandbook.py @@ -2,20 +2,20 @@ CallbackQuery, ) -from functions.main_app.auxiliary_tools import ( - handle_guide_callback, - information_menu, - send_photo_with_caption, +from loader import ( + _, + dp, ) -from keyboards.inline.back_inline import ( +from src.tgbot.keyboards.inline.back_inline import ( only_back_keyboard, ) -from keyboards.inline.guide_inline import ( +from src.tgbot.keyboards.inline.guide_inline import ( guide_callback, ) -from loader import ( - _, - dp, +from src.tgbot.services.app.message_operations import ( + handle_guide_callback, + information_menu, + send_photo_with_caption, ) diff --git a/handlers/users/buy_unban.py b/src/tgbot/handlers/users/buy_unban.py similarity index 92% rename from handlers/users/buy_unban.py rename to src/tgbot/handlers/users/buy_unban.py index fd3791d..71b2ab5 100644 --- a/handlers/users/buy_unban.py +++ b/src/tgbot/handlers/users/buy_unban.py @@ -7,27 +7,27 @@ CallbackQuery, ) -from data.config import ( - load_config, -) -from keyboards.inline.main_menu_inline import ( - start_keyboard, -) -from keyboards.inline.payments_inline import ( - payment_menu_keyboard, - yoomoney_keyboard, -) from loader import ( _, dp, wallet, ) -from utils.db_api import ( +from src.tgbot.config import ( + load_config, +) +from src.infrastructure.db_api import ( db_commands, ) -from utils.yoomoney.types import ( +from src.infrastructure.yoomoney.types import ( PaymentSource, ) +from src.tgbot.keyboards.inline.main_menu_inline import ( + start_keyboard, +) +from src.tgbot.keyboards.inline.payments_inline import ( + payment_menu_keyboard, + yoomoney_keyboard, +) @dp.callback_query_handler(text="unban") diff --git a/handlers/users/change_datas.py b/src/tgbot/handlers/users/change_datas.py similarity index 95% rename from handlers/users/change_datas.py rename to src/tgbot/handlers/users/change_datas.py index a0a1818..5a0e9e3 100644 --- a/handlers/users/change_datas.py +++ b/src/tgbot/handlers/users/change_datas.py @@ -19,48 +19,46 @@ DataError, ) -from functions.main_app.auxiliary_tools import ( - saving_censored_photo, - update_normal_photo, +from src.infrastructure.NudeNet.predictor import ( + classification_image, + generate_censored_image, ) -from functions.main_app.determin_location import ( - Location, - RegistrationStrategy, +from src.infrastructure.YandexMap.exceptions import ( + NothingFound, ) -from handlers.users.back import ( +from src.infrastructure.db_api import ( + db_commands, +) +from loader import ( + _, + dp, + logger, +) +from src.tgbot.handlers.users.back import ( delete_message, ) -from keyboards.default.get_photo import ( +from src.tgbot.keyboards.default.get_photo import ( get_photo_from_profile, ) -from keyboards.inline.change_data_profile_inline import ( +from src.tgbot.keyboards.inline.change_data_profile_inline import ( change_info_keyboard, gender_keyboard, ) -from keyboards.inline.main_menu_inline import ( +from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from loader import ( - _, - dp, - logger, -) -from states.new_data_state import ( - NewData, -) -from utils.NudeNet.predictor import ( - classification_image, - generate_censored_image, -) -from utils.YandexMap.exceptions import ( - NothingFound, +from src.tgbot.services.app.determin_location import ( + Location, + RegistrationStrategy, ) -from utils.db_api import ( - db_commands, +from src.tgbot.services.app.photo_operations import ( + saving_censored_photo, + update_normal_photo, ) -from utils.misc.profanityFilter import ( +from src.tgbot.services.app.profanityFilter import ( censored_message, ) +from src.tgbot.services.app.states import NewData @dp.callback_query_handler(text="change_profile") diff --git a/handlers/users/change_event_datas.py b/src/tgbot/handlers/users/change_event_datas.py similarity index 93% rename from handlers/users/change_event_datas.py rename to src/tgbot/handlers/users/change_event_datas.py index 6a4a166..4101608 100644 --- a/handlers/users/change_event_datas.py +++ b/src/tgbot/handlers/users/change_event_datas.py @@ -8,19 +8,19 @@ Message, ) -from handlers.users.back import ( - delete_message, -) -from keyboards.inline.poster_inline import ( - change_datas_keyboard, -) from loader import ( _, dp, ) -from utils.db_api import ( +from src.tgbot.handlers.users.back import ( + delete_message, +) +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.inline.poster_inline import ( + change_datas_keyboard, +) @dp.callback_query_handler(text="change_event_data") diff --git a/handlers/users/event.py b/src/tgbot/handlers/users/event.py similarity index 95% rename from handlers/users/event.py rename to src/tgbot/handlers/users/event.py index df7d155..a7fb45a 100644 --- a/handlers/users/event.py +++ b/src/tgbot/handlers/users/event.py @@ -20,39 +20,39 @@ DataError, ) -from data.config import ( - load_config, +from loader import ( + _, + bot, + dp, + logger, ) -from functions.event.extra_features import ( - check_event_date, +from src.tgbot.config import ( + load_config, ) -from functions.event.templates_messages import ( - ME, +from src.infrastructure.YandexMap.exceptions import ( + NothingFound, ) -from functions.main_app.determin_location import ( - EventStrategy, - Location, +from src.infrastructure.db_api import ( + db_commands, ) -from keyboards.inline.calendar import ( +from src.tgbot.keyboards.inline.calendar import ( SimpleCalendar, calendar_callback, search_cb, ) -from keyboards.inline.poster_inline import ( +from src.tgbot.keyboards.inline.poster_inline import ( cancel_registration_keyboard, poster_keyboard, ) -from loader import ( - _, - bot, - dp, - logger, +from src.tgbot.services.app.determin_location import ( + EventStrategy, + Location, ) -from utils.YandexMap.exceptions import ( - NothingFound, +from src.tgbot.services.event.extra_features import ( + check_event_date, ) -from utils.db_api import ( - db_commands, +from src.tgbot.services.event.templates_messages import ( + ME, ) diff --git a/handlers/users/event_list.py b/src/tgbot/handlers/users/event_list.py similarity index 92% rename from handlers/users/event_list.py rename to src/tgbot/handlers/users/event_list.py index 2b2e63d..308a95c 100644 --- a/handlers/users/event_list.py +++ b/src/tgbot/handlers/users/event_list.py @@ -7,17 +7,17 @@ CallbackQuery, ) -from functions.event.extra_features import ( - create_form, - get_next_registration, -) from loader import ( _, dp, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.services.event.extra_features import ( + create_form, + get_next_registration, +) @dp.callback_query_handler(text="my_appointment") diff --git a/handlers/users/filters.py b/src/tgbot/handlers/users/filters.py similarity index 92% rename from handlers/users/filters.py rename to src/tgbot/handlers/users/filters.py index 6767bd7..83838b8 100644 --- a/handlers/users/filters.py +++ b/src/tgbot/handlers/users/filters.py @@ -14,33 +14,33 @@ BadRequest, ) -from functions.main_app.auxiliary_tools import ( - choice_gender, - show_dating_filters, -) -from functions.main_app.determin_location import ( - FiltersStrategy, - Location, +from loader import ( + _, + dp, ) -from handlers.users.back import ( +from src.tgbot.handlers.users.back import ( delete_message, ) -from keyboards.inline.change_data_profile_inline import ( +from src.infrastructure.YandexMap.exceptions import ( + NothingFound, +) +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.keyboards.inline.change_data_profile_inline import ( gender_keyboard, ) -from keyboards.inline.filters_inline import ( +from src.tgbot.keyboards.inline.filters_inline import ( event_filters_keyboard, filters_keyboard, ) -from loader import ( - _, - dp, -) -from utils.YandexMap.exceptions import ( - NothingFound, +from src.tgbot.services.app.determin_location import ( + FiltersStrategy, + Location, ) -from utils.db_api import ( - db_commands, +from src.tgbot.services.app.message_operations import ( + choice_gender, + show_dating_filters, ) diff --git a/handlers/users/registration.py b/src/tgbot/handlers/users/registration.py similarity index 93% rename from handlers/users/registration.py rename to src/tgbot/handlers/users/registration.py index 71b879c..a9a4a3a 100644 --- a/handlers/users/registration.py +++ b/src/tgbot/handlers/users/registration.py @@ -23,52 +23,53 @@ DataError, ) -from functions.main_app.auxiliary_tools import ( - choice_gender, - saving_censored_photo, - saving_normal_photo, +from loader import ( + _, + client, + dp, + logger, ) -from functions.main_app.determin_location import ( - Location, - RegistrationStrategy, +from src.infrastructure.NudeNet.predictor import ( + classification_image, + generate_censored_image, +) +from src.infrastructure.YandexMap.exceptions import ( + NothingFound, ) -from keyboards.default.get_location_default import ( +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.keyboards.default.get_location_default import ( location_keyboard, ) -from keyboards.default.get_photo import ( +from src.tgbot.keyboards.default.get_photo import ( get_photo_from_profile, ) -from keyboards.inline.cancel_inline import ( +from src.tgbot.keyboards.inline.cancel_inline import ( cancel_registration_keyboard, ) -from keyboards.inline.change_data_profile_inline import ( +from src.tgbot.keyboards.inline.change_data_profile_inline import ( gender_keyboard, ) -from keyboards.inline.registration_inline import ( +from src.tgbot.keyboards.inline.registration_inline import ( second_registration_keyboard, ) -from loader import ( - _, - client, - dp, - logger, -) -from states.reg_state import ( - RegData, -) -from utils.NudeNet.predictor import ( - classification_image, - generate_censored_image, + +from src.tgbot.services.app.profanityFilter import ( + censored_message, ) -from utils.YandexMap.exceptions import ( - NothingFound, +from src.tgbot.services.app.determin_location import ( + Location, + RegistrationStrategy, ) -from utils.db_api import ( - db_commands, +from src.tgbot.services.app.message_operations import ( + choice_gender, ) -from utils.misc.profanityFilter import ( - censored_message, +from src.tgbot.services.app.photo_operations import ( + saving_censored_photo, + saving_normal_photo, ) +from src.tgbot.services.app.states import RegData @dp.callback_query_handler(text="registration") diff --git a/handlers/users/start.py b/src/tgbot/handlers/users/start.py similarity index 94% rename from handlers/users/start.py rename to src/tgbot/handlers/users/start.py index 304245f..8b50e28 100644 --- a/handlers/users/start.py +++ b/src/tgbot/handlers/users/start.py @@ -12,24 +12,24 @@ BadRequest, ) -from filters import ( - IsPrivate, -) -from functions.main_app.auxiliary_tools import ( - check_user_in_db, - delete_message, - registration_menu, -) -from keyboards.inline.language_inline import ( - language_keyboard, -) from loader import ( _, dp, ) -from utils.db_api import ( +from src.tgbot.filters import ( + IsPrivate, +) +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.inline.language_inline import ( + language_keyboard, +) +from src.tgbot.services.app.message_operations import ( + check_user_in_db, + delete_message, + registration_menu, +) @dp.message_handler(IsPrivate(), CommandStart()) diff --git a/handlers/users/support.py b/src/tgbot/handlers/users/support.py similarity index 96% rename from handlers/users/support.py rename to src/tgbot/handlers/users/support.py index 71a748a..74a1ad1 100644 --- a/handlers/users/support.py +++ b/src/tgbot/handlers/users/support.py @@ -8,13 +8,18 @@ BadRequest, ) -from handlers.users.back import ( +from loader import ( + _, + bot, + dp, +) +from src.tgbot.handlers.users.back import ( delete_message, ) -from keyboards.inline.main_menu_inline import ( +from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from keyboards.inline.support_inline import ( +from src.tgbot.keyboards.inline.support_inline import ( cancel_support, cancel_support_callback, check_support_available, @@ -22,11 +27,6 @@ support_callback, support_keyboard, ) -from loader import ( - _, - bot, - dp, -) @dp.callback_query_handler(text="support") diff --git a/handlers/users/user_profile.py b/src/tgbot/handlers/users/user_profile.py similarity index 81% rename from handlers/users/user_profile.py rename to src/tgbot/handlers/users/user_profile.py index a9c5e37..3e4ab22 100644 --- a/handlers/users/user_profile.py +++ b/src/tgbot/handlers/users/user_profile.py @@ -2,22 +2,22 @@ CallbackQuery, ) -from functions.main_app.auxiliary_tools import ( - display_profile, -) -from handlers.users.back import ( - delete_message, -) -from keyboards.inline.menu_profile_inline import ( - get_profile_keyboard, -) from loader import ( _, dp, ) -from utils.db_api import ( +from src.tgbot.handlers.users.back import ( + delete_message, +) +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.inline.menu_profile_inline import ( + get_profile_keyboard, +) +from src.tgbot.services.app.message_operations import ( + display_profile, +) @dp.callback_query_handler(text="my_profile") diff --git a/handlers/users/verification.py b/src/tgbot/handlers/users/verification.py similarity index 90% rename from handlers/users/verification.py rename to src/tgbot/handlers/users/verification.py index 710f34a..f9ec80a 100644 --- a/handlers/users/verification.py +++ b/src/tgbot/handlers/users/verification.py @@ -8,22 +8,22 @@ ReplyKeyboardRemove, ) -from handlers.users.back import ( - delete_message, -) -from keyboards.default.get_contact_default import ( - contact_keyboard, -) -from keyboards.inline.main_menu_inline import ( - start_keyboard, -) from loader import ( _, dp, ) -from utils.db_api import ( +from src.tgbot.handlers.users.back import ( + delete_message, +) +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.default.get_contact_default import ( + contact_keyboard, +) +from src.tgbot.keyboards.inline.main_menu_inline import ( + start_keyboard, +) @dp.callback_query_handler(text="verification") diff --git a/handlers/users/view_event.py b/src/tgbot/handlers/users/view_event.py similarity index 95% rename from handlers/users/view_event.py rename to src/tgbot/handlers/users/view_event.py index 26abfe3..8271048 100644 --- a/handlers/users/view_event.py +++ b/src/tgbot/handlers/users/view_event.py @@ -5,20 +5,20 @@ CallbackQuery, ) -from functions.event.extra_features import ( - add_events_to_user, - check_event_date, - create_form, - get_next_random_event_id, -) -from keyboards.inline.poster_inline import ( - poster_keyboard, -) from loader import ( _, bot, dp, ) +from src.tgbot.keyboards.inline.poster_inline import ( + poster_keyboard, +) +from src.tgbot.services.event.extra_features import ( + add_events_to_user, + check_event_date, + create_form, + get_next_random_event_id, +) @dp.callback_query_handler(text="view_poster") diff --git a/handlers/users/view_ques.py b/src/tgbot/handlers/users/view_ques.py similarity index 94% rename from handlers/users/view_ques.py rename to src/tgbot/handlers/users/view_ques.py index d138038..cf16ef5 100644 --- a/handlers/users/view_ques.py +++ b/src/tgbot/handlers/users/view_ques.py @@ -8,10 +8,27 @@ IntegrityError, ) -from data.config import ( +from loader import ( + _, + bot, + dp, + logger, +) +from src.tgbot.config import ( load_config, ) -from functions.dating import ( +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.keyboards.inline.questionnaires_inline import ( + action_keyboard, + action_reciprocity_keyboard, + action_report_keyboard, +) +from src.tgbot.services.app.message_operations import ( + delete_message, +) +from src.tgbot.services.dating import ( ChooseReportReason, DislikeAction, DislikeReciprocity, @@ -24,26 +41,9 @@ StartFindingSuccess, StoppedAction, ) -from functions.dating.get_next_user_func import ( +from src.tgbot.services.dating.get_next_user_func import ( get_next_user, ) -from functions.main_app.auxiliary_tools import ( - delete_message, -) -from keyboards.inline.questionnaires_inline import ( - action_keyboard, - action_reciprocity_keyboard, - action_report_keyboard, -) -from loader import ( - _, - bot, - dp, - logger, -) -from utils.db_api import ( - db_commands, -) @dp.callback_query_handler(text="find_ques") diff --git a/keyboards/__init__.py b/src/tgbot/keyboards/__init__.py similarity index 100% rename from keyboards/__init__.py rename to src/tgbot/keyboards/__init__.py diff --git a/keyboards/admin/__init__.py b/src/tgbot/keyboards/admin/__init__.py similarity index 100% rename from keyboards/admin/__init__.py rename to src/tgbot/keyboards/admin/__init__.py diff --git a/keyboards/admin/inline/__init__.py b/src/tgbot/keyboards/admin/inline/__init__.py similarity index 100% rename from keyboards/admin/inline/__init__.py rename to src/tgbot/keyboards/admin/inline/__init__.py diff --git a/keyboards/admin/inline/customers.py b/src/tgbot/keyboards/admin/inline/customers.py similarity index 100% rename from keyboards/admin/inline/customers.py rename to src/tgbot/keyboards/admin/inline/customers.py diff --git a/keyboards/admin/inline/mailing.py b/src/tgbot/keyboards/admin/inline/mailing.py similarity index 100% rename from keyboards/admin/inline/mailing.py rename to src/tgbot/keyboards/admin/inline/mailing.py diff --git a/keyboards/admin/inline/payments.py b/src/tgbot/keyboards/admin/inline/payments.py similarity index 100% rename from keyboards/admin/inline/payments.py rename to src/tgbot/keyboards/admin/inline/payments.py diff --git a/keyboards/admin/inline/ref.py b/src/tgbot/keyboards/admin/inline/ref.py similarity index 100% rename from keyboards/admin/inline/ref.py rename to src/tgbot/keyboards/admin/inline/ref.py diff --git a/keyboards/admin/inline/reply_menu.py b/src/tgbot/keyboards/admin/inline/reply_menu.py similarity index 100% rename from keyboards/admin/inline/reply_menu.py rename to src/tgbot/keyboards/admin/inline/reply_menu.py diff --git a/keyboards/admin/inline/setting.py b/src/tgbot/keyboards/admin/inline/setting.py similarity index 100% rename from keyboards/admin/inline/setting.py rename to src/tgbot/keyboards/admin/inline/setting.py diff --git a/keyboards/admin/main_menu.py b/src/tgbot/keyboards/admin/main_menu.py similarity index 100% rename from keyboards/admin/main_menu.py rename to src/tgbot/keyboards/admin/main_menu.py diff --git a/keyboards/default/__init__.py b/src/tgbot/keyboards/default/__init__.py similarity index 100% rename from keyboards/default/__init__.py rename to src/tgbot/keyboards/default/__init__.py diff --git a/keyboards/default/admin_default.py b/src/tgbot/keyboards/default/admin_default.py similarity index 100% rename from keyboards/default/admin_default.py rename to src/tgbot/keyboards/default/admin_default.py diff --git a/keyboards/default/get_contact_default.py b/src/tgbot/keyboards/default/get_contact_default.py similarity index 100% rename from keyboards/default/get_contact_default.py rename to src/tgbot/keyboards/default/get_contact_default.py diff --git a/keyboards/default/get_location_default.py b/src/tgbot/keyboards/default/get_location_default.py similarity index 100% rename from keyboards/default/get_location_default.py rename to src/tgbot/keyboards/default/get_location_default.py diff --git a/keyboards/default/get_photo.py b/src/tgbot/keyboards/default/get_photo.py similarity index 100% rename from keyboards/default/get_photo.py rename to src/tgbot/keyboards/default/get_photo.py diff --git a/keyboards/inline/__init__.py b/src/tgbot/keyboards/inline/__init__.py similarity index 100% rename from keyboards/inline/__init__.py rename to src/tgbot/keyboards/inline/__init__.py diff --git a/keyboards/inline/admin_inline.py b/src/tgbot/keyboards/inline/admin_inline.py similarity index 100% rename from keyboards/inline/admin_inline.py rename to src/tgbot/keyboards/inline/admin_inline.py diff --git a/keyboards/inline/back_inline.py b/src/tgbot/keyboards/inline/back_inline.py similarity index 100% rename from keyboards/inline/back_inline.py rename to src/tgbot/keyboards/inline/back_inline.py diff --git a/keyboards/inline/calendar.py b/src/tgbot/keyboards/inline/calendar.py similarity index 100% rename from keyboards/inline/calendar.py rename to src/tgbot/keyboards/inline/calendar.py diff --git a/keyboards/inline/cancel_inline.py b/src/tgbot/keyboards/inline/cancel_inline.py similarity index 100% rename from keyboards/inline/cancel_inline.py rename to src/tgbot/keyboards/inline/cancel_inline.py diff --git a/keyboards/inline/change_data_profile_inline.py b/src/tgbot/keyboards/inline/change_data_profile_inline.py similarity index 100% rename from keyboards/inline/change_data_profile_inline.py rename to src/tgbot/keyboards/inline/change_data_profile_inline.py diff --git a/keyboards/inline/filters_inline.py b/src/tgbot/keyboards/inline/filters_inline.py similarity index 100% rename from keyboards/inline/filters_inline.py rename to src/tgbot/keyboards/inline/filters_inline.py diff --git a/keyboards/inline/guide_inline.py b/src/tgbot/keyboards/inline/guide_inline.py similarity index 100% rename from keyboards/inline/guide_inline.py rename to src/tgbot/keyboards/inline/guide_inline.py diff --git a/keyboards/inline/language_inline.py b/src/tgbot/keyboards/inline/language_inline.py similarity index 100% rename from keyboards/inline/language_inline.py rename to src/tgbot/keyboards/inline/language_inline.py diff --git a/keyboards/inline/main_menu_inline.py b/src/tgbot/keyboards/inline/main_menu_inline.py similarity index 93% rename from keyboards/inline/main_menu_inline.py rename to src/tgbot/keyboards/inline/main_menu_inline.py index 195b89c..dad63d3 100644 --- a/keyboards/inline/main_menu_inline.py +++ b/src/tgbot/keyboards/inline/main_menu_inline.py @@ -1,7 +1,3 @@ -from typing import ( - Union, -) - from aiogram.types import ( CallbackQuery, InlineKeyboardButton, @@ -9,19 +5,19 @@ Message, ) -from data.config import ( - load_config, -) from loader import ( _, ) -from utils.db_api import ( +from src.tgbot.config import ( + load_config, +) +from src.infrastructure.db_api import ( db_commands, ) async def start_keyboard( - obj: Union[CallbackQuery, Message, int] + obj: CallbackQuery | Message | int ) -> InlineKeyboardMarkup: markup = InlineKeyboardMarkup(row_width=2) try: diff --git a/keyboards/inline/menu_profile_inline.py b/src/tgbot/keyboards/inline/menu_profile_inline.py similarity index 100% rename from keyboards/inline/menu_profile_inline.py rename to src/tgbot/keyboards/inline/menu_profile_inline.py diff --git a/keyboards/inline/necessary_links_inline.py b/src/tgbot/keyboards/inline/necessary_links_inline.py similarity index 100% rename from keyboards/inline/necessary_links_inline.py rename to src/tgbot/keyboards/inline/necessary_links_inline.py diff --git a/keyboards/inline/payments_inline.py b/src/tgbot/keyboards/inline/payments_inline.py similarity index 100% rename from keyboards/inline/payments_inline.py rename to src/tgbot/keyboards/inline/payments_inline.py diff --git a/keyboards/inline/poster_inline.py b/src/tgbot/keyboards/inline/poster_inline.py similarity index 97% rename from keyboards/inline/poster_inline.py rename to src/tgbot/keyboards/inline/poster_inline.py index be79b32..63bcc37 100644 --- a/keyboards/inline/poster_inline.py +++ b/src/tgbot/keyboards/inline/poster_inline.py @@ -12,12 +12,12 @@ from loader import ( _, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) -async def poster_keyboard(obj: Union[Message, CallbackQuery]) -> InlineKeyboardMarkup: +async def poster_keyboard(obj: Message | CallbackQuery) -> InlineKeyboardMarkup: user = await db_commands.select_user_meetings(telegram_id=obj.from_user.id) is_admin = user.is_admin is_verification = user.verification_status diff --git a/keyboards/inline/questionnaires_inline.py b/src/tgbot/keyboards/inline/questionnaires_inline.py similarity index 100% rename from keyboards/inline/questionnaires_inline.py rename to src/tgbot/keyboards/inline/questionnaires_inline.py diff --git a/keyboards/inline/registration_inline.py b/src/tgbot/keyboards/inline/registration_inline.py similarity index 100% rename from keyboards/inline/registration_inline.py rename to src/tgbot/keyboards/inline/registration_inline.py diff --git a/keyboards/inline/settings_menu.py b/src/tgbot/keyboards/inline/settings_menu.py similarity index 100% rename from keyboards/inline/settings_menu.py rename to src/tgbot/keyboards/inline/settings_menu.py diff --git a/keyboards/inline/support_inline.py b/src/tgbot/keyboards/inline/support_inline.py similarity index 90% rename from keyboards/inline/support_inline.py rename to src/tgbot/keyboards/inline/support_inline.py index 88efaf5..98ad609 100644 --- a/keyboards/inline/support_inline.py +++ b/src/tgbot/keyboards/inline/support_inline.py @@ -12,19 +12,19 @@ CallbackData, ) -from data.config import ( - load_config, -) from loader import ( _, dp, ) +from src.tgbot.config import ( + load_config, +) support_callback = CallbackData("ask_support", "messages", "user_id", "as_user") cancel_support_callback = CallbackData("cancel_support", "user_id") -async def check_support_available(support_id) -> Optional[int]: +async def check_support_available(support_id) -> int | None: state = dp.current_state(chat=support_id, user=support_id) state_str = str(await state.get_state()) if state_str == "in_support": @@ -33,7 +33,7 @@ async def check_support_available(support_id) -> Optional[int]: return support_id -async def get_support_manager() -> Optional[int]: +async def get_support_manager() -> int | None: random.shuffle(load_config().tg_bot.support_ids) for support_id in load_config().tg_bot.support_ids: support_id = await check_support_available(support_id) @@ -43,7 +43,7 @@ async def get_support_manager() -> Optional[int]: return -async def support_keyboard(messages, user_id=None) -> Union[bool, InlineKeyboardMarkup]: +async def support_keyboard(messages, user_id=None) -> bool | InlineKeyboardMarkup: if user_id: contact_id = int(user_id) as_user = "no" diff --git a/middlewares/AgentSupport.py b/src/tgbot/middlewares/AgentSupport.py similarity index 100% rename from middlewares/AgentSupport.py rename to src/tgbot/middlewares/AgentSupport.py diff --git a/middlewares/BanCheck.py b/src/tgbot/middlewares/BanCheck.py similarity index 91% rename from middlewares/BanCheck.py rename to src/tgbot/middlewares/BanCheck.py index a8d7498..2c75cf7 100644 --- a/middlewares/BanCheck.py +++ b/src/tgbot/middlewares/BanCheck.py @@ -1,6 +1,5 @@ from typing import ( NoReturn, - Union, ) from aiogram import ( @@ -13,15 +12,15 @@ BaseMiddleware, ) -from keyboards.inline.admin_inline import ( - unban_user_keyboard, -) from loader import ( _, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.inline.admin_inline import ( + unban_user_keyboard, +) class BanMiddleware(BaseMiddleware): @@ -52,7 +51,7 @@ async def on_process_callback_query( await self.check_ban_user(obj=call) async def check_ban_user( - self, obj: Union[types.CallbackQuery, types.Message] + self, obj: types.CallbackQuery | types.Message ) -> NoReturn: user = await db_commands.select_user(telegram_id=obj.from_user.id) diff --git a/middlewares/IsMaintenanceCheck.py b/src/tgbot/middlewares/IsMaintenanceCheck.py similarity index 91% rename from middlewares/IsMaintenanceCheck.py rename to src/tgbot/middlewares/IsMaintenanceCheck.py index 0861206..c62b39f 100644 --- a/middlewares/IsMaintenanceCheck.py +++ b/src/tgbot/middlewares/IsMaintenanceCheck.py @@ -1,6 +1,5 @@ from typing import ( NoReturn, - Union, ) from aiogram import ( @@ -13,13 +12,13 @@ BaseMiddleware, ) -from data.config import ( - load_config, -) from loader import ( _, ) -from utils.db_api import ( +from src.tgbot.config import ( + load_config, +) +from src.infrastructure.db_api import ( db_commands, ) @@ -36,7 +35,7 @@ async def on_process_callback_query(self, call: types.CallbackQuery, data: dict) @staticmethod async def check_tech_works( - obj: Union[types.CallbackQuery, types.Message] + obj: types.CallbackQuery | types.Message ) -> NoReturn: text = _("Ведутся технические работы") try: diff --git a/middlewares/LinkCheck.py b/src/tgbot/middlewares/LinkCheck.py similarity index 88% rename from middlewares/LinkCheck.py rename to src/tgbot/middlewares/LinkCheck.py index d977a3d..dea450c 100644 --- a/middlewares/LinkCheck.py +++ b/src/tgbot/middlewares/LinkCheck.py @@ -14,19 +14,15 @@ BaseMiddleware, ) -from data.config import ( +from src.tgbot.config import ( load_config, ) -from keyboards.inline.necessary_links_inline import ( - necessary_links_keyboard, -) -from loader import ( - _, - bot, -) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.inline.necessary_links_inline import ( + necessary_links_keyboard, +) class LinkCheckMiddleware(BaseMiddleware): @@ -41,7 +37,7 @@ async def on_process_callback_query( @staticmethod async def _check_links_and_handle( - user_id: int, obj: Union[types.CallbackQuery, types.Message] + user_id: int, obj: types.CallbackQuery | types.Message ) -> NoReturn: links_db = await db_commands.select_all_links() subscribed_links = set() @@ -53,7 +49,7 @@ async def check_subscription(link_id): for link in links_db: if await check_subscription(link["telegram_link_id"]): subscribed_links.add(link["telegram_link_id"]) - text, markup = _( + text, markup = ( "Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " "подпишитесь! Ссылки ниже: " ), await necessary_links_keyboard( diff --git a/middlewares/Log.py b/src/tgbot/middlewares/Log.py similarity index 100% rename from middlewares/Log.py rename to src/tgbot/middlewares/Log.py diff --git a/middlewares/SchedulerWare.py b/src/tgbot/middlewares/SchedulerWare.py similarity index 86% rename from middlewares/SchedulerWare.py rename to src/tgbot/middlewares/SchedulerWare.py index 70d5c85..b40c9b8 100644 --- a/middlewares/SchedulerWare.py +++ b/src/tgbot/middlewares/SchedulerWare.py @@ -23,9 +23,9 @@ def __init__(self, scheduler: AsyncIOScheduler): def __call__( self, - handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], event: TelegramObject, - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: data["appscheduler"] = self.scheduler return handler(event, data) diff --git a/middlewares/Throttling.py b/src/tgbot/middlewares/Throttling.py similarity index 100% rename from middlewares/Throttling.py rename to src/tgbot/middlewares/Throttling.py diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py new file mode 100644 index 0000000..a979855 --- /dev/null +++ b/src/tgbot/middlewares/__init__.py @@ -0,0 +1,48 @@ +# from loader import ( +# dp, +# scheduler, +# ) +# +from .AgentSupport import ( + SupportMiddleware, +) +from .BanCheck import ( + BanMiddleware, +) +from .IsMaintenanceCheck import ( + IsMaintenance, +) +from .LinkCheck import ( + LinkCheckMiddleware, +) +from .Log import ( + LogMiddleware, +) +from .SchedulerWare import ( + SchedulerMiddleware, +) +from .Throttling import ( + ThrottlingMiddleware, +) + +__all__ = ( + "AgentSupport", + "BanMiddleware", + "IsMaintenance", + "LinkCheck", + "LogMiddleware", + "SchedulerMiddleware", + "ThrottlingMiddleware", + "SupportMiddleware", + "LinkCheckMiddleware", +) + +# +# if __name__ == "middlewares": +# dp.middleware.setup(ThrottlingMiddleware()) +# dp.middleware.setup(LinkCheckMiddleware()) +# dp.middleware.setup(SupportMiddleware()) +# dp.middleware.setup(IsMaintenance()) +# dp.middleware.setup(SchedulerMiddleware(scheduler)) +# dp.middleware.setup(BanMiddleware()) +# dp.middleware.setup(LogMiddleware()) diff --git a/states/__init__.py b/src/tgbot/services/__init__.py similarity index 100% rename from states/__init__.py rename to src/tgbot/services/__init__.py diff --git a/utils/misc/AsyncObj.py b/src/tgbot/services/app/AsyncObj.py similarity index 100% rename from utils/misc/AsyncObj.py rename to src/tgbot/services/app/AsyncObj.py diff --git a/utils/NudeNet/__init__.py b/src/tgbot/services/app/__init__.py similarity index 100% rename from utils/NudeNet/__init__.py rename to src/tgbot/services/app/__init__.py diff --git a/functions/main_app/app_scheduler.py b/src/tgbot/services/app/app_scheduler.py similarity index 86% rename from functions/main_app/app_scheduler.py rename to src/tgbot/services/app/app_scheduler.py index fc7e435..f35952e 100644 --- a/functions/main_app/app_scheduler.py +++ b/src/tgbot/services/app/app_scheduler.py @@ -2,16 +2,16 @@ Message, ) -from keyboards.inline.questionnaires_inline import ( - viewing_ques_keyboard, -) from loader import ( _, bot, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.inline.questionnaires_inline import ( + viewing_ques_keyboard, +) async def send_message_week(message: Message) -> None: diff --git a/src/tgbot/services/app/data_operations.py b/src/tgbot/services/app/data_operations.py new file mode 100644 index 0000000..88896d3 --- /dev/null +++ b/src/tgbot/services/app/data_operations.py @@ -0,0 +1,24 @@ +import shutil + +import aiofiles + +from src.infrastructure.db_api import ( + db_commands, +) + + +async def dump_users_to_file(): + async with aiofiles.open("users.txt", "w", encoding="utf-8") as file: + _text = "" + _users = await db_commands.select_all_users() + for user in _users: + _text += str(user) + "\n" + + await file.write(_text) + + return "users.txt" + + +async def backup_configs(): + shutil.make_archive("backup_data", "zip", "./logs/") + return "./backup_data.zip" diff --git a/functions/main_app/determin_location.py b/src/tgbot/services/app/determin_location.py similarity index 92% rename from functions/main_app/determin_location.py rename to src/tgbot/services/app/determin_location.py index eea9ea2..cab66c4 100644 --- a/functions/main_app/determin_location.py +++ b/src/tgbot/services/app/determin_location.py @@ -10,21 +10,21 @@ Message, ) -from keyboards.inline.registration_inline import ( - confirm_keyboard, -) from loader import ( _, client, logger, ) -from utils.YandexMap.exceptions import ( +from src.infrastructure.YandexMap.exceptions import ( NothingFound, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) -from utils.misc.AsyncObj import ( +from src.tgbot.keyboards.inline.registration_inline import ( + confirm_keyboard, +) +from src.tgbot.services.app.AsyncObj import ( AsyncObj, ) diff --git a/utils/misc/ds_name.py b/src/tgbot/services/app/ds_name.py similarity index 100% rename from utils/misc/ds_name.py rename to src/tgbot/services/app/ds_name.py diff --git a/functions/main_app/language_ware.py b/src/tgbot/services/app/language_ware.py similarity index 75% rename from functions/main_app/language_ware.py rename to src/tgbot/services/app/language_ware.py index bffa931..b49fb07 100644 --- a/functions/main_app/language_ware.py +++ b/src/tgbot/services/app/language_ware.py @@ -1,7 +1,5 @@ from typing import ( Any, - Optional, - Tuple, ) from aiogram import ( @@ -11,22 +9,22 @@ I18nMiddleware, ) -from data.config import ( +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.config import ( LOCALES_DIR, load_config, ) -from utils.db_api import ( - db_commands, -) -async def get_lang(user_id) -> Optional[str]: +async def get_lang(user_id) -> str | None: user = await db_commands.select_user(telegram_id=user_id) return user.language if user else None class ACLMiddleware(I18nMiddleware): - async def get_user_locale(self, action: str, args: Tuple[Any]) -> Optional[str]: + async def get_user_locale(self, action: str, args: tuple[Any]) -> str | None: user_id = types.User.get_current().id return await get_lang(user_id) or (await super().get_user_locale(action, args)) diff --git a/utils/logger.py b/src/tgbot/services/app/logger.py similarity index 75% rename from utils/logger.py rename to src/tgbot/services/app/logger.py index ae97a17..e231c9d 100644 --- a/utils/logger.py +++ b/src/tgbot/services/app/logger.py @@ -3,6 +3,7 @@ import betterlogging as bl +# TODO: Удалить эту функцию. Перенести ее функционал в функцию main в bot.py def setup_logger(level=logging.INFO, ignored=""): bl.basic_colorized_config(level=level) logging.basicConfig( diff --git a/functions/main_app/auxiliary_tools.py b/src/tgbot/services/app/message_operations.py similarity index 64% rename from functions/main_app/auxiliary_tools.py rename to src/tgbot/services/app/message_operations.py index 9f46361..72dae27 100644 --- a/functions/main_app/auxiliary_tools.py +++ b/src/tgbot/services/app/message_operations.py @@ -1,19 +1,11 @@ -import asyncio from contextlib import ( suppress, ) import datetime import os -import pathlib import random import re -import shutil -from typing import ( - Optional, - Union, -) -import aiofiles from aiogram import ( types, ) @@ -23,9 +15,7 @@ from aiogram.types import ( CallbackQuery, InlineKeyboardMarkup, - InputFile, Message, - ReplyKeyboardRemove, ) from aiogram.utils.exceptions import ( BadRequest, @@ -36,35 +26,34 @@ UniqueViolationError, ) -from data.config import ( +from loader import ( + _, + bot, + scheduler, +) +from src.tgbot.config import ( load_config, ) -from functions.main_app.app_scheduler import ( - send_message_week, +from src.infrastructure.db_api import ( + db_commands, +) +from src.infrastructure.db_api.db_commands import ( + check_user_meetings_exists, ) -from keyboards.inline.filters_inline import ( +from src.tgbot.keyboards.inline.filters_inline import ( dating_filters_keyboard, ) -from keyboards.inline.guide_inline import ( +from src.tgbot.keyboards.inline.guide_inline import ( create_pagination_keyboard, ) -from keyboards.inline.main_menu_inline import ( +from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from keyboards.inline.settings_menu import ( +from src.tgbot.keyboards.inline.settings_menu import ( information_keyboard, ) -from loader import ( - _, - bot, - logger, - scheduler, -) -from utils.db_api import ( - db_commands, -) -from utils.db_api.db_commands import ( - check_user_meetings_exists, +from src.tgbot.services.app.app_scheduler import ( + send_message_week, ) @@ -116,7 +105,7 @@ async def display_profile(call: CallbackQuery, markup: InlineKeyboardMarkup) -> ) -async def show_dating_filters(obj: Union[CallbackQuery, Message]) -> None: +async def show_dating_filters(obj: CallbackQuery | Message) -> None: user_id = obj.from_user.id user = await db_commands.select_user(telegram_id=user_id) markup = await dating_filters_keyboard() @@ -139,7 +128,7 @@ async def show_dating_filters(obj: Union[CallbackQuery, Message]) -> None: async def registration_menu( - obj: Union[CallbackQuery, Message], + obj: CallbackQuery | Message, ) -> None: support = await db_commands.select_user( telegram_id=load_config().tg_bot.support_ids[0] @@ -177,7 +166,7 @@ async def registration_menu( async def check_user_in_db(telegram_id: int, message: Message, username: str) -> None: if not await db_commands.check_user_exists( - telegram_id + telegram_id ) and not await check_user_meetings_exists(telegram_id): user = await db_commands.select_user_object(telegram_id=telegram_id) referrer_id = message.text[7:] @@ -210,7 +199,7 @@ async def check_user_in_db(telegram_id: int, message: Message, username: str) -> async def finished_registration( - state: FSMContext, telegram_id: int, message: Message + state: FSMContext, telegram_id: int, message: Message ) -> None: await state.finish() await db_commands.update_user_data(telegram_id=telegram_id, status=True) @@ -230,116 +219,6 @@ async def finished_registration( await message.answer_photo(caption=text, photo=user.photo_id, reply_markup=markup) -async def saving_normal_photo( - message: Message, telegram_id: int, file_id: str, state: FSMContext -) -> None: - """Функция, сохраняющая фотографию пользователя без цензуры.""" - try: - await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) - - await message.answer( - text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() - ) - except Exception as err: - logger.info(f"Ошибка в saving_normal_photo | err: {err}") - await message.answer( - text=_( - "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" - "Если ошибка осталась, напишите агенту поддержки." - ) - ) - await finished_registration(state=state, telegram_id=telegram_id, message=message) - - -async def saving_censored_photo( - message: Message, - telegram_id: int, - state: FSMContext, - out_path: Union[str, pathlib.Path], - flag: Optional[str] = "registration", - markup: Union[InlineKeyboardMarkup, None] = None, -) -> None: - """.Функция, сохраняющая фотографию пользователя с цензурой.""" - photo = InputFile(out_path) - id_photo = await bot.send_photo( - chat_id=telegram_id, - photo=photo, - caption=_( - "Во время проверки вашего фото мы обнаружили подозрительный контент!\n" - "Поэтому мы чуть-чуть подкорректировали вашу фотографию" - ), - ) - file_id = id_photo["photo"][0]["file_id"] - await asyncio.sleep(1) - try: - await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) - - except Exception as err: - logger.info(f"Ошибка в saving_censored_photo | err: {err}") - await message.answer( - text=_( - "Произошла ошибка!" - " Попробуйте еще раз либо отправьте другую фотографию. \n" - "Если ошибка осталась, напишите агенту поддержки." - ) - ) - if flag == "change_datas": - await message.answer( - text=_("Фото принято!\n" "Выберите, что вы хотите изменить: "), - reply_markup=markup, - ) - await state.reset_state() - elif flag == "registration": - await finished_registration( - state=state, telegram_id=telegram_id, message=message - ) - - -async def update_normal_photo( - message: Message, - telegram_id: int, - file_id: str, - state: FSMContext, - markup -) -> None: - """Функция, которая обновляет фотографию пользователя.""" - try: - await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) - await message.answer( - text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() - ) - await asyncio.sleep(3) - await message.answer( - text=_("Выберите, что вы хотите изменить: "), reply_markup=markup - ) - await state.reset_state() - except Exception as err: - logger.info(f"Ошибка в update_normal_photo | err: {err}") - await message.answer( - text=_( - "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" - "Если ошибка осталась, напишите агенту поддержки." - ) - ) - - -async def dump_users_to_file(): - async with aiofiles.open("users.txt", "w", encoding="utf-8") as file: - _text = "" - _users = await db_commands.select_all_users() - for user in _users: - _text += str(user) + "\n" - - await file.write(_text) - - return "users.txt" - - -async def backup_configs(): - shutil.make_archive("backup_data", "zip", "./logs/") - return "./backup_data.zip" - - async def send_photo_with_caption( call: CallbackQuery, photo: str, diff --git a/utils/notify_admins.py b/src/tgbot/services/app/notify_admins.py similarity index 97% rename from utils/notify_admins.py rename to src/tgbot/services/app/notify_admins.py index d3733f9..04fffe3 100644 --- a/utils/notify_admins.py +++ b/src/tgbot/services/app/notify_admins.py @@ -12,14 +12,14 @@ ChatNotFound, ) -from data.config import ( - load_config, -) from loader import ( _, bot, logger, ) +from src.tgbot.config import ( + load_config, +) class BaseNotification(ABC): diff --git a/src/tgbot/services/app/photo_operations.py b/src/tgbot/services/app/photo_operations.py new file mode 100644 index 0000000..4868052 --- /dev/null +++ b/src/tgbot/services/app/photo_operations.py @@ -0,0 +1,117 @@ +import asyncio +import pathlib + +from aiogram.dispatcher import ( + FSMContext, +) +from aiogram.types import ( + InlineKeyboardMarkup, + InputFile, + Message, + ReplyKeyboardRemove, +) + +from loader import ( + _, + bot, + logger, +) +from src.infrastructure.db_api import ( + db_commands, +) +from src.tgbot.services.app.message_operations import ( + finished_registration, +) + + +async def saving_normal_photo( + message: Message, telegram_id: int, file_id: str, state: FSMContext +) -> None: + """Функция, сохраняющая фотографию пользователя без цензуры.""" + try: + await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) + + await message.answer( + text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() + ) + except Exception as err: + logger.info(f"Ошибка в saving_normal_photo | err: {err}") + await message.answer( + text=_( + "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" + "Если ошибка осталась, напишите агенту поддержки." + ) + ) + await finished_registration(state=state, telegram_id=telegram_id, message=message) + + +async def saving_censored_photo( + message: Message, + telegram_id: int, + state: FSMContext, + out_path: str | pathlib.Path, + flag: str | None = "registration", + markup: InlineKeyboardMarkup | None = None, +) -> None: + """.Функция, сохраняющая фотографию пользователя с цензурой.""" + photo = InputFile(out_path) + id_photo = await bot.send_photo( + chat_id=telegram_id, + photo=photo, + caption=_( + "Во время проверки вашего фото мы обнаружили подозрительный контент!\n" + "Поэтому мы чуть-чуть подкорректировали вашу фотографию" + ), + ) + file_id = id_photo["photo"][0]["file_id"] + await asyncio.sleep(1) + try: + await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) + + except Exception as err: + logger.info(f"Ошибка в saving_censored_photo | err: {err}") + await message.answer( + text=_( + "Произошла ошибка!" + " Попробуйте еще раз либо отправьте другую фотографию. \n" + "Если ошибка осталась, напишите агенту поддержки." + ) + ) + if flag == "change_datas": + await message.answer( + text=_("Фото принято!\n" "Выберите, что вы хотите изменить: "), + reply_markup=markup, + ) + await state.reset_state() + elif flag == "registration": + await finished_registration( + state=state, telegram_id=telegram_id, message=message + ) + + +async def update_normal_photo( + message: Message, + telegram_id: int, + file_id: str, + state: FSMContext, + markup +) -> None: + """Функция, которая обновляет фотографию пользователя.""" + try: + await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) + await message.answer( + text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() + ) + await asyncio.sleep(3) + await message.answer( + text=_("Выберите, что вы хотите изменить: "), reply_markup=markup + ) + await state.reset_state() + except Exception as err: + logger.info(f"Ошибка в update_normal_photo | err: {err}") + await message.answer( + text=_( + "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" + "Если ошибка осталась, напишите агенту поддержки." + ) + ) diff --git a/utils/misc/profanityFilter.py b/src/tgbot/services/app/profanityFilter.py similarity index 100% rename from utils/misc/profanityFilter.py rename to src/tgbot/services/app/profanityFilter.py diff --git a/utils/set_bot_commands.py b/src/tgbot/services/app/set_bot_commands.py similarity index 97% rename from utils/set_bot_commands.py rename to src/tgbot/services/app/set_bot_commands.py index 2b5f43d..00c12e8 100644 --- a/utils/set_bot_commands.py +++ b/src/tgbot/services/app/set_bot_commands.py @@ -5,7 +5,7 @@ types, ) -from data.config import ( +from src.tgbot.config import ( load_config, ) diff --git a/states/new_data_state.py b/src/tgbot/services/app/states.py similarity index 51% rename from states/new_data_state.py rename to src/tgbot/services/app/states.py index 2c2b6a3..d8e0637 100644 --- a/states/new_data_state.py +++ b/src/tgbot/services/app/states.py @@ -4,6 +4,11 @@ ) +class AdminsActions(StatesGroup): + add = State() + delete = State() + + class NewData(StatesGroup): sex = State() commentary = State() @@ -20,3 +25,20 @@ class NewData(StatesGroup): child = State() marital = State() photo = State() + + +class RegData(StatesGroup): + sex = State() + commentary = State() + name = State() + need_partner_sex = State() + age = State() + nationality = State() + education = State() + town = State() + car = State() + own_home = State() + hobbies = State() + child = State() + marital = State() + photo = State() diff --git a/utils/statistics.py b/src/tgbot/services/app/statistics.py similarity index 97% rename from utils/statistics.py rename to src/tgbot/services/app/statistics.py index 93dafab..d6dadc8 100644 --- a/utils/statistics.py +++ b/src/tgbot/services/app/statistics.py @@ -5,7 +5,7 @@ from loader import ( _, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) diff --git a/utils/misc/throttling.py b/src/tgbot/services/app/throttling.py similarity index 100% rename from utils/misc/throttling.py rename to src/tgbot/services/app/throttling.py diff --git a/functions/dating/__init__.py b/src/tgbot/services/dating/__init__.py similarity index 100% rename from functions/dating/__init__.py rename to src/tgbot/services/dating/__init__.py diff --git a/functions/dating/create_forms_funcs.py b/src/tgbot/services/dating/create_forms_funcs.py similarity index 89% rename from functions/dating/create_forms_funcs.py rename to src/tgbot/services/dating/create_forms_funcs.py index c6ce1f4..10f2514 100644 --- a/functions/dating/create_forms_funcs.py +++ b/src/tgbot/services/dating/create_forms_funcs.py @@ -11,24 +11,24 @@ BadRequest, ) -from functions.dating.get_next_user_func import ( - get_next_user, -) -from functions.dating.send_form_func import ( - send_questionnaire, +from loader import ( + bot, ) -from keyboards.inline.questionnaires_inline import ( +from src.tgbot.keyboards.inline.questionnaires_inline import ( questionnaires_keyboard, ) -from loader import ( - bot, +from src.tgbot.services.dating.get_next_user_func import ( + get_next_user, +) +from src.tgbot.services.dating.send_form_func import ( + send_questionnaire, ) async def create_questionnaire( form_owner: int, chat_id: int, - add_text: Optional[str] = None, + add_text: str | None = None, monitoring: bool = False, report_system: bool = False, ) -> None: diff --git a/functions/dating/get_next_user_func.py b/src/tgbot/services/dating/get_next_user_func.py similarity index 97% rename from functions/dating/get_next_user_func.py rename to src/tgbot/services/dating/get_next_user_func.py index 219eff7..9caa017 100644 --- a/functions/dating/get_next_user_func.py +++ b/src/tgbot/services/dating/get_next_user_func.py @@ -6,7 +6,7 @@ alru_cache, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) diff --git a/functions/dating/reaction_strategies.py b/src/tgbot/services/dating/reaction_strategies.py similarity index 95% rename from functions/dating/reaction_strategies.py rename to src/tgbot/services/dating/reaction_strategies.py index b3c1573..c4ec7f3 100644 --- a/functions/dating/reaction_strategies.py +++ b/src/tgbot/services/dating/reaction_strategies.py @@ -13,33 +13,33 @@ CallbackQuery, ) -from data.config import ( - load_config, -) -from functions.dating.create_forms_funcs import ( - create_questionnaire, - create_questionnaire_reciprocity, - rand_user_list, +from loader import ( + _, + bot, ) -from functions.dating.get_next_user_func import ( - get_next_user, +from src.tgbot.config import ( + load_config, ) -from functions.main_app.auxiliary_tools import ( - get_report_reason, +from src.infrastructure.db_api import ( + db_commands, ) -from keyboards.inline.main_menu_inline import ( +from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from keyboards.inline.questionnaires_inline import ( +from src.tgbot.keyboards.inline.questionnaires_inline import ( report_menu_keyboard, user_link_keyboard, ) -from loader import ( - _, - bot, +from src.tgbot.services.app.message_operations import ( + get_report_reason, ) -from utils.db_api import ( - db_commands, +from src.tgbot.services.dating.create_forms_funcs import ( + create_questionnaire, + create_questionnaire_reciprocity, + rand_user_list, +) +from src.tgbot.services.dating.get_next_user_func import ( + get_next_user, ) diff --git a/functions/dating/send_form_func.py b/src/tgbot/services/dating/send_form_func.py similarity index 92% rename from functions/dating/send_form_func.py rename to src/tgbot/services/dating/send_form_func.py index 37b76a2..22fdfd0 100644 --- a/functions/dating/send_form_func.py +++ b/src/tgbot/services/dating/send_form_func.py @@ -9,28 +9,28 @@ BadRequest, ) -from keyboards.admin.inline.customers import ( - user_blocking_keyboard, -) -from keyboards.inline.questionnaires_inline import ( - questionnaires_keyboard, - reciprocity_keyboard, -) from loader import ( _, bot, logger, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.keyboards.admin.inline.customers import ( + user_blocking_keyboard, +) +from src.tgbot.keyboards.inline.questionnaires_inline import ( + questionnaires_keyboard, + reciprocity_keyboard, +) async def send_questionnaire( chat_id: int, - owner_id: Optional[int] = None, - markup: Optional[InlineKeyboardMarkup] = None, - add_text: Optional[str] = None, + owner_id: int | None = None, + markup: InlineKeyboardMarkup | None = None, + add_text: str | None = None, monitoring: bool = False, report_system: bool = False, ) -> None: diff --git a/utils/YandexMap/__init__.py b/src/tgbot/services/event/__init__.py similarity index 100% rename from utils/YandexMap/__init__.py rename to src/tgbot/services/event/__init__.py diff --git a/functions/event/extra_features.py b/src/tgbot/services/event/extra_features.py similarity index 93% rename from functions/event/extra_features.py rename to src/tgbot/services/event/extra_features.py index d04ef07..14f39e4 100644 --- a/functions/event/extra_features.py +++ b/src/tgbot/services/event/extra_features.py @@ -14,16 +14,16 @@ BadRequest, ) -from functions.event.templates_messages import ( - ME, -) from loader import ( _, bot, ) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.services.event.templates_messages import ( + ME, +) async def add_events_to_user(call: CallbackQuery, event_id: int) -> None: @@ -69,7 +69,7 @@ async def check_event_date(telegram_id: int) -> None: async def create_form( - form_owner: int, chat_id: int, call: CallbackQuery, view: Union[bool, None] = True + form_owner: int, chat_id: int, call: CallbackQuery, view: bool | None = True ) -> None: """Function that fills the form with text.""" try: @@ -97,7 +97,7 @@ async def create_form( ) -async def get_next_random_event_id(telegram_id: int) -> Optional[int]: +async def get_next_random_event_id(telegram_id: int) -> int | None: """Function that returns a random id of an event created by another user.""" event_ids = await db_commands.search_event_forms() @@ -118,7 +118,7 @@ async def get_next_random_event_id(telegram_id: int) -> Optional[int]: raise ValueError("No more event ids") -async def get_next_registration(telegram_id: int) -> List[int]: +async def get_next_registration(telegram_id: int) -> list[int]: user = await db_commands.select_user(telegram_id=telegram_id) events: list = user.events return events diff --git a/functions/event/templates_messages.py b/src/tgbot/services/event/templates_messages.py similarity index 92% rename from functions/event/templates_messages.py rename to src/tgbot/services/event/templates_messages.py index 3aaad2e..47b9c04 100644 --- a/functions/event/templates_messages.py +++ b/src/tgbot/services/event/templates_messages.py @@ -10,15 +10,15 @@ CallbackQuery, ) -from keyboards.inline.poster_inline import ( +from loader import ( + _, +) +from src.tgbot.keyboards.inline.poster_inline import ( cancel_event_keyboard, create_moderate_ik, event_settings_keyboard, view_event_keyboard, ) -from loader import ( - _, -) class TemplateEvent: @@ -32,11 +32,11 @@ def template_event(self) -> str: async def send_event_message( self, - text: dict[str, Union[str, str, int]], + text: dict[str, str | str | int], bot: Bot, chat_id: int, moderate: bool = False, - call: Optional[CallbackQuery] = None, + call: CallbackQuery | None = None, view_event: bool = False, ) -> None: msg = self.template_event().format( @@ -71,7 +71,7 @@ async def send_event_message( async def send_event_list( self, - text: dict[str, Union[str, str, int]], + text: dict[str, str | str | int], call: CallbackQuery, telegram_id: int, bot: Bot, diff --git a/utils/db_api/__init__.py b/utils/db_api/__init__.py deleted file mode 100644 index e69de29..0000000 From 1275b12676016b7676bf120c6f7407ad048a0d67 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:53:10 +0300 Subject: [PATCH 002/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20app.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 66 ---------------------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 app.py diff --git a/app.py b/app.py deleted file mode 100644 index a9004f5..0000000 --- a/app.py +++ /dev/null @@ -1,66 +0,0 @@ -# noinspection PyUnresolvedReferences -import logging -import os - -import django -from aiogram import ( - executor, -) - -# noinspection PyUnresolvedReferences -import filters - -# noinspection PyUnresolvedReferences -from django_project.telegrambot.telegrambot import ( - settings, -) - -from loader import ( - dp, - scheduler, -) -from utils.db_api.db_commands import ( - reset_view_limit, -) -from utils.logger import ( - setup_logger, -) -from utils.notify_admins import ( - AdminNotification, -) -from utils.set_bot_commands import ( - set_default_commands, -) - - -async def on_startup(dispatcher) -> None: - await set_default_commands(dispatcher) - scheduler.add_job( - func=reset_view_limit, - trigger="cron", - hour=0, - id="reset_view_limit", - replace_existing=True, - ) - await AdminNotification.send(dispatcher) - - -def setup_django(): - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "django_project.telegrambot.telegrambot.settings" - ) - os.environ.update({"DJANGO_ALLOW_ASYNC_UNSAFE": "true"}) - django.setup() - - -if __name__ == "__main__": - setup_django() - setup_logger("INFO", ["aiogram.bot.api"]) - # noinspection PyUnresolvedReferences - import middlewares - - # noinspection PyUnresolvedReferences - import handlers - - scheduler.start() - executor.start_polling(dp, on_startup=on_startup, skip_updates=True) From 26758d9a357c7cc5b22fec52145905d8f9c50457 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:55:46 +0300 Subject: [PATCH 003/148] =?UTF-8?q?=F0=9F=8E=A8=20move=20registration=20fu?= =?UTF-8?q?nc=20of=20the=20middleware=20and=20filters=20to=20bot.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 bot.py diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..d3c4845 --- /dev/null +++ b/bot.py @@ -0,0 +1,89 @@ +# noinspection PyUnresolvedReferences +import logging +import os + +import django +from aiogram import ( + executor, Dispatcher, +) + +# noinspection PyUnresolvedReferences +from django_project.telegrambot.telegrambot import ( + settings, +) +from loader import ( + dp, + scheduler, +) +from src.tgbot.filters import IsGroup, IsAdmin, IsPrivate +from src.infrastructure.db_api.db_commands import ( + reset_view_limit, +) +from src.tgbot.services.app.logger import ( + setup_logger, +) +from src.tgbot.services.app.notify_admins import ( + AdminNotification, +) +from src.tgbot.services.app.set_bot_commands import ( + set_default_commands, +) +from src.tgbot.middlewares import ( + ThrottlingMiddleware, + LinkCheckMiddleware, + SupportMiddleware, + IsMaintenance, + SchedulerMiddleware, + BanMiddleware, + LogMiddleware +) + + +def register_all_middlewares(dp: Dispatcher) -> None: + dp.setup_middleware(ThrottlingMiddleware()) + dp.setup_middleware(LinkCheckMiddleware()) + dp.setup_middleware(SupportMiddleware()) + dp.setup_middleware(IsMaintenance()) + dp.setup_middleware(SchedulerMiddleware(scheduler)) + dp.setup_middleware(BanMiddleware()) + dp.setup_middleware(LogMiddleware()) + + +def register_all_filters(dp: Dispatcher) -> None: + dp.filters_factory.bind(IsGroup) + dp.filters_factory.bind(IsPrivate) + dp.filters_factory.bind(IsAdmin) + + +async def on_startup(dispatcher) -> None: + await set_default_commands(dispatcher) + scheduler.add_job( + func=reset_view_limit, + trigger="cron", + hour=0, + id="reset_view_limit", + replace_existing=True, + ) + await AdminNotification.send(dispatcher) + + +def setup_django(): + os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "django_project.telegrambot.telegrambot.settings" + ) + os.environ.update({"DJANGO_ALLOW_ASYNC_UNSAFE": "true"}) + django.setup() + + +if __name__ == "__main__": + setup_django() + setup_logger("INFO", ["aiogram.bot.api"]) + # noinspection PyUnresolvedReferences + from src.tgbot import ( + filters, + middlewares, + handlers + ) + + scheduler.start() + executor.start_polling(dp, on_startup=on_startup, skip_updates=True) From 336eb085c455b2ed121eaf012066a483dfd9e9a1 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:55:58 +0300 Subject: [PATCH 004/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20fix=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- loader.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/loader.py b/loader.py index e5d12bd..a48e67f 100644 --- a/loader.py +++ b/loader.py @@ -21,19 +21,14 @@ NudeDetector, ) -from data.config import ( - load_config, -) -from functions.main_app.language_ware import ( - setup_middleware, -) -from utils.YandexMap.api import ( +from src.tgbot.config import load_config +from src.infrastructure.YandexMap.api import ( Client, ) - -from utils.yoomoney import ( +from src.infrastructure.yoomoney import ( YooMoneyWallet, ) +from src.tgbot.services.app.language_ware import setup_middleware bot = Bot(token=load_config().tg_bot.token, parse_mode=types.ParseMode.HTML) storage = RedisStorage2() if load_config().tg_bot.use_redis else MemoryStorage() From aedc7c79d4ede56ea3b276c1a00a16a2e5de8e85 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:56:20 +0300 Subject: [PATCH 005/148] =?UTF-8?q?=F0=9F=94=A7=20update=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- babel.cfg | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/babel.cfg b/babel.cfg index 40fadc3..6e0b734 100644 --- a/babel.cfg +++ b/babel.cfg @@ -1,10 +1,7 @@ -[python: data/**.py] -[python: django_project/**.py] -[python: filters/**.py] -[python: functions/**.py] -[python: handlers/**.py] -[python: keyboards/**.py] -[python: logs/**.py] -[python: middlewares/**.py] -[python: utils/**.py] +[python: src/tgbot/filters/**.py] +[python: src/tgbot/services/**.py] +[python: src/tgbot/handlers/**.py] +[python: src/tgbot/keyboards/**.py] +[python: src/tgbot/middlewares/**.py] +[python: src/tgbot/utils/**.py] encoding = utf-8 \ No newline at end of file From 95638112ddb395d9472e58a2907464c9c4f1c2e9 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:56:34 +0300 Subject: [PATCH 006/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20utils=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/__init__.py | 5 --- utils/faker.py | 82 ------------------------------------------ utils/jobs/__init__.py | 0 utils/misc/__init__.py | 3 -- 4 files changed, 90 deletions(-) delete mode 100644 utils/__init__.py delete mode 100644 utils/faker.py delete mode 100644 utils/jobs/__init__.py delete mode 100644 utils/misc/__init__.py diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index f09a801..0000000 --- a/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import ( - db_api, - misc, - set_bot_commands, -) diff --git a/utils/faker.py b/utils/faker.py deleted file mode 100644 index e56f898..0000000 --- a/utils/faker.py +++ /dev/null @@ -1,82 +0,0 @@ -from datetime import ( - datetime, - timedelta, -) -import random - -import psycopg2 - -from data.config import ( - load_config, -) - -db_params = { - "host": load_config().db.host, - "database": load_config().db.database, - "user": load_config().db.user, - "password": load_config().db.password, -} - -cities = ["cities"] -photos = [ - "id of photos" -] - - -def create_users(num_users): - conn = psycopg2.connect(**db_params) - cursor = conn.cursor() - - for _ in range(num_users): - user_data = { - "created_at": datetime.now() - timedelta(days=random.randint(0, 365 * 5)), - "updated_at": datetime.now() - timedelta(days=random.randint(0, 365 * 5)), - "telegram_id": random.randint(10_000_000, 100_000_000), - "name": "User" + str(random.randint(1, 10000)), - "username": "user" + str(random.randint(1, 10000)), - "sex": random.choice(["Женский", "Мужской"]), - "age": random.randint(18, 80), - "city": random.choice(cities), - "need_city": random.choice(cities), - "longitude": random.uniform(-180, 180), - "latitude": random.uniform(-90, 90), - "verification": random.choice([True, False]), - "language": "en" if random.random() < 0.5 else "es", - "varname": "Var" + str(random.randint(1, 100)), - "lifestyle": None, - "is_banned": random.choice([True, False]), - "photo_id": random.choice(photos), - "commentary": "Comment" + str(random.randint(1, 100)), - "need_partner_sex": random.choice(["Мужской", "Женский"]), - "need_partner_age_min": random.randint(18, 80), - "need_partner_age_max": random.randint(18, 80), - "phone_number": None, - "status": True, - "instagram": "insta_" + str(random.randint(1, 10000)), - } - - insert_query = """ - INSERT INTO usersmanage_user ( - telegram_id, name, username, sex, age, city, need_city, - longitude, latitude, verification, language, varname, lifestyle, - is_banned, photo_id, commentary, need_partner_sex, need_partner_age_min, - need_partner_age_max, phone_number, status, instagram, created_at, updated_at - ) - VALUES ( - %(telegram_id)s, %(name)s, %(username)s, %(sex)s, %(age)s, %(city)s, - %(need_city)s, %(longitude)s, %(latitude)s, %(verification)s, %(language)s, - %(varname)s, %(lifestyle)s, %(is_banned)s, %(photo_id)s, %(commentary)s, - %(need_partner_sex)s, %(need_partner_age_min)s, %(need_partner_age_max)s, - %(phone_number)s, %(status)s, %(instagram)s, %(created_at)s, %(updated_at)s - ) - """ - - cursor.execute(insert_query, user_data) - conn.commit() - - cursor.close() - conn.close() - - -num_users_to_create = 500_000 -create_users(num_users_to_create) diff --git a/utils/jobs/__init__.py b/utils/jobs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/utils/misc/__init__.py b/utils/misc/__init__.py deleted file mode 100644 index 467e60f..0000000 --- a/utils/misc/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .throttling import ( - rate_limit, -) From c4543597f009124280e80f2555dbcdef2d805dc7 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:58:36 +0300 Subject: [PATCH 007/148] =?UTF-8?q?=F0=9F=A7=AA=20add=20falling=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_api/test_location.py | 14 +++----------- test/test_functions/test_event_functions.py | 8 ++++---- test/test_handlers/test_echo.py | 2 +- test/test_handlers/test_group_start.py | 6 +++--- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/test/test_api/test_location.py b/test/test_api/test_location.py index a48f153..ca87432 100644 --- a/test/test_api/test_location.py +++ b/test/test_api/test_location.py @@ -6,20 +6,12 @@ import aiohttp import pytest -from data.config import ( - load_config, -) from loader import ( client, ) -from utils.YandexMap.api import ( - Client, -) -from utils.YandexMap.exceptions import ( - InvalidKey, - NothingFound, - UnexpectedResponse, -) +from src.infrastructure.YandexMap.api import Client +from src.infrastructure.YandexMap.exceptions import UnexpectedResponse, InvalidKey, NothingFound +from src.tgbot.config import load_config @pytest.mark.asyncio diff --git a/test/test_functions/test_event_functions.py b/test/test_functions/test_event_functions.py index 49a6deb..964f340 100644 --- a/test/test_functions/test_event_functions.py +++ b/test/test_functions/test_event_functions.py @@ -16,12 +16,12 @@ import pytest -from functions.event import ( - extra_features, -) -from utils.db_api import ( +from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.services.event import ( + extra_features, +) @pytest.mark.asyncio diff --git a/test/test_handlers/test_echo.py b/test/test_handlers/test_echo.py index 3e8c196..777c568 100644 --- a/test/test_handlers/test_echo.py +++ b/test/test_handlers/test_echo.py @@ -7,7 +7,7 @@ ) import pytest -from handlers.echo_handler import ( +from src.tgbot.handlers.echo_handler import ( bot_echo, ) diff --git a/test/test_handlers/test_group_start.py b/test/test_handlers/test_group_start.py index f550abd..a5eec3b 100644 --- a/test/test_handlers/test_group_start.py +++ b/test/test_handlers/test_group_start.py @@ -2,11 +2,11 @@ AsyncMock, ) -import pytest - -from handlers.groups.start import ( +from src.tgbot.handlers.groups.start import ( start_group_handler, ) +import pytest + from loader import ( _, ) From 5f00a565c6856254a8fb7d728cc61c1bd5d15633 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:58:48 +0300 Subject: [PATCH 008/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20states=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- states/admins.py | 9 --------- states/reg_state.py | 21 --------------------- 2 files changed, 30 deletions(-) delete mode 100644 states/admins.py delete mode 100644 states/reg_state.py diff --git a/states/admins.py b/states/admins.py deleted file mode 100644 index 508c601..0000000 --- a/states/admins.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram.dispatcher.filters.state import ( - State, - StatesGroup, -) - - -class AdminsActions(StatesGroup): - add = State() - delete = State() diff --git a/states/reg_state.py b/states/reg_state.py deleted file mode 100644 index a583dff..0000000 --- a/states/reg_state.py +++ /dev/null @@ -1,21 +0,0 @@ -from aiogram.dispatcher.filters.state import ( - State, - StatesGroup, -) - - -class RegData(StatesGroup): - sex = State() - commentary = State() - name = State() - need_partner_sex = State() - age = State() - nationality = State() - education = State() - town = State() - car = State() - own_home = State() - hobbies = State() - child = State() - marital = State() - photo = State() From 8d4a4e91a9ec118e25c4a68e4cb485ee7a87eba4 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:59:14 +0300 Subject: [PATCH 009/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20folders?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- filters/__init__.py | 20 -------------------- middlewares/__init__.py | 35 ----------------------------------- 2 files changed, 55 deletions(-) delete mode 100644 filters/__init__.py delete mode 100644 middlewares/__init__.py diff --git a/filters/__init__.py b/filters/__init__.py deleted file mode 100644 index a9b2cc3..0000000 --- a/filters/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -from aiogram import ( - Dispatcher, -) - -from filters.FiltersChat import ( - IsPrivate, -) -from loader import ( - logger, -) - - -def setup(dp: Dispatcher): - logger.info("Подключение filters...") - text_messages = [ - dp.message_handlers, - dp.edited_message_handlers, - ] - logger.info(text_messages) - dp.filters_factory.bind(IsPrivate) diff --git a/middlewares/__init__.py b/middlewares/__init__.py deleted file mode 100644 index 69228fb..0000000 --- a/middlewares/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from loader import ( - dp, - scheduler, -) - -from .AgentSupport import ( - SupportMiddleware, -) -from .BanCheck import ( - BanMiddleware, -) -from .IsMaintenanceCheck import ( - IsMaintenance, -) -from .LinkCheck import ( - LinkCheckMiddleware, -) -from .Log import ( - LogMiddleware, -) -from .SchedulerWare import ( - SchedulerMiddleware, -) -from .Throttling import ( - ThrottlingMiddleware, -) - -if __name__ == "middlewares": - dp.middleware.setup(ThrottlingMiddleware()) - dp.middleware.setup(LinkCheckMiddleware()) - dp.middleware.setup(SupportMiddleware()) - dp.middleware.setup(IsMaintenance()) - dp.middleware.setup(SchedulerMiddleware(scheduler)) - dp.middleware.setup(BanMiddleware()) - dp.middleware.setup(LogMiddleware()) From 256fefa3aaae323fe469cbb077c7f276477d6436 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Thu, 7 Mar 2024 21:19:03 +0300 Subject: [PATCH 010/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20fix=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- django_project/telegrambot/telegrambot/settings.py | 4 +--- .../telegrambot/usersmanage/migrations/0001_initial.py | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/django_project/telegrambot/telegrambot/settings.py b/django_project/telegrambot/telegrambot/settings.py index 442a9d9..2fd70f1 100644 --- a/django_project/telegrambot/telegrambot/settings.py +++ b/django_project/telegrambot/telegrambot/settings.py @@ -3,9 +3,7 @@ Path, ) -from data.config import ( - load_config, -) +from src.tgbot.config import load_config BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/django_project/telegrambot/usersmanage/migrations/0001_initial.py b/django_project/telegrambot/usersmanage/migrations/0001_initial.py index 35a164f..fbdf196 100644 --- a/django_project/telegrambot/usersmanage/migrations/0001_initial.py +++ b/django_project/telegrambot/usersmanage/migrations/0001_initial.py @@ -1,7 +1,10 @@ # Generated by Django 4.2 on 2023-08-09 20:47 import django.contrib.postgres.fields -from django.db import migrations, models +from django.db import ( + migrations, + models, +) import django.db.models.deletion From dde289ea02dbef002e2e4264d747a472dccc2d39 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:32:49 +0300 Subject: [PATCH 011/148] =?UTF-8?q?=F0=9F=94=A5=20drop=20django?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- django_app.py | 5 - django_project/telegrambot/common/apps.py | 8 -- django_project/telegrambot/manage.py | 24 ---- .../telegrambot/telegrambot/asgi.py | 9 -- .../telegrambot/telegrambot/settings.py | 96 ------------- .../telegrambot/telegrambot/urls.py | 15 --- .../telegrambot/telegrambot/wsgi.py | 9 -- .../telegrambot/usersmanage/admin.py | 42 ------ .../telegrambot/usersmanage/apps.py | 7 - .../usersmanage/migrations/0001_initial.py | 127 ------------------ .../usersmanage/models/__init__.py | 18 --- .../telegrambot/usersmanage/models/base.py | 20 --- .../usersmanage/models/meetings.py | 40 ------ .../usersmanage/models/necessary_link.py | 20 --- .../usersmanage/models/settings_models.py | 18 --- .../telegrambot/usersmanage/models/user.py | 94 ------------- .../usersmanage/models/viewed_profile.py | 16 --- .../telegrambot/usersmanage/views.py | 67 --------- 18 files changed, 635 deletions(-) delete mode 100644 django_app.py delete mode 100644 django_project/telegrambot/common/apps.py delete mode 100644 django_project/telegrambot/manage.py delete mode 100644 django_project/telegrambot/telegrambot/asgi.py delete mode 100644 django_project/telegrambot/telegrambot/settings.py delete mode 100644 django_project/telegrambot/telegrambot/urls.py delete mode 100644 django_project/telegrambot/telegrambot/wsgi.py delete mode 100644 django_project/telegrambot/usersmanage/admin.py delete mode 100644 django_project/telegrambot/usersmanage/apps.py delete mode 100644 django_project/telegrambot/usersmanage/migrations/0001_initial.py delete mode 100644 django_project/telegrambot/usersmanage/models/__init__.py delete mode 100644 django_project/telegrambot/usersmanage/models/base.py delete mode 100644 django_project/telegrambot/usersmanage/models/meetings.py delete mode 100644 django_project/telegrambot/usersmanage/models/necessary_link.py delete mode 100644 django_project/telegrambot/usersmanage/models/settings_models.py delete mode 100644 django_project/telegrambot/usersmanage/models/user.py delete mode 100644 django_project/telegrambot/usersmanage/models/viewed_profile.py delete mode 100644 django_project/telegrambot/usersmanage/views.py diff --git a/django_app.py b/django_app.py deleted file mode 100644 index e1590e9..0000000 --- a/django_app.py +++ /dev/null @@ -1,5 +0,0 @@ -from django_project.telegrambot.manage import ( - main, -) - -main() diff --git a/django_project/telegrambot/common/apps.py b/django_project/telegrambot/common/apps.py deleted file mode 100644 index 80b93d2..0000000 --- a/django_project/telegrambot/common/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.apps import ( - AppConfig, -) - - -class CommonConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "django_project.telegrambot.common" diff --git a/django_project/telegrambot/manage.py b/django_project/telegrambot/manage.py deleted file mode 100644 index fbddbf7..0000000 --- a/django_project/telegrambot/manage.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -import os -import sys - - -def main(): - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "django_project.telegrambot.telegrambot.settings" - ) - try: - from django.core.management import ( - execute_from_command_line, - ) - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == "__main__": - main() diff --git a/django_project/telegrambot/telegrambot/asgi.py b/django_project/telegrambot/telegrambot/asgi.py deleted file mode 100644 index 3963f2b..0000000 --- a/django_project/telegrambot/telegrambot/asgi.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -from django.core.asgi import ( - get_asgi_application, -) - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "telegrambot.settings") - -application = get_asgi_application() diff --git a/django_project/telegrambot/telegrambot/settings.py b/django_project/telegrambot/telegrambot/settings.py deleted file mode 100644 index 2fd70f1..0000000 --- a/django_project/telegrambot/telegrambot/settings.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -from pathlib import ( - Path, -) - -from src.tgbot.config import load_config - -BASE_DIR = Path(__file__).resolve().parent.parent - -SECRET_KEY = load_config().misc.secret_key - -DEBUG = True - -ALLOWED_HOSTS = [] - -INSTALLED_APPS = [ - 'jazzmin', - 'django_project.telegrambot.common', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - "django_project.telegrambot.usersmanage", -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] - -ROOT_URLCONF = 'django_project.telegrambot.telegrambot.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - os.path.join(BASE_DIR, "frontend/build"), - ], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'django_project.telegrambot.telegrambot.wsgi.application' - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': load_config().db.database, - 'USER': load_config().db.user, - 'PASSWORD': load_config().db.password, - 'HOST': load_config().db.host, - "PORT": load_config().db.port - } -} - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - -LANGUAGE_CODE = 'ru-ru' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - -STATIC_URL = '/static/' diff --git a/django_project/telegrambot/telegrambot/urls.py b/django_project/telegrambot/telegrambot/urls.py deleted file mode 100644 index ee6e353..0000000 --- a/django_project/telegrambot/telegrambot/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.contrib import ( - admin, -) -from django.urls import ( - path, -) - -from django_project.telegrambot.usersmanage import ( - views, -) - -urlpatterns = [ - path("admin/", admin.site.urls), - path("export/", views.export_users_csv), -] diff --git a/django_project/telegrambot/telegrambot/wsgi.py b/django_project/telegrambot/telegrambot/wsgi.py deleted file mode 100644 index 240753b..0000000 --- a/django_project/telegrambot/telegrambot/wsgi.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -from django.core.wsgi import ( - get_wsgi_application, -) - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "telegrambot.settings") - -application = get_wsgi_application() diff --git a/django_project/telegrambot/usersmanage/admin.py b/django_project/telegrambot/usersmanage/admin.py deleted file mode 100644 index 281661e..0000000 --- a/django_project/telegrambot/usersmanage/admin.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.contrib import ( - admin, -) - -from django_project.telegrambot.usersmanage.models.meetings import ( - UserMeetings, -) -from django_project.telegrambot.usersmanage.models.necessary_link import ( - NecessaryLink, -) -from django_project.telegrambot.usersmanage.models.settings_models import ( - SettingModel, -) -from django_project.telegrambot.usersmanage.models.user import ( - User, -) - - -@admin.register(User) -class UserAdmin(admin.ModelAdmin): - list_display = ("id", "telegram_id", "username", "created_at") - - -@admin.register(UserMeetings) -class UserMeetingsAdmin(admin.ModelAdmin): - list_display = ("id", "telegram_id", "username", "created_at") - - -@admin.register(SettingModel) -class UserSettingsAdmin(admin.ModelAdmin): - list_display = ("telegram_id", "technical_works") - - -@admin.register(NecessaryLink) -class NecessaryLinkAdmin(admin.ModelAdmin): - list_display = ["id", "link", "telegram_link_id", "title"] - - search_fields = ( - "link__startswith", - "title__startswith", - "telegram_link_id__startswith", - ) diff --git a/django_project/telegrambot/usersmanage/apps.py b/django_project/telegrambot/usersmanage/apps.py deleted file mode 100644 index 3b28978..0000000 --- a/django_project/telegrambot/usersmanage/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import ( - AppConfig, -) - - -class UsersmanageConfig(AppConfig): - name = "django_project.telegrambot.usersmanage" diff --git a/django_project/telegrambot/usersmanage/migrations/0001_initial.py b/django_project/telegrambot/usersmanage/migrations/0001_initial.py deleted file mode 100644 index fbdf196..0000000 --- a/django_project/telegrambot/usersmanage/migrations/0001_initial.py +++ /dev/null @@ -1,127 +0,0 @@ -# Generated by Django 4.2 on 2023-08-09 20:47 - -import django.contrib.postgres.fields -from django.db import ( - migrations, - models, -) -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='NecessaryLink', - fields=[ - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('id', models.AutoField(primary_key=True, serialize=False)), - ('link', models.URLField(verbose_name='Обязательная ссылка')), - ('telegram_link_id', models.BigIntegerField(verbose_name='id КАНАЛА/ЧАТА - ОБЯЗАТЕЛЬНО')), - ('title', models.CharField(max_length=50, verbose_name='Название кнопки - ОБЯЗАТЕЛЬНО. Можно смайлики')), - ], - options={ - 'verbose_name': 'Необходимая ссылка', - 'verbose_name_plural': 'Необходимые ссылки', - }, - ), - migrations.CreateModel( - name='SettingModel', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('telegram_id', models.PositiveBigIntegerField(default=1, unique=True, verbose_name='ID пользователя Телеграм')), - ('technical_works', models.BooleanField(default=False, verbose_name='Технические работы')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='User', - fields=[ - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('id', models.AutoField(primary_key=True, serialize=False)), - ('telegram_id', models.PositiveBigIntegerField(default=1, unique=True, verbose_name='ID пользователя Телеграм')), - ('name', models.CharField(max_length=255, verbose_name='Имя пользователя')), - ('username', models.CharField(max_length=255, verbose_name='Username Telegram')), - ('sex', models.CharField(blank=True, max_length=30, null=True, verbose_name='Пол искателя')), - ('age', models.BigIntegerField(blank=True, null=True, verbose_name='Возраст искателя')), - ('city', models.CharField(blank=True, max_length=255, null=True, verbose_name='Город искателя')), - ('need_city', models.CharField(blank=True, max_length=255, null=True, verbose_name='Город партнера')), - ('need_distance', models.PositiveIntegerField(blank=True, null=True, verbose_name='Расстояние между партнерами')), - ('longitude', models.FloatField(blank=True, null=True, verbose_name='координаты пользователя')), - ('latitude', models.FloatField(blank=True, null=True, verbose_name='координаты пользователя')), - ('verification', models.BooleanField(default=False, verbose_name='Верификация')), - ('language', models.CharField(blank=True, max_length=10, null=True, verbose_name='Язык пользователя')), - ('varname', models.CharField(blank=True, max_length=100, null=True, verbose_name='Публичное имя пользователя')), - ('lifestyle', models.CharField(blank=True, max_length=100, null=True, verbose_name='Стиль жизни пользователя')), - ('is_banned', models.BooleanField(default=False, verbose_name='Забанен ли пользователь')), - ('photo_id', models.CharField(max_length=400, null=True, verbose_name='Photo_ID')), - ('commentary', models.CharField(blank=True, max_length=300, null=True, verbose_name='Комментарий пользователя')), - ('need_partner_sex', models.CharField(blank=True, max_length=50, null=True, verbose_name='Пол партнера')), - ('need_partner_age_min', models.PositiveIntegerField(default=16, verbose_name='Минимальный возраст партнера')), - ('need_partner_age_max', models.PositiveIntegerField(default=24, verbose_name='Максимальный возраст партнера')), - ('referrer_id', models.PositiveBigIntegerField(blank=True, null=True, verbose_name='реферал')), - ('phone_number', models.BigIntegerField(blank=True, null=True, verbose_name='Номер телефона')), - ('status', models.BooleanField(default=False, verbose_name='Статус анкеты')), - ('instagram', models.CharField(blank=True, max_length=200, null=True, verbose_name='Ник в инстаграме')), - ('events', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), default=list, size=None)), - ('id_of_events_seen', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), default=list, size=None)), - ], - options={ - 'verbose_name': ('Пользователь Знакомств',), - 'verbose_name_plural': 'Пользователи Знакомств', - }, - ), - migrations.CreateModel( - name='UserMeetings', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('telegram_id', models.PositiveBigIntegerField(default=1, unique=True, verbose_name='ID пользователя Телеграм')), - ('username', models.CharField(max_length=255, verbose_name='Username Telegram')), - ('commentary', models.CharField(max_length=50, null=True, verbose_name='Комментарий')), - ('time_event', models.CharField(max_length=10, null=True, verbose_name='Время проведения')), - ('venue', models.CharField(max_length=50, null=True, verbose_name='Место проведения')), - ('need_location', models.CharField(max_length=50, null=True)), - ('event_name', models.CharField(max_length=50, null=True, verbose_name='Название мероприятия')), - ('verification_status', models.BooleanField(default=False, verbose_name='Статус пользователя')), - ('moderation_process', models.BooleanField(default=True, verbose_name='Процесс модерации')), - ('is_premium', models.BooleanField(default=False, verbose_name='Премиум')), - ('photo_id', models.CharField(max_length=400, null=True, verbose_name='Photo_ID')), - ('is_admin', models.BooleanField(default=False)), - ('is_active', models.BooleanField(default=True)), - ], - options={ - 'verbose_name': ('Пользователь Мероприятий',), - 'verbose_name_plural': 'Пользователи Мероприятий', - }, - ), - migrations.CreateModel( - name='ViewedProfile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('viewed_at', models.DateTimeField(auto_now_add=True)), - ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to='usersmanage.user')), - ('viewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='viewer', to='usersmanage.user')), - ], - options={ - 'unique_together': {('viewer', 'profile')}, - }, - ), - migrations.AddField( - model_name='user', - name='viewed_profiles', - field=models.ManyToManyField(through='usersmanage.ViewedProfile', to='usersmanage.user'), - ), - ] diff --git a/django_project/telegrambot/usersmanage/models/__init__.py b/django_project/telegrambot/usersmanage/models/__init__.py deleted file mode 100644 index de5a973..0000000 --- a/django_project/telegrambot/usersmanage/models/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from .base import ( - TimeBasedModel, -) -from .meetings import ( - UserMeetings, -) -from .necessary_link import ( - NecessaryLink, -) -from .settings_models import ( - SettingModel, -) -from .user import ( - User, -) -from .viewed_profile import ( - ViewedProfile, -) diff --git a/django_project/telegrambot/usersmanage/models/base.py b/django_project/telegrambot/usersmanage/models/base.py deleted file mode 100644 index b17d6c7..0000000 --- a/django_project/telegrambot/usersmanage/models/base.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.db import ( - models, -) - - -class TimeBasedModel(models.Model): - id = models.AutoField( - primary_key=True, - ) - created_at = models.DateTimeField( - auto_now_add=True, - verbose_name="Дата создания", - ) - updated_at = models.DateTimeField( - auto_now=True, - verbose_name="Дата обновления", - ) - - class Meta: - abstract = True diff --git a/django_project/telegrambot/usersmanage/models/meetings.py b/django_project/telegrambot/usersmanage/models/meetings.py deleted file mode 100644 index 901ede0..0000000 --- a/django_project/telegrambot/usersmanage/models/meetings.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.db import ( - models, -) - -from django_project.telegrambot.usersmanage.models.base import ( - TimeBasedModel, -) - - -class UserMeetings(TimeBasedModel): - class Meta: - verbose_name = ("Пользователь Мероприятий",) - verbose_name_plural = "Пользователи Мероприятий" - - telegram_id = models.PositiveBigIntegerField( - unique=True, default=1, verbose_name="ID пользователя Телеграм" - ) - username = models.CharField(max_length=255, verbose_name="Username Telegram") - commentary = models.CharField(max_length=50, verbose_name="Комментарий", null=True) - time_event = models.CharField( - max_length=10, verbose_name="Время проведения", null=True - ) - venue = models.CharField(max_length=50, verbose_name="Место проведения", null=True) - need_location = models.CharField(max_length=50, null=True) - event_name = models.CharField( - max_length=50, verbose_name="Название мероприятия", null=True - ) - verification_status = models.BooleanField( - verbose_name="Статус пользователя", default=False - ) - moderation_process = models.BooleanField( - verbose_name="Процесс модерации", default=False - ) - is_premium = models.BooleanField(verbose_name="Премиум", default=False) - photo_id = models.CharField(max_length=400, verbose_name="Photo_ID", null=True) - is_admin = models.BooleanField(default=False) - is_active = models.BooleanField(default=True) - - def __str__(self): - return f"№{self.id} ({self.telegram_id} - {self.username})" diff --git a/django_project/telegrambot/usersmanage/models/necessary_link.py b/django_project/telegrambot/usersmanage/models/necessary_link.py deleted file mode 100644 index 44dd7f6..0000000 --- a/django_project/telegrambot/usersmanage/models/necessary_link.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.db import ( - models, -) - -from django_project.telegrambot.usersmanage.models.base import ( - TimeBasedModel, -) - - -class NecessaryLink(TimeBasedModel): - class Meta: - verbose_name = "Необходимая ссылка" - verbose_name_plural = "Необходимые ссылки" - - id = models.AutoField(primary_key=True) - link = models.URLField(verbose_name="Обязательная ссылка") - telegram_link_id = models.BigIntegerField(verbose_name="id канала/чата") - title = models.CharField( - verbose_name="Название кнопки. Можно смайлики", max_length=50 - ) diff --git a/django_project/telegrambot/usersmanage/models/settings_models.py b/django_project/telegrambot/usersmanage/models/settings_models.py deleted file mode 100644 index 6869722..0000000 --- a/django_project/telegrambot/usersmanage/models/settings_models.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.db import ( - models, -) - -from django_project.telegrambot.usersmanage.models.base import ( - TimeBasedModel, -) - - -class SettingModel(TimeBasedModel): - telegram_id = models.PositiveBigIntegerField( - unique=True, default=1, verbose_name="ID пользователя Телеграм" - ) - technical_works = models.BooleanField( - default=False, verbose_name="Технические работы" - ) - - objects = models.Manager() diff --git a/django_project/telegrambot/usersmanage/models/user.py b/django_project/telegrambot/usersmanage/models/user.py deleted file mode 100644 index 7af3b06..0000000 --- a/django_project/telegrambot/usersmanage/models/user.py +++ /dev/null @@ -1,94 +0,0 @@ -from django.contrib.postgres.fields import ( - ArrayField, -) -from django.db import ( - models, -) - -from django_project.telegrambot.usersmanage.models.base import ( - TimeBasedModel, -) - - -class User(TimeBasedModel): - class Meta: - verbose_name = ("Пользователь Знакомств",) - verbose_name_plural = "Пользователи Знакомств" - - id = models.AutoField(primary_key=True) - telegram_id = models.PositiveBigIntegerField( - unique=True, default=1, verbose_name="ID пользователя Телеграм" - ) - name = models.CharField(max_length=255, verbose_name="Имя пользователя") - username = models.CharField(max_length=255, verbose_name="Username Telegram") - sex = models.CharField( - max_length=30, verbose_name="Пол искателя", null=True, blank=True - ) - age = models.BigIntegerField(verbose_name="Возраст искателя", null=True, blank=True) - city = models.CharField( - max_length=255, verbose_name="Город искателя", null=True, blank=True - ) - need_city = models.CharField( - max_length=255, verbose_name="Город партнера", null=True, blank=True - ) - need_distance = models.PositiveIntegerField( - verbose_name="Расстояние между партнерами", null=True, blank=True - ) - longitude = models.FloatField( - verbose_name="координаты пользователя", null=True, blank=True - ) - latitude = models.FloatField( - verbose_name="координаты пользователя", null=True, blank=True - ) - verification = models.BooleanField(verbose_name="Верификация", default=False) - language = models.CharField( - max_length=10, verbose_name="Язык пользователя", null=True, blank=True - ) - varname = models.CharField( - max_length=100, verbose_name="Публичное имя пользователя", null=True, blank=True - ) - lifestyle = models.CharField( - max_length=100, verbose_name="Стиль жизни пользователя", null=True, blank=True - ) - is_banned = models.BooleanField( - verbose_name="Забанен ли пользователь", default=False - ) - photo_id = models.CharField(max_length=400, verbose_name="Photo_ID", null=True) - commentary = models.CharField( - max_length=300, verbose_name="Комментарий пользователя", null=True, blank=True - ) - need_partner_sex = models.CharField( - max_length=50, verbose_name="Пол партнера", null=True, blank=True - ) - need_partner_age_min = models.PositiveIntegerField( - verbose_name="Минимальный возраст партнера", default=16 - ) - need_partner_age_max = models.PositiveIntegerField( - verbose_name="Максимальный возраст партнера", default=24 - ) - referrer_id = models.PositiveBigIntegerField( - verbose_name="реферал", null=True, blank=True - ) - phone_number = models.BigIntegerField( - verbose_name="Номер телефона", null=True, blank=True - ) - status = models.BooleanField(verbose_name="Статус анкеты", default=False) - instagram = models.CharField( - max_length=200, verbose_name="Ник в инстаграме", null=True, blank=True - ) - events = ArrayField(models.CharField(max_length=200), default=list) - id_of_events_seen = ArrayField(models.CharField(max_length=255), default=list) - viewed_profiles = models.ManyToManyField( - "self", through="ViewedProfile", symmetrical=False - ) - limit_of_views = models.PositiveIntegerField(default=10, null=True) - counter_of_report = models.PositiveIntegerField(default=0, null=True) - on_check_by_admin = models.BooleanField(default=False, null=True) - - def __str__(self): - return f"№{self.id} ({self.telegram_id}) - {self.name}" - - def remove_events(self, event_to_remove): - if event_to_remove in self.events: - self.events.remove(event_to_remove) - self.save() diff --git a/django_project/telegrambot/usersmanage/models/viewed_profile.py b/django_project/telegrambot/usersmanage/models/viewed_profile.py deleted file mode 100644 index 0c24b3e..0000000 --- a/django_project/telegrambot/usersmanage/models/viewed_profile.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.db import ( - models, -) - -from django_project.telegrambot.usersmanage.models.user import ( - User, -) - - -class ViewedProfile(models.Model): - viewer = models.ForeignKey(User, related_name="viewer", on_delete=models.CASCADE) - profile = models.ForeignKey(User, related_name="profile", on_delete=models.CASCADE) - viewed_at = models.DateTimeField(auto_now_add=True) - - class Meta: - unique_together = ("viewer", "profile") diff --git a/django_project/telegrambot/usersmanage/views.py b/django_project/telegrambot/usersmanage/views.py deleted file mode 100644 index 2ba86c8..0000000 --- a/django_project/telegrambot/usersmanage/views.py +++ /dev/null @@ -1,67 +0,0 @@ -import csv - -from django.http import ( - HttpResponse, -) - -from django_project.telegrambot.usersmanage.models.user import ( - User, -) - - -def export_users_csv(request): - response = HttpResponse(content_type="text/csv") - - writer = csv.writer(response) - writer.writerow([ - "id", - "telegram_id", - "name", - "username", - "sex", - "age", - "city", - "need_city", - "longitude", - "latitude", - "verification", - "language", - "varname", - "lifestyle", - "is_banned", - "photo_id", - "commentary", - "need_partner_sex", - "need_partner_age_min", - "need_partner_age_max", - "phone_number", - "status", - "instagram" - ]) - for user in User.objects.all().values_list("id", - "telegram_id", - "name", - "username", - "sex", - "age", - "city", - "need_city", - "longitude", - "latitude", - "verification", - "language", - "varname", - "lifestyle", - "is_banned", - "photo_id", - "commentary", - "need_partner_sex", - "need_partner_age_min", - "need_partner_age_max", - "phone_number", - "status", - "instagram"): - writer.writerow(user) - - response['Content-Disposition'] = 'attachment; filename="users.csv"' - return response From 276994bd7533ad2e7ce481672384d34d23cf2a81 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:36:04 +0300 Subject: [PATCH 012/148] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Move=20legacy?= =?UTF-8?q?=20code=20to=20deprecated=20folder=20for=20future=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin}/__init__.py | 0 .../admin/inline}/__init__.py | 0 .../admin/inline/customers.py | 0 .../admin/inline/mailing.py | 0 .../admin/inline/payments.py | 0 .../admin/inline/ref.py | 0 .../admin/inline/reply_menu.py | 0 .../admin/inline/setting.py | 0 .../admin/main_menu.py | 0 .../app => deprecated}/app_scheduler.py | 6 +- .../create_forms_funcs.py | 7 +- .../app => deprecated}/data_operations.py | 6 +- .../common => deprecated/default}/__init__.py | 0 .../default/admin_default.py | 0 .../default/get_contact_default.py | 0 .../default/get_location_default.py | 0 .../default/get_photo.py | 0 .../app => deprecated}/determin_location.py | 12 +- deprecated/extra_features.py | 119 ++++++++ deprecated/get_next_user_func.py | 53 ++++ .../handlers}/__init__.py | 0 .../handlers/admins/__init__.py | 0 .../handlers/admins/advert/__init__.py | 0 .../handlers/admins/advert/advertisement.py | 0 .../admins/advert/mailing/__init__.py | 0 .../handlers/admins/advert/mailing/create.py | 0 .../handlers/admins/advert/ref}/__init__.py | 0 .../admins/advert/requiredsub}/__init__.py | 0 .../handlers/admins/customers/__init__.py | 0 .../handlers/admins/customers/users.py | 0 .../handlers/admins/monitoring.py | 2 +- .../handlers/admins/settings/__init__.py | 0 .../handlers/admins/settings/admins.py | 0 .../handlers/admins/settings/logs_user.py | 2 +- .../handlers/admins/settings/setting.py | 0 .../handlers/admins/settings/tech_works.py | 2 +- .../handlers/echo_handler.py | 0 .../handlers/errors/__init__.py | 0 .../handlers/errors/error_handler.py | 0 .../handlers/groups/__init__.py | 0 .../handlers/groups/event_moderate.py | 0 .../handlers/groups/start.py | 0 .../handlers/users/__init__.py | 0 .../handlers/users/back.py | 4 +- .../handlers/users/brandbook.py | 2 +- .../handlers/users/buy_unban.py | 0 .../handlers/users/change_datas.py | 4 +- .../handlers/users/change_event_datas.py | 0 .../handlers/users/event.py | 6 +- .../handlers/users/event_list.py | 2 +- .../handlers/users/filters.py | 6 +- .../handlers/users/registration.py | 6 +- .../handlers/users/start.py | 2 +- .../handlers/users/support.py | 0 .../handlers/users/user_profile.py | 2 +- .../handlers/users/verification.py | 0 .../handlers/users/view_event.py | 2 +- .../handlers/users/view_ques.py | 4 +- .../inline}/__init__.py | 0 .../inline/admin_inline.py | 0 .../inline/back_inline.py | 0 .../inline/calendar.py | 0 .../inline/cancel_inline.py | 0 .../inline/change_data_profile_inline.py | 0 .../inline/filters_inline.py | 0 .../inline/guide_inline.py | 0 .../inline/language_inline.py | 0 .../inline/main_menu_inline.py | 0 .../inline/menu_profile_inline.py | 0 .../inline/necessary_links_inline.py | 0 .../inline/payments_inline.py | 0 .../inline/poster_inline.py | 0 .../inline/questionnaires_inline.py | 0 .../inline/registration_inline.py | 0 .../inline/settings_menu.py | 0 .../inline/support_inline.py | 0 .../app => deprecated}/language_ware.py | 9 +- deprecated/message_operations.py | 263 ++++++++++++++++++ deprecated/notify_admins.py | 60 ++++ deprecated/photo_operations.py | 103 +++++++ deprecated/reaction_strategies.py | 252 +++++++++++++++++ deprecated/send_form_func.py | 134 +++++++++ .../services/app => deprecated}/statistics.py | 9 +- .../templates_messages.py | 12 +- 84 files changed, 1022 insertions(+), 69 deletions(-) rename {django_project => deprecated/admin}/__init__.py (100%) rename {django_project/telegrambot => deprecated/admin/inline}/__init__.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/inline/customers.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/inline/mailing.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/inline/payments.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/inline/ref.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/inline/reply_menu.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/inline/setting.py (100%) rename {src/tgbot/keyboards => deprecated}/admin/main_menu.py (100%) rename {src/tgbot/services/app => deprecated}/app_scheduler.py (85%) rename {src/tgbot/services/dating => deprecated}/create_forms_funcs.py (92%) rename {src/tgbot/services/app => deprecated}/data_operations.py (82%) rename {django_project/telegrambot/common => deprecated/default}/__init__.py (100%) rename {src/tgbot/keyboards => deprecated}/default/admin_default.py (100%) rename {src/tgbot/keyboards => deprecated}/default/get_contact_default.py (100%) rename {src/tgbot/keyboards => deprecated}/default/get_location_default.py (100%) rename {src/tgbot/keyboards => deprecated}/default/get_photo.py (100%) rename {src/tgbot/services/app => deprecated}/determin_location.py (87%) create mode 100644 deprecated/extra_features.py create mode 100644 deprecated/get_next_user_func.py rename {django_project/telegrambot/common/migrations => deprecated/handlers}/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/advert/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/advert/advertisement.py (100%) rename {src/tgbot => deprecated}/handlers/admins/advert/mailing/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/advert/mailing/create.py (100%) rename {django_project/telegrambot/telegrambot => deprecated/handlers/admins/advert/ref}/__init__.py (100%) rename {django_project/telegrambot/usersmanage => deprecated/handlers/admins/advert/requiredsub}/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/customers/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/customers/users.py (100%) rename {src/tgbot => deprecated}/handlers/admins/monitoring.py (96%) rename {src/tgbot => deprecated}/handlers/admins/settings/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/admins/settings/admins.py (100%) rename {src/tgbot => deprecated}/handlers/admins/settings/logs_user.py (96%) rename {src/tgbot => deprecated}/handlers/admins/settings/setting.py (100%) rename {src/tgbot => deprecated}/handlers/admins/settings/tech_works.py (97%) rename {src/tgbot => deprecated}/handlers/echo_handler.py (100%) rename {src/tgbot => deprecated}/handlers/errors/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/errors/error_handler.py (100%) rename {src/tgbot => deprecated}/handlers/groups/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/groups/event_moderate.py (100%) rename {src/tgbot => deprecated}/handlers/groups/start.py (100%) rename {src/tgbot => deprecated}/handlers/users/__init__.py (100%) rename {src/tgbot => deprecated}/handlers/users/back.py (96%) rename {src/tgbot => deprecated}/handlers/users/brandbook.py (96%) rename {src/tgbot => deprecated}/handlers/users/buy_unban.py (100%) rename {src/tgbot => deprecated}/handlers/users/change_datas.py (99%) rename {src/tgbot => deprecated}/handlers/users/change_event_datas.py (100%) rename {src/tgbot => deprecated}/handlers/users/event.py (98%) rename {src/tgbot => deprecated}/handlers/users/event_list.py (95%) rename {src/tgbot => deprecated}/handlers/users/filters.py (96%) rename {src/tgbot => deprecated}/handlers/users/registration.py (98%) rename {src/tgbot => deprecated}/handlers/users/start.py (98%) rename {src/tgbot => deprecated}/handlers/users/support.py (100%) rename {src/tgbot => deprecated}/handlers/users/user_profile.py (94%) rename {src/tgbot => deprecated}/handlers/users/verification.py (100%) rename {src/tgbot => deprecated}/handlers/users/view_event.py (97%) rename {src/tgbot => deprecated}/handlers/users/view_ques.py (97%) rename {django_project/telegrambot/usersmanage/migrations => deprecated/inline}/__init__.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/admin_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/back_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/calendar.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/cancel_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/change_data_profile_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/filters_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/guide_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/language_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/main_menu_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/menu_profile_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/necessary_links_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/payments_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/poster_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/questionnaires_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/registration_inline.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/settings_menu.py (100%) rename {src/tgbot/keyboards => deprecated}/inline/support_inline.py (100%) rename {src/tgbot/services/app => deprecated}/language_ware.py (79%) create mode 100644 deprecated/message_operations.py create mode 100644 deprecated/notify_admins.py create mode 100644 deprecated/photo_operations.py create mode 100644 deprecated/reaction_strategies.py create mode 100644 deprecated/send_form_func.py rename {src/tgbot/services/app => deprecated}/statistics.py (92%) rename {src/tgbot/services/event => deprecated}/templates_messages.py (92%) diff --git a/django_project/__init__.py b/deprecated/admin/__init__.py similarity index 100% rename from django_project/__init__.py rename to deprecated/admin/__init__.py diff --git a/django_project/telegrambot/__init__.py b/deprecated/admin/inline/__init__.py similarity index 100% rename from django_project/telegrambot/__init__.py rename to deprecated/admin/inline/__init__.py diff --git a/src/tgbot/keyboards/admin/inline/customers.py b/deprecated/admin/inline/customers.py similarity index 100% rename from src/tgbot/keyboards/admin/inline/customers.py rename to deprecated/admin/inline/customers.py diff --git a/src/tgbot/keyboards/admin/inline/mailing.py b/deprecated/admin/inline/mailing.py similarity index 100% rename from src/tgbot/keyboards/admin/inline/mailing.py rename to deprecated/admin/inline/mailing.py diff --git a/src/tgbot/keyboards/admin/inline/payments.py b/deprecated/admin/inline/payments.py similarity index 100% rename from src/tgbot/keyboards/admin/inline/payments.py rename to deprecated/admin/inline/payments.py diff --git a/src/tgbot/keyboards/admin/inline/ref.py b/deprecated/admin/inline/ref.py similarity index 100% rename from src/tgbot/keyboards/admin/inline/ref.py rename to deprecated/admin/inline/ref.py diff --git a/src/tgbot/keyboards/admin/inline/reply_menu.py b/deprecated/admin/inline/reply_menu.py similarity index 100% rename from src/tgbot/keyboards/admin/inline/reply_menu.py rename to deprecated/admin/inline/reply_menu.py diff --git a/src/tgbot/keyboards/admin/inline/setting.py b/deprecated/admin/inline/setting.py similarity index 100% rename from src/tgbot/keyboards/admin/inline/setting.py rename to deprecated/admin/inline/setting.py diff --git a/src/tgbot/keyboards/admin/main_menu.py b/deprecated/admin/main_menu.py similarity index 100% rename from src/tgbot/keyboards/admin/main_menu.py rename to deprecated/admin/main_menu.py diff --git a/src/tgbot/services/app/app_scheduler.py b/deprecated/app_scheduler.py similarity index 85% rename from src/tgbot/services/app/app_scheduler.py rename to deprecated/app_scheduler.py index f35952e..d68c5f2 100644 --- a/src/tgbot/services/app/app_scheduler.py +++ b/deprecated/app_scheduler.py @@ -6,15 +6,13 @@ _, bot, ) -from src.infrastructure.db_api import ( - db_commands, -) + from src.tgbot.keyboards.inline.questionnaires_inline import ( viewing_ques_keyboard, ) -async def send_message_week(message: Message) -> None: +async def send_message_week(message: Message, db_commands=None) -> None: user = await db_commands.select_user(telegram_id=message.from_user.id) user_gender = "Парней" if user.need_partner_sex == "Мужской" else "Девушек" diff --git a/src/tgbot/services/dating/create_forms_funcs.py b/deprecated/create_forms_funcs.py similarity index 92% rename from src/tgbot/services/dating/create_forms_funcs.py rename to deprecated/create_forms_funcs.py index 10f2514..1e56b4c 100644 --- a/src/tgbot/services/dating/create_forms_funcs.py +++ b/deprecated/create_forms_funcs.py @@ -1,8 +1,5 @@ import random import secrets -from typing import ( - Optional, -) from aiogram.types import ( CallbackQuery, @@ -17,10 +14,10 @@ from src.tgbot.keyboards.inline.questionnaires_inline import ( questionnaires_keyboard, ) -from src.tgbot.services.dating.get_next_user_func import ( +from deprecated.get_next_user_func import ( get_next_user, ) -from src.tgbot.services.dating.send_form_func import ( +from deprecated.send_form_func import ( send_questionnaire, ) diff --git a/src/tgbot/services/app/data_operations.py b/deprecated/data_operations.py similarity index 82% rename from src/tgbot/services/app/data_operations.py rename to deprecated/data_operations.py index 88896d3..8386636 100644 --- a/src/tgbot/services/app/data_operations.py +++ b/deprecated/data_operations.py @@ -2,12 +2,8 @@ import aiofiles -from src.infrastructure.db_api import ( - db_commands, -) - -async def dump_users_to_file(): +async def dump_users_to_file(db_commands=None): async with aiofiles.open("users.txt", "w", encoding="utf-8") as file: _text = "" _users = await db_commands.select_all_users() diff --git a/django_project/telegrambot/common/__init__.py b/deprecated/default/__init__.py similarity index 100% rename from django_project/telegrambot/common/__init__.py rename to deprecated/default/__init__.py diff --git a/src/tgbot/keyboards/default/admin_default.py b/deprecated/default/admin_default.py similarity index 100% rename from src/tgbot/keyboards/default/admin_default.py rename to deprecated/default/admin_default.py diff --git a/src/tgbot/keyboards/default/get_contact_default.py b/deprecated/default/get_contact_default.py similarity index 100% rename from src/tgbot/keyboards/default/get_contact_default.py rename to deprecated/default/get_contact_default.py diff --git a/src/tgbot/keyboards/default/get_location_default.py b/deprecated/default/get_location_default.py similarity index 100% rename from src/tgbot/keyboards/default/get_location_default.py rename to deprecated/default/get_location_default.py diff --git a/src/tgbot/keyboards/default/get_photo.py b/deprecated/default/get_photo.py similarity index 100% rename from src/tgbot/keyboards/default/get_photo.py rename to deprecated/default/get_photo.py diff --git a/src/tgbot/services/app/determin_location.py b/deprecated/determin_location.py similarity index 87% rename from src/tgbot/services/app/determin_location.py rename to deprecated/determin_location.py index cab66c4..0427666 100644 --- a/src/tgbot/services/app/determin_location.py +++ b/deprecated/determin_location.py @@ -18,9 +18,7 @@ from src.infrastructure.YandexMap.exceptions import ( NothingFound, ) -from src.infrastructure.db_api import ( - db_commands, -) + from src.tgbot.keyboards.inline.registration_inline import ( confirm_keyboard, ) @@ -64,7 +62,7 @@ async def det_loc(self) -> None: class RegistrationStrategy(UserDataUpdateStrategy): - async def update_user_data(self: Location, message: Message): + async def update_user_data(self: Location, message: Message, db_commands=None): await db_commands.update_user_data( telegram_id=message.from_user.id, city=self.city, @@ -75,21 +73,21 @@ async def update_user_data(self: Location, message: Message): class FiltersStrategy(UserDataUpdateStrategy): - async def update_user_data(self: Location, message: Message): + async def update_user_data(self: Location, message: Message, db_commands=None): await db_commands.update_user_data( telegram_id=message.from_user.id, need_city=self.city ) class EventStrategy(UserDataUpdateStrategy): - async def update_user_data(self: Location, message: Message): + async def update_user_data(self: Location, message: Message, db_commands=None): await db_commands.update_user_meetings_data( telegram_id=message.from_user.id, venue=self.city ) class EventFiltersStrategy(UserDataUpdateStrategy): - async def update_user_data(self: Location, message: Message): + async def update_user_data(self: Location, message: Message, db_commands=None): await db_commands.update_user_meetings_data( telegram_id=message.from_user.id, need_location=self.city ) diff --git a/deprecated/extra_features.py b/deprecated/extra_features.py new file mode 100644 index 0000000..72d5fab --- /dev/null +++ b/deprecated/extra_features.py @@ -0,0 +1,119 @@ +# from datetime import ( +# datetime, +# ) +# +# from aiogram.types import ( +# CallbackQuery, +# ) +# from aiogram.utils.exceptions import ( +# BadRequest, +# ) +# +# from loader import ( +# _, +# bot, +# ) +# from src.infrastructure.db_api import ( +# db_commands, +# ) +# from deprecated.templates_messages import ( +# ME, +# ) +# +# +# async def add_events_to_user(call: CallbackQuery, event_id: int) -> None: +# """Function that stores id's of events liked by a user.""" +# user = await db_commands.select_user(telegram_id=call.from_user.id) +# event_list = user.events +# +# if str(event_id) not in event_list: +# await db_commands.update_user_events( +# telegram_id=call.from_user.id, events_id=event_id +# ) +# +# +# async def check_availability_on_event() -> bool: +# """Function that checks the availability of seats for an event.""" +# ... +# +# +# async def check_event_date(telegram_id: int) -> None: +# """Function that checks whether an event has passed or not.""" +# event = await db_commands.select_user_meetings(telegram_id) +# event_time = event.time_event +# if event_time is None: +# return +# event_datetime, now_datetime = ( +# datetime.strptime(event_time, "%d-%m-%Y"), +# datetime.now().date(), +# ) +# is_admin = True +# verification_status = True +# is_active = True +# +# if event_datetime.date() <= now_datetime: +# is_admin = False +# verification_status = False +# is_active = False +# await db_commands.update_user_meetings_data( +# telegram_id=telegram_id, +# is_admin=is_admin, +# verification_status=verification_status, +# is_active=is_active, +# ) +# +# +# async def create_form( +# form_owner: int, chat_id: int, call: CallbackQuery, view: bool | None = True +# ) -> None: +# """Function that fills the form with text.""" +# try: +# owner = await db_commands.select_user_meetings(telegram_id=form_owner) +# document = { +# "title": owner.event_name, +# "date": owner.time_event, +# "place": owner.venue, +# "description": owner.commentary, +# "photo_id": owner.photo_id, +# "telegram_id": form_owner, +# } +# if view: +# await ME.send_event_message( +# text=document, bot=bot, chat_id=chat_id, view_event=True, call=call +# ) +# else: +# await ME.send_event_list( +# text=document, call=call, bot=bot, telegram_id=call.from_user.id +# ) +# except BadRequest: +# await call.answer( +# text=_("На данный момент у нас нет подходящих мероприятий для вас"), +# show_alert=True, +# ) +# +# +# async def get_next_random_event_id(telegram_id: int) -> int | None: +# """Function that returns a random id of an event created by another user.""" +# event_ids = await db_commands.search_event_forms() +# +# other_events_ids = [] +# for e in event_ids: +# if e["telegram_id"] != telegram_id: +# other_events_ids.append(e["telegram_id"]) +# +# for event_id in other_events_ids: +# if not await db_commands.check_returned_event_id( +# telegram_id=telegram_id, id_of_events_seen=event_id +# ): +# await db_commands.add_returned_event_id( +# telegram_id=telegram_id, id_of_events_seen=event_id +# ) +# return event_id +# +# raise ValueError("No more event ids") +# +# +# async def get_next_registration(telegram_id: int) -> list[int]: +# user = await db_commands.select_user(telegram_id=telegram_id) +# events: list = user.events +# return events diff --git a/deprecated/get_next_user_func.py b/deprecated/get_next_user_func.py new file mode 100644 index 0000000..cea47f8 --- /dev/null +++ b/deprecated/get_next_user_func.py @@ -0,0 +1,53 @@ +# from typing import ( +# List, +# ) +# +# from async_lru import ( +# alru_cache, +# ) +# +# from src.infrastructure.db_api import ( +# db_commands, +# ) +# +# +# @alru_cache +# async def get_next_user( +# telegram_id: int, monitoring: bool = False, offset: int = 0, limit: int = 100 +# ) -> List[int]: +# user = await db_commands.select_user_object(telegram_id=telegram_id) +# viewed_profiles = user.viewed_profiles.all() +# +# if monitoring: +# user_filter = await db_commands.search_users_all(offset, limit) +# else: +# user_filter = await db_commands.search_users( +# user.need_partner_sex, +# user.need_partner_age_min, +# user.need_partner_age_max, +# user.need_city, +# offset, +# limit, +# ) +# +# viewed_profiles_ids = [profile.telegram_id for profile in viewed_profiles] +# +# user_list = [] +# for i in user_filter: +# if ( +# int(i["telegram_id"]) != int(telegram_id) +# and i["telegram_id"] not in viewed_profiles_ids +# ): +# user_list.append(i["telegram_id"]) +# +# if not user_list: +# user_filter_2 = await db_commands.search_users_all(offset, limit) +# for k in user_filter_2: +# if ( +# k not in user_filter +# and int(k["telegram_id"]) != int(telegram_id) +# and k["telegram_id"] not in viewed_profiles_ids +# ): +# user_list.append(k["telegram_id"]) +# +# return user_list diff --git a/django_project/telegrambot/common/migrations/__init__.py b/deprecated/handlers/__init__.py similarity index 100% rename from django_project/telegrambot/common/migrations/__init__.py rename to deprecated/handlers/__init__.py diff --git a/src/tgbot/handlers/admins/__init__.py b/deprecated/handlers/admins/__init__.py similarity index 100% rename from src/tgbot/handlers/admins/__init__.py rename to deprecated/handlers/admins/__init__.py diff --git a/src/tgbot/handlers/admins/advert/__init__.py b/deprecated/handlers/admins/advert/__init__.py similarity index 100% rename from src/tgbot/handlers/admins/advert/__init__.py rename to deprecated/handlers/admins/advert/__init__.py diff --git a/src/tgbot/handlers/admins/advert/advertisement.py b/deprecated/handlers/admins/advert/advertisement.py similarity index 100% rename from src/tgbot/handlers/admins/advert/advertisement.py rename to deprecated/handlers/admins/advert/advertisement.py diff --git a/src/tgbot/handlers/admins/advert/mailing/__init__.py b/deprecated/handlers/admins/advert/mailing/__init__.py similarity index 100% rename from src/tgbot/handlers/admins/advert/mailing/__init__.py rename to deprecated/handlers/admins/advert/mailing/__init__.py diff --git a/src/tgbot/handlers/admins/advert/mailing/create.py b/deprecated/handlers/admins/advert/mailing/create.py similarity index 100% rename from src/tgbot/handlers/admins/advert/mailing/create.py rename to deprecated/handlers/admins/advert/mailing/create.py diff --git a/django_project/telegrambot/telegrambot/__init__.py b/deprecated/handlers/admins/advert/ref/__init__.py similarity index 100% rename from django_project/telegrambot/telegrambot/__init__.py rename to deprecated/handlers/admins/advert/ref/__init__.py diff --git a/django_project/telegrambot/usersmanage/__init__.py b/deprecated/handlers/admins/advert/requiredsub/__init__.py similarity index 100% rename from django_project/telegrambot/usersmanage/__init__.py rename to deprecated/handlers/admins/advert/requiredsub/__init__.py diff --git a/src/tgbot/handlers/admins/customers/__init__.py b/deprecated/handlers/admins/customers/__init__.py similarity index 100% rename from src/tgbot/handlers/admins/customers/__init__.py rename to deprecated/handlers/admins/customers/__init__.py diff --git a/src/tgbot/handlers/admins/customers/users.py b/deprecated/handlers/admins/customers/users.py similarity index 100% rename from src/tgbot/handlers/admins/customers/users.py rename to deprecated/handlers/admins/customers/users.py diff --git a/src/tgbot/handlers/admins/monitoring.py b/deprecated/handlers/admins/monitoring.py similarity index 96% rename from src/tgbot/handlers/admins/monitoring.py rename to deprecated/handlers/admins/monitoring.py index 96d8b38..5982bd6 100644 --- a/src/tgbot/handlers/admins/monitoring.py +++ b/deprecated/handlers/admins/monitoring.py @@ -18,7 +18,7 @@ from src.tgbot.keyboards.inline.questionnaires_inline import ( action_keyboard_monitoring, ) -from src.tgbot.services.dating.create_forms_funcs import ( +from deprecated.create_forms_funcs import ( monitoring_questionnaire, ) diff --git a/src/tgbot/handlers/admins/settings/__init__.py b/deprecated/handlers/admins/settings/__init__.py similarity index 100% rename from src/tgbot/handlers/admins/settings/__init__.py rename to deprecated/handlers/admins/settings/__init__.py diff --git a/src/tgbot/handlers/admins/settings/admins.py b/deprecated/handlers/admins/settings/admins.py similarity index 100% rename from src/tgbot/handlers/admins/settings/admins.py rename to deprecated/handlers/admins/settings/admins.py diff --git a/src/tgbot/handlers/admins/settings/logs_user.py b/deprecated/handlers/admins/settings/logs_user.py similarity index 96% rename from src/tgbot/handlers/admins/settings/logs_user.py rename to deprecated/handlers/admins/settings/logs_user.py index 2220e38..86467cb 100644 --- a/src/tgbot/handlers/admins/settings/logs_user.py +++ b/deprecated/handlers/admins/settings/logs_user.py @@ -19,7 +19,7 @@ from src.tgbot.keyboards.admin.inline.reply_menu import ( logs_keyboard, ) -from src.tgbot.services.app.data_operations import ( +from deprecated.data_operations import ( backup_configs, dump_users_to_file, ) diff --git a/src/tgbot/handlers/admins/settings/setting.py b/deprecated/handlers/admins/settings/setting.py similarity index 100% rename from src/tgbot/handlers/admins/settings/setting.py rename to deprecated/handlers/admins/settings/setting.py diff --git a/src/tgbot/handlers/admins/settings/tech_works.py b/deprecated/handlers/admins/settings/tech_works.py similarity index 97% rename from src/tgbot/handlers/admins/settings/tech_works.py rename to deprecated/handlers/admins/settings/tech_works.py index 86482ef..f313998 100644 --- a/src/tgbot/handlers/admins/settings/tech_works.py +++ b/deprecated/handlers/admins/settings/tech_works.py @@ -22,7 +22,7 @@ from src.tgbot.keyboards.inline.admin_inline import ( tech_works_keyboard, ) -from src.tgbot.services.app.statistics import ( +from deprecated.statistics import ( get_statistics, ) diff --git a/src/tgbot/handlers/echo_handler.py b/deprecated/handlers/echo_handler.py similarity index 100% rename from src/tgbot/handlers/echo_handler.py rename to deprecated/handlers/echo_handler.py diff --git a/src/tgbot/handlers/errors/__init__.py b/deprecated/handlers/errors/__init__.py similarity index 100% rename from src/tgbot/handlers/errors/__init__.py rename to deprecated/handlers/errors/__init__.py diff --git a/src/tgbot/handlers/errors/error_handler.py b/deprecated/handlers/errors/error_handler.py similarity index 100% rename from src/tgbot/handlers/errors/error_handler.py rename to deprecated/handlers/errors/error_handler.py diff --git a/src/tgbot/handlers/groups/__init__.py b/deprecated/handlers/groups/__init__.py similarity index 100% rename from src/tgbot/handlers/groups/__init__.py rename to deprecated/handlers/groups/__init__.py diff --git a/src/tgbot/handlers/groups/event_moderate.py b/deprecated/handlers/groups/event_moderate.py similarity index 100% rename from src/tgbot/handlers/groups/event_moderate.py rename to deprecated/handlers/groups/event_moderate.py diff --git a/src/tgbot/handlers/groups/start.py b/deprecated/handlers/groups/start.py similarity index 100% rename from src/tgbot/handlers/groups/start.py rename to deprecated/handlers/groups/start.py diff --git a/src/tgbot/handlers/users/__init__.py b/deprecated/handlers/users/__init__.py similarity index 100% rename from src/tgbot/handlers/users/__init__.py rename to deprecated/handlers/users/__init__.py diff --git a/src/tgbot/handlers/users/back.py b/deprecated/handlers/users/back.py similarity index 96% rename from src/tgbot/handlers/users/back.py rename to deprecated/handlers/users/back.py index a52d084..8334067 100644 --- a/src/tgbot/handlers/users/back.py +++ b/deprecated/handlers/users/back.py @@ -24,13 +24,13 @@ from src.tgbot.keyboards.inline.admin_inline import ( unban_user_keyboard, ) -from src.tgbot.keyboards.inline.filters_inline import ( +from deprecated.inline.filters_inline import ( filters_keyboard, ) from src.tgbot.keyboards.inline.menu_profile_inline import ( get_profile_keyboard, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( delete_message, display_profile, information_menu, diff --git a/src/tgbot/handlers/users/brandbook.py b/deprecated/handlers/users/brandbook.py similarity index 96% rename from src/tgbot/handlers/users/brandbook.py rename to deprecated/handlers/users/brandbook.py index 511839e..dd899ea 100644 --- a/src/tgbot/handlers/users/brandbook.py +++ b/deprecated/handlers/users/brandbook.py @@ -12,7 +12,7 @@ from src.tgbot.keyboards.inline.guide_inline import ( guide_callback, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( handle_guide_callback, information_menu, send_photo_with_caption, diff --git a/src/tgbot/handlers/users/buy_unban.py b/deprecated/handlers/users/buy_unban.py similarity index 100% rename from src/tgbot/handlers/users/buy_unban.py rename to deprecated/handlers/users/buy_unban.py diff --git a/src/tgbot/handlers/users/change_datas.py b/deprecated/handlers/users/change_datas.py similarity index 99% rename from src/tgbot/handlers/users/change_datas.py rename to deprecated/handlers/users/change_datas.py index 5a0e9e3..a160527 100644 --- a/src/tgbot/handlers/users/change_datas.py +++ b/deprecated/handlers/users/change_datas.py @@ -47,11 +47,11 @@ from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from src.tgbot.services.app.determin_location import ( +from deprecated.determin_location import ( Location, RegistrationStrategy, ) -from src.tgbot.services.app.photo_operations import ( +from deprecated.photo_operations import ( saving_censored_photo, update_normal_photo, ) diff --git a/src/tgbot/handlers/users/change_event_datas.py b/deprecated/handlers/users/change_event_datas.py similarity index 100% rename from src/tgbot/handlers/users/change_event_datas.py rename to deprecated/handlers/users/change_event_datas.py diff --git a/src/tgbot/handlers/users/event.py b/deprecated/handlers/users/event.py similarity index 98% rename from src/tgbot/handlers/users/event.py rename to deprecated/handlers/users/event.py index a7fb45a..9ab8c51 100644 --- a/src/tgbot/handlers/users/event.py +++ b/deprecated/handlers/users/event.py @@ -44,14 +44,14 @@ cancel_registration_keyboard, poster_keyboard, ) -from src.tgbot.services.app.determin_location import ( +from deprecated.determin_location import ( EventStrategy, Location, ) -from src.tgbot.services.event.extra_features import ( +from deprecated.extra_features import ( check_event_date, ) -from src.tgbot.services.event.templates_messages import ( +from deprecated.templates_messages import ( ME, ) diff --git a/src/tgbot/handlers/users/event_list.py b/deprecated/handlers/users/event_list.py similarity index 95% rename from src/tgbot/handlers/users/event_list.py rename to deprecated/handlers/users/event_list.py index 308a95c..505f103 100644 --- a/src/tgbot/handlers/users/event_list.py +++ b/deprecated/handlers/users/event_list.py @@ -14,7 +14,7 @@ from src.infrastructure.db_api import ( db_commands, ) -from src.tgbot.services.event.extra_features import ( +from deprecated.extra_features import ( create_form, get_next_registration, ) diff --git a/src/tgbot/handlers/users/filters.py b/deprecated/handlers/users/filters.py similarity index 96% rename from src/tgbot/handlers/users/filters.py rename to deprecated/handlers/users/filters.py index 83838b8..2ed45cc 100644 --- a/src/tgbot/handlers/users/filters.py +++ b/deprecated/handlers/users/filters.py @@ -30,15 +30,15 @@ from src.tgbot.keyboards.inline.change_data_profile_inline import ( gender_keyboard, ) -from src.tgbot.keyboards.inline.filters_inline import ( +from deprecated.inline.filters_inline import ( event_filters_keyboard, filters_keyboard, ) -from src.tgbot.services.app.determin_location import ( +from deprecated.determin_location import ( FiltersStrategy, Location, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( choice_gender, show_dating_filters, ) diff --git a/src/tgbot/handlers/users/registration.py b/deprecated/handlers/users/registration.py similarity index 98% rename from src/tgbot/handlers/users/registration.py rename to deprecated/handlers/users/registration.py index a9a4a3a..c1c56a4 100644 --- a/src/tgbot/handlers/users/registration.py +++ b/deprecated/handlers/users/registration.py @@ -58,14 +58,14 @@ from src.tgbot.services.app.profanityFilter import ( censored_message, ) -from src.tgbot.services.app.determin_location import ( +from deprecated.determin_location import ( Location, RegistrationStrategy, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( choice_gender, ) -from src.tgbot.services.app.photo_operations import ( +from deprecated.photo_operations import ( saving_censored_photo, saving_normal_photo, ) diff --git a/src/tgbot/handlers/users/start.py b/deprecated/handlers/users/start.py similarity index 98% rename from src/tgbot/handlers/users/start.py rename to deprecated/handlers/users/start.py index 8b50e28..5f4fba6 100644 --- a/src/tgbot/handlers/users/start.py +++ b/deprecated/handlers/users/start.py @@ -25,7 +25,7 @@ from src.tgbot.keyboards.inline.language_inline import ( language_keyboard, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( check_user_in_db, delete_message, registration_menu, diff --git a/src/tgbot/handlers/users/support.py b/deprecated/handlers/users/support.py similarity index 100% rename from src/tgbot/handlers/users/support.py rename to deprecated/handlers/users/support.py diff --git a/src/tgbot/handlers/users/user_profile.py b/deprecated/handlers/users/user_profile.py similarity index 94% rename from src/tgbot/handlers/users/user_profile.py rename to deprecated/handlers/users/user_profile.py index 3e4ab22..0a82dfa 100644 --- a/src/tgbot/handlers/users/user_profile.py +++ b/deprecated/handlers/users/user_profile.py @@ -15,7 +15,7 @@ from src.tgbot.keyboards.inline.menu_profile_inline import ( get_profile_keyboard, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( display_profile, ) diff --git a/src/tgbot/handlers/users/verification.py b/deprecated/handlers/users/verification.py similarity index 100% rename from src/tgbot/handlers/users/verification.py rename to deprecated/handlers/users/verification.py diff --git a/src/tgbot/handlers/users/view_event.py b/deprecated/handlers/users/view_event.py similarity index 97% rename from src/tgbot/handlers/users/view_event.py rename to deprecated/handlers/users/view_event.py index 8271048..371a1d2 100644 --- a/src/tgbot/handlers/users/view_event.py +++ b/deprecated/handlers/users/view_event.py @@ -13,7 +13,7 @@ from src.tgbot.keyboards.inline.poster_inline import ( poster_keyboard, ) -from src.tgbot.services.event.extra_features import ( +from deprecated.extra_features import ( add_events_to_user, check_event_date, create_form, diff --git a/src/tgbot/handlers/users/view_ques.py b/deprecated/handlers/users/view_ques.py similarity index 97% rename from src/tgbot/handlers/users/view_ques.py rename to deprecated/handlers/users/view_ques.py index cf16ef5..f6233f8 100644 --- a/src/tgbot/handlers/users/view_ques.py +++ b/deprecated/handlers/users/view_ques.py @@ -25,7 +25,7 @@ action_reciprocity_keyboard, action_report_keyboard, ) -from src.tgbot.services.app.message_operations import ( +from deprecated.message_operations import ( delete_message, ) from src.tgbot.services.dating import ( @@ -41,7 +41,7 @@ StartFindingSuccess, StoppedAction, ) -from src.tgbot.services.dating.get_next_user_func import ( +from deprecated.get_next_user_func import ( get_next_user, ) diff --git a/django_project/telegrambot/usersmanage/migrations/__init__.py b/deprecated/inline/__init__.py similarity index 100% rename from django_project/telegrambot/usersmanage/migrations/__init__.py rename to deprecated/inline/__init__.py diff --git a/src/tgbot/keyboards/inline/admin_inline.py b/deprecated/inline/admin_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/admin_inline.py rename to deprecated/inline/admin_inline.py diff --git a/src/tgbot/keyboards/inline/back_inline.py b/deprecated/inline/back_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/back_inline.py rename to deprecated/inline/back_inline.py diff --git a/src/tgbot/keyboards/inline/calendar.py b/deprecated/inline/calendar.py similarity index 100% rename from src/tgbot/keyboards/inline/calendar.py rename to deprecated/inline/calendar.py diff --git a/src/tgbot/keyboards/inline/cancel_inline.py b/deprecated/inline/cancel_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/cancel_inline.py rename to deprecated/inline/cancel_inline.py diff --git a/src/tgbot/keyboards/inline/change_data_profile_inline.py b/deprecated/inline/change_data_profile_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/change_data_profile_inline.py rename to deprecated/inline/change_data_profile_inline.py diff --git a/src/tgbot/keyboards/inline/filters_inline.py b/deprecated/inline/filters_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/filters_inline.py rename to deprecated/inline/filters_inline.py diff --git a/src/tgbot/keyboards/inline/guide_inline.py b/deprecated/inline/guide_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/guide_inline.py rename to deprecated/inline/guide_inline.py diff --git a/src/tgbot/keyboards/inline/language_inline.py b/deprecated/inline/language_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/language_inline.py rename to deprecated/inline/language_inline.py diff --git a/src/tgbot/keyboards/inline/main_menu_inline.py b/deprecated/inline/main_menu_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/main_menu_inline.py rename to deprecated/inline/main_menu_inline.py diff --git a/src/tgbot/keyboards/inline/menu_profile_inline.py b/deprecated/inline/menu_profile_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/menu_profile_inline.py rename to deprecated/inline/menu_profile_inline.py diff --git a/src/tgbot/keyboards/inline/necessary_links_inline.py b/deprecated/inline/necessary_links_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/necessary_links_inline.py rename to deprecated/inline/necessary_links_inline.py diff --git a/src/tgbot/keyboards/inline/payments_inline.py b/deprecated/inline/payments_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/payments_inline.py rename to deprecated/inline/payments_inline.py diff --git a/src/tgbot/keyboards/inline/poster_inline.py b/deprecated/inline/poster_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/poster_inline.py rename to deprecated/inline/poster_inline.py diff --git a/src/tgbot/keyboards/inline/questionnaires_inline.py b/deprecated/inline/questionnaires_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/questionnaires_inline.py rename to deprecated/inline/questionnaires_inline.py diff --git a/src/tgbot/keyboards/inline/registration_inline.py b/deprecated/inline/registration_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/registration_inline.py rename to deprecated/inline/registration_inline.py diff --git a/src/tgbot/keyboards/inline/settings_menu.py b/deprecated/inline/settings_menu.py similarity index 100% rename from src/tgbot/keyboards/inline/settings_menu.py rename to deprecated/inline/settings_menu.py diff --git a/src/tgbot/keyboards/inline/support_inline.py b/deprecated/inline/support_inline.py similarity index 100% rename from src/tgbot/keyboards/inline/support_inline.py rename to deprecated/inline/support_inline.py diff --git a/src/tgbot/services/app/language_ware.py b/deprecated/language_ware.py similarity index 79% rename from src/tgbot/services/app/language_ware.py rename to deprecated/language_ware.py index b49fb07..f8d93ee 100644 --- a/src/tgbot/services/app/language_ware.py +++ b/deprecated/language_ware.py @@ -5,20 +5,15 @@ from aiogram import ( types, ) -from aiogram.contrib.middlewares.i18n import ( - I18nMiddleware, -) +from aiogram.utils.i18n import I18nMiddleware -from src.infrastructure.db_api import ( - db_commands, -) from src.tgbot.config import ( LOCALES_DIR, load_config, ) -async def get_lang(user_id) -> str | None: +async def get_lang(user_id, db_commands=None) -> str | None: user = await db_commands.select_user(telegram_id=user_id) return user.language if user else None diff --git a/deprecated/message_operations.py b/deprecated/message_operations.py new file mode 100644 index 0000000..7eda773 --- /dev/null +++ b/deprecated/message_operations.py @@ -0,0 +1,263 @@ +# from contextlib import ( +# suppress, +# ) +# import datetime +# import os +# import random +# import re +# +# from aiogram import ( +# types, +# ) +# +# from aiogram.types import ( +# CallbackQuery, +# InlineKeyboardMarkup, +# Message, +# ) +# +# from asyncpg import ( +# UniqueViolationError, +# ) +# +# +# from src.tgbot.keyboards.inline.filters_inline import ( +# dating_filters_keyboard, +# ) +# from src.tgbot.keyboards.inline.guide_inline import ( +# create_pagination_keyboard, +# ) +# from src.tgbot.keyboards.inline.main_menu_inline import ( +# start_keyboard, +# ) +# from src.tgbot.keyboards.inline.settings_menu import ( +# information_keyboard, +# ) +# from deprecated.app_scheduler import ( +# send_message_week, +# ) +# +# +# # async def delete_message(message: Message) -> None: +# # with suppress(MessageCantBeDeleted, MessageToDeleteNotFound): +# # await message.delete() +# +# +# async def choice_gender(call: CallbackQuery) -> None: +# """Function that saves to the database the gender that the user has selected.""" +# sex_mapping = {"male": "Мужской", "female": "Женский"} +# +# selected_sex = sex_mapping.get(call.data) +# +# # if selected_sex: +# # try: +# # await db_commands.update_user_data( +# # telegram_id=call.from_user.id, need_partner_sex=selected_sex +# # ) +# # except UniqueViolationError: +# # pass +# +# +# async def display_profile(call: CallbackQuery, markup: InlineKeyboardMarkup) -> None: +# """Function for displaying the user profile.""" +# user = await db_commands.select_user(telegram_id=call.from_user.id) +# count_referrals = await db_commands.count_all_users_kwarg( +# referrer_id=call.from_user.id +# ) +# user_verification = "✅" if user.verification else "" +# +# user_info_template = _( +# "{name}, {age} лет, {city}, {verification}\n\n{commentary}\n\n" +# "Партнерка:\nКоличество приглашенных друзей: {reff}\nРеферальная ссылка:\n {link}" +# ) +# info = await bot.get_me() +# user_info = user_info_template.format( +# name=user.varname, +# age=user.age, +# city=user.city, +# verification=user_verification, +# commentary=user.commentary, +# reff=count_referrals, +# link=f"https://t.me/{info.username}?start={call.from_user.id}", +# ) +# +# await call.message.answer_photo( +# caption=user_info, photo=user.photo_id, reply_markup=markup +# ) +# +# +# async def show_dating_filters(obj: CallbackQuery | Message) -> None: +# user_id = obj.from_user.id +# user = await db_commands.select_user(telegram_id=user_id) +# markup = await dating_filters_keyboard() +# +# text = _( +# "Фильтр по подбору партнеров:\n\n" +# "🚻 Необходимы пол партнера: {}\n" +# "🔞 Возрастной диапазон: {}-{} лет\n\n" +# "🏙️ Город партнера: {}" +# ).format( +# user.need_partner_sex, +# user.need_partner_age_min, +# user.need_partner_age_max, +# user.need_city, +# ) +# try: +# await obj.message.edit_text(text, reply_markup=markup) +# except AttributeError: +# await obj.answer(text, reply_markup=markup) +# +# +# async def registration_menu( +# obj: CallbackQuery | Message, +# ) -> None: +# support = await db_commands.select_user( +# telegram_id=load_config().tg_bot.support_ids[0] +# ) +# markup = await start_keyboard(obj) +# heart = random.choice(["💙", "💚", "💛", "🧡", "💜", "🖤", "❤", "🤍", "💖", "💝"]) +# text = _( +# "Приветствую вас, {fullname}!!\n\n" +# "{heart} Querendo - платформа для поиска новых знакомств.\n\n" +# "🪧 Новости о проекте вы можете прочитать в нашем канале - " +# "https://t.me/que_group \n\n" +# "🤝 Сотрудничество: \n" +# "Если у вас есть предложение о сотрудничестве, пишите агенту поддержки - " +# "@{supports}\n\n" +# ).format(fullname=obj.from_user.full_name, heart=heart, supports=support.username) +# try: +# await obj.message.edit_text(text=text, reply_markup=markup) +# scheduler.add_job( +# send_message_week, +# trigger="interval", +# weeks=1, +# jitter=120, +# args={obj.message}, +# ) +# except AttributeError: +# await obj.answer(text=text, reply_markup=markup) +# scheduler.add_job( +# send_message_week, trigger="interval", weeks=1, jitter=120, args={obj} +# ) +# except BadRequest: +# await delete_message(obj.message) +# +# await obj.message.answer(text=text, reply_markup=markup) +# +# +# async def check_user_in_db(telegram_id: int, message: Message, username: str) -> None: +# if not await db_commands.check_user_exists( +# telegram_id +# ) and not await check_user_meetings_exists(telegram_id): +# user = await db_commands.select_user_object(telegram_id=telegram_id) +# referrer_id = message.text[7:] +# if referrer_id != "" and referrer_id != telegram_id: +# await db_commands.add_user( +# name=message.from_user.full_name, +# telegram_id=telegram_id, +# username=username, +# referrer_id=referrer_id, +# ) +# await db_commands.update_user_data( +# telegram_id=telegram_id, limit_of_views=user.limit_of_views + 15 +# ) +# await bot.send_message( +# chat_id=referrer_id, +# text=_( +# "По вашей ссылке зарегистрировался пользователь {}!\n" +# "Вы получаете дополнительных 15 ❤️" +# ).format(message.from_user.username), +# ) +# else: +# await db_commands.add_user( +# name=message.from_user.full_name, +# telegram_id=telegram_id, +# username=username, +# ) +# await db_commands.add_meetings_user(telegram_id=telegram_id, username=username) +# if telegram_id in load_config().tg_bot.admin_ids: +# await db_commands.add_user_to_settings(telegram_id=telegram_id) +# +# +# async def finished_registration( +# state: FSMContext, telegram_id: int, message: Message +# ) -> None: +# await state.finish() +# await db_commands.update_user_data(telegram_id=telegram_id, status=True) +# +# user = await db_commands.select_user(telegram_id=telegram_id) +# +# markup = await start_keyboard(obj=message) +# +# text = _( +# "Регистрация успешно завершена! \n\n " +# "{}, " +# "{} лет, " +# "{}\n\n" +# "О себе - {}" +# ).format(user.varname, user.age, user.city, user.commentary) +# +# await message.answer_photo(caption=text, photo=user.photo_id, reply_markup=markup) +# +# +# async def send_photo_with_caption( +# call: CallbackQuery, +# photo: str, +# caption: str, +# step: int, +# total_steps: int, +# ) -> None: +# markup = await create_pagination_keyboard(step, total_steps) +# +# await call.message.delete() +# await call.message.answer_photo( +# types.InputFile(photo), reply_markup=markup, caption=caption +# ) +# +# +# async def handle_guide_callback( +# call: CallbackQuery, +# callback_data: dict, +# ) -> None: +# step = int(callback_data.get("value")) +# +# photo_path = f"brandbook/{step}_page.png" +# caption = _("Руководство по боту: \nСтраница №{}").format(step) +# await send_photo_with_caption( +# call=call, +# photo=photo_path, +# caption=caption, +# step=step, +# total_steps=len(os.listdir("brandbook/")), +# ) +# +# +# async def information_menu(call: CallbackQuery) -> None: +# start_date = datetime.datetime(2021, 8, 10, 14, 0) +# now_date = datetime.datetime.now() +# delta = now_date - start_date +# count_users = await db_commands.count_users() +# markup = await information_keyboard() +# txt = _( +# "Вы попали в раздел Информации бота, здесь вы можете посмотреть: статистику," +# "изменить язык, а также посмотреть наш брендбук.\n\n" +# "🌐 Дней работаем: {}\n" +# "👤 Всего пользователей: {}\n" +# ).format(delta.days, count_users) +# try: +# await call.message.edit_text(text=txt, reply_markup=markup) +# except BadRequest: +# await delete_message(call.message) +# await call.message.answer(text=txt, reply_markup=markup) +# +# +# async def get_report_reason(call: CallbackQuery) -> str: +# match = re.search(r"report:(.*?):", call.data) +# reason_key = match.group(1) +# reason_mapping = { +# "adults_only": "🔞 Развратный контент", +# "drugs": "💊 Продажа наркотиков", +# "scam": "💰 Мошенничество", +# "another": "🦨 Другая причина", +# } +# return reason_mapping.get(reason_key, "Неизвестная причина") diff --git a/deprecated/notify_admins.py b/deprecated/notify_admins.py new file mode 100644 index 0000000..84323b7 --- /dev/null +++ b/deprecated/notify_admins.py @@ -0,0 +1,60 @@ +# from abc import ( +# ABC, +# abstractmethod, +# ) +# +# import aiogram +# from aiogram import ( +# Dispatcher, +# ) +# import aiogram.utils.exceptions +# from aiogram.utils.exceptions import ( +# ChatNotFound, +# ) +# +# from loader import ( +# _, +# bot, +# logger, +# ) +# from src.tgbot.config import ( +# load_config, +# ) +# +# +# class BaseNotification(ABC): +# @abstractmethod +# def send(self, *args): +# pass +# +# +# class AdminNotification(BaseNotification): +# def __init__(self, dp: Dispatcher): +# self.dp = dp +# +# async def send(self) -> None: +# logger.info(_("Оповещение администрации...")) +# for admin in load_config().tg_bot.admin_ids: +# try: +# await bot.send_message( +# admin, _("Бот был успешно запущен"), disable_notification=True +# ) +# except ChatNotFound: +# logger.debug("Чат с админом не найден") +# +# +# class ErrorNotification(BaseNotification): +# def __init__(self, error_message: Exception): +# self.__error_message = error_message +# +# async def send(self) -> None: +# text = ( +# f"❗ Error During Operation ❗\n" +# f"{self.__error_message}\n\n❗" +# f" The bot will restart automatically." +# ) +# for user_id in load_config().tg_bot.admin_ids: +# try: +# await bot.send_message(user_id, text) +# except (aiogram.exceptions.BotBlocked, aiogram.exceptions.ChatNotFound): +# continue diff --git a/deprecated/photo_operations.py b/deprecated/photo_operations.py new file mode 100644 index 0000000..6c9dd68 --- /dev/null +++ b/deprecated/photo_operations.py @@ -0,0 +1,103 @@ +# import asyncio +# import pathlib +# +# from aiogram.fsm.context import FSMContext +# from aiogram.types import ( +# InlineKeyboardMarkup, +# InputFile, +# Message, +# ReplyKeyboardRemove, +# ) +# +# +# async def saving_normal_photo( +# message: Message, telegram_id: int, file_id: str, state: FSMContext +# ) -> None: +# """Функция, сохраняющая фотографию пользователя без цензуры.""" +# try: +# await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) +# +# await message.answer( +# text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() +# ) +# except Exception as err: +# logger.info(f"Ошибка в saving_normal_photo | err: {err}") +# await message.answer( +# text=_( +# "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" +# "Если ошибка осталась, напишите агенту поддержки." +# ) +# ) +# await finished_registration(state=state, telegram_id=telegram_id, message=message) +# +# +# async def saving_censored_photo( +# message: Message, +# telegram_id: int, +# state: FSMContext, +# out_path: str | pathlib.Path, +# flag: str | None = "registration", +# markup: InlineKeyboardMarkup | None = None, +# ) -> None: +# """.Функция, сохраняющая фотографию пользователя с цензурой.""" +# photo = InputFile(out_path) +# id_photo = await bot.send_photo( +# chat_id=telegram_id, +# photo=photo, +# caption=_( +# "Во время проверки вашего фото мы обнаружили подозрительный контент!\n" +# "Поэтому мы чуть-чуть подкорректировали вашу фотографию" +# ), +# ) +# file_id = id_photo["photo"][0]["file_id"] +# await asyncio.sleep(1) +# try: +# await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) +# +# except Exception as err: +# logger.info(f"Ошибка в saving_censored_photo | err: {err}") +# await message.answer( +# text=_( +# "Произошла ошибка!" +# " Попробуйте еще раз либо отправьте другую фотографию. \n" +# "Если ошибка осталась, напишите агенту поддержки." +# ) +# ) +# if flag == "change_datas": +# await message.answer( +# text=_("Фото принято!\n" "Выберите, что вы хотите изменить: "), +# reply_markup=markup, +# ) +# await state.reset_state() +# elif flag == "registration": +# await finished_registration( +# state=state, telegram_id=telegram_id, message=message +# ) +# +# +# async def update_normal_photo( +# message: Message, +# telegram_id: int, +# file_id: str, +# state: FSMContext, +# markup +# ) -> None: +# """Функция, которая обновляет фотографию пользователя.""" +# try: +# await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) +# await message.answer( +# text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() +# ) +# await asyncio.sleep(3) +# await message.answer( +# text=_("Выберите, что вы хотите изменить: "), reply_markup=markup +# ) +# await state.reset_state() +# except Exception as err: +# logger.info(f"Ошибка в update_normal_photo | err: {err}") +# await message.answer( +# text=_( +# "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" +# "Если ошибка осталась, напишите агенту поддержки." +# ) +# ) diff --git a/deprecated/reaction_strategies.py b/deprecated/reaction_strategies.py new file mode 100644 index 0000000..dc56f56 --- /dev/null +++ b/deprecated/reaction_strategies.py @@ -0,0 +1,252 @@ +# from abc import ( +# ABC, +# abstractmethod, +# ) +# import asyncio +# import random +# import secrets +# +# from aiogram.dispatcher import ( +# FSMContext, +# ) +# from aiogram.types import ( +# CallbackQuery, +# ) +# +# from loader import ( +# _, +# bot, +# ) +# from src.tgbot.config import ( +# load_config, +# ) +# from src.infrastructure.db_api import ( +# db_commands, +# ) +# from src.tgbot.keyboards.inline.main_menu_inline import ( +# start_keyboard, +# ) +# from src.tgbot.keyboards.inline.questionnaires_inline import ( +# report_menu_keyboard, +# user_link_keyboard, +# ) +# from deprecated.message_operations import ( +# get_report_reason, +# ) +# from deprecated.create_forms_funcs import ( +# create_questionnaire, +# create_questionnaire_reciprocity, +# rand_user_list, +# ) +# from deprecated.get_next_user_func import ( +# get_next_user, +# ) +# +# +# class ActionStrategy(ABC): +# @abstractmethod +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# pass +# +# +# class StartFindingSuccess(ActionStrategy): +# async def execute(self, call: CallbackQuery, state: FSMContext, **kwargs): +# await call.message.delete() +# telegram_id = call.from_user.id +# user_list = await get_next_user(telegram_id) +# random_user = random.choice(user_list) +# await create_questionnaire(form_owner=random_user, chat_id=telegram_id) +# await state.set_state("finding") +# +# +# class StartFindingFailure(ActionStrategy): +# async def execute(self, call: CallbackQuery, state: FSMContext, **kwargs): +# await call.answer(_("На данный момент у нас нет подходящих анкет для вас")) +# +# +# class StartFindingReachLimit(ActionStrategy): +# async def execute(self, call: CallbackQuery, state: FSMContext, **kwargs): +# await call.answer( +# text=_("У вас достигнут лимит на просмотры анкет"), show_alert=True +# ) +# +# +# class LikeAction(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# user = await db_commands.select_user_object(telegram_id=call.from_user.id) +# text = _("Кому-то понравилась твоя анкета") +# target_id = int(callback_data["target_id"]) +# +# await create_questionnaire( +# form_owner=call.from_user.id, chat_id=target_id, add_text=text +# ) +# +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=None, +# ) +# +# await db_commands.update_user_data( +# telegram_id=call.from_user.id, limit_of_views=user.limit_of_views - 1 +# ) +# await create_questionnaire( +# form_owner=(await rand_user_list(call)), chat_id=call.from_user.id +# ) +# +# await state.reset_data() +# +# +# class DislikeAction(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=None, +# ) +# await create_questionnaire( +# form_owner=(await rand_user_list(call)), chat_id=call.from_user.id +# ) +# await state.reset_data() +# +# +# class StoppedAction(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# text = _( +# "Рад был помочь, {fullname}!\nНадеюсь, ты нашел кого-то благодаря мне" +# ).format(fullname=call.from_user.full_name) +# await call.answer(text, show_alert=True) +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=await start_keyboard(call), +# ) +# await state.reset_state() +# +# +# class LikeReciprocity(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# user_for_like = int(callback_data["user_for_like"]) +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=None, +# ) +# await call.message.answer( +# text=_("Отлично! Надеюсь вы хорошо проведете время ;) Начинай общаться 👉"), +# reply_markup=await user_link_keyboard(telegram_id=user_for_like), +# ) +# await create_questionnaire_reciprocity( +# liker=call.from_user.id, chat_id=user_for_like, add_text="" +# ) +# await bot.send_message( +# chat_id=user_for_like, +# text="Есть взаимная симпатия! Начиная общаться 👉", +# reply_markup=await user_link_keyboard(telegram_id=call.from_user.id), +# ) +# await state.reset_state() +# +# +# class DislikeReciprocity(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=await start_keyboard(call), +# ) +# await state.reset_state() +# +# +# class GoBackToViewing(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=None, +# ) +# +# user_list = await get_next_user(call.from_user.id) +# random_user = secrets.choice(user_list) +# await state.set_state("finding") +# try: +# await create_questionnaire( +# form_owner=random_user, chat_id=call.from_user.id +# ) +# await state.reset_data() +# except IndexError: +# await call.answer(_("На данный момент у нас нет подходящих анкет для вас")) +# await state.reset_data() +# +# +# class ChooseReportReason(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# await state.reset_state() +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=None, +# ) +# target_id = int(callback_data["target_id"]) +# await call.message.answer( +# text=_("Выберите причину жалобы:"), +# reply_markup=await report_menu_keyboard(telegram_id=target_id), +# ) +# +# +# class SendReport(ActionStrategy): +# async def execute( +# self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] +# ): +# target_id = int(callback_data["target_id"]) +# target_user = await db_commands.select_user(telegram_id=target_id) +# +# counter_of_report = target_user.counter_of_report +# username = call.from_user.username +# user_id = call.from_user.id +# report_reason = await get_report_reason(call) +# +# text = _( +# "Жалоба от пользователя: [@{username} | {tg_id}]\n\n" +# "На пользователя: [{owner_id}]\n" +# "Причина жалобы: {reason}\n" +# "Количество жалоб на пользователя: {counter_of_report}" +# ).format( +# username=username, +# tg_id=user_id, +# owner_id=target_id, +# reason=report_reason, +# counter_of_report=counter_of_report, +# ) +# +# await db_commands.update_user_data( +# telegram_id=target_id, counter_of_report=counter_of_report + 1 +# ) +# +# moderate_chat = load_config().tg_bot.moderate_chat +# if counter_of_report >= 5 and not target_user.on_check_by_admin: +# await db_commands.update_user_data( +# telegram_id=target_id, on_check_by_admin=True +# ) +# await create_questionnaire( +# form_owner=target_id, +# chat_id=moderate_chat, +# report_system=True, +# add_text=text, +# ) +# await asyncio.sleep(0.5) diff --git a/deprecated/send_form_func.py b/deprecated/send_form_func.py new file mode 100644 index 0000000..71832f9 --- /dev/null +++ b/deprecated/send_form_func.py @@ -0,0 +1,134 @@ +# from typing import ( +# Optional, +# ) +# +# from aiogram.types import ( +# InlineKeyboardMarkup, +# ) +# from aiogram.utils.exceptions import ( +# BadRequest, +# ) +# +# from loader import ( +# _, +# bot, +# logger, +# ) +# from src.infrastructure.db_api import ( +# db_commands, +# ) +# from src.tgbot.keyboards.admin.inline.customers import ( +# user_blocking_keyboard, +# ) +# from src.tgbot.keyboards.inline.questionnaires_inline import ( +# questionnaires_keyboard, +# reciprocity_keyboard, +# ) +# +# +# async def send_questionnaire( +# chat_id: int, +# owner_id: int | None = None, +# markup: InlineKeyboardMarkup | None = None, +# add_text: str | None = None, +# monitoring: bool = False, +# report_system: bool = False, +# ) -> None: +# user = await db_commands.select_user(owner_id) +# text_template = _("{}, {} лет, {} {verification}\n\n") +# user_verification = "✅" if user.verification else "" +# +# text_without_inst = _(text_template + "{commentary}").format( +# user.varname, +# user.age, +# user.city, +# commentary=user.commentary, +# verification=user_verification, +# ) +# +# text_with_inst_template = text_template + _( +# "Инстаграм - {instagram}\n" +# ) +# text_with_inst = _(text_with_inst_template).format( +# user.varname, +# user.age, +# user.city, +# user.commentary, +# verification=user_verification, +# instagram=user.instagram, +# ) +# +# caption_with_add_text = _("{}\n\n" + text_template + "{}").format( +# add_text, +# user.varname, +# user.age, +# user.city, +# user.commentary, +# verification=user_verification, +# ) +# +# add_text_with_inst = _( +# "{}\n\n" + text_template + "Инстаграм - {instagram}\n" +# ).format( +# add_text, +# user.varname, +# user.age, +# user.city, +# user.commentary, +# verification=user_verification, +# instagram=user.instagram, +# ) +# try: +# if add_text is None and user.instagram is None: +# await bot.send_photo( +# chat_id=chat_id, +# caption=text_without_inst, +# photo=user.photo_id, +# reply_markup=await questionnaires_keyboard( +# target_id=owner_id, monitoring=monitoring +# ), +# ) +# elif add_text is None: +# await bot.send_photo( +# chat_id=chat_id, +# caption=text_with_inst, +# photo=user.photo_id, +# reply_markup=await questionnaires_keyboard( +# target_id=owner_id, monitoring=monitoring +# ), +# ) +# elif markup is None and user.instagram is None: +# await bot.send_photo( +# chat_id=chat_id, +# caption=caption_with_add_text, +# photo=user.photo_id, +# ) +# elif markup is None: +# await bot.send_photo( +# chat_id=chat_id, caption=add_text_with_inst, photo=user.photo_id +# ) +# elif user.instagram is None and not report_system: +# await bot.send_photo( +# chat_id=chat_id, +# caption=caption_with_add_text, +# photo=user.photo_id, +# reply_markup=await reciprocity_keyboard(user_for_like=owner_id), +# ) +# elif report_system: +# await bot.send_photo( +# chat_id=chat_id, +# caption=add_text, +# photo=user.photo_id, +# reply_markup=await user_blocking_keyboard( +# user_id=owner_id, is_banned=user.is_banned +# ), +# ) +# else: +# await bot.send_photo( +# chat_id=chat_id, +# caption=add_text_with_inst, +# photo=user.photo_id, +# reply_markup=await reciprocity_keyboard(user_for_like=owner_id), +# ) +# except BadRequest as err: +# logger.info(f"{err}. Error in the send_questionnaire function") diff --git a/src/tgbot/services/app/statistics.py b/deprecated/statistics.py similarity index 92% rename from src/tgbot/services/app/statistics.py rename to deprecated/statistics.py index d6dadc8..3ce6c32 100644 --- a/src/tgbot/services/app/statistics.py +++ b/deprecated/statistics.py @@ -2,15 +2,8 @@ Message, ) -from loader import ( - _, -) -from src.infrastructure.db_api import ( - db_commands, -) - -async def get_statistics(message: Message): +async def get_statistics(message: Message, db_commands=None, _=None): user = await db_commands.select_user(telegram_id=message.from_user.id) user_city = user.city users_gender_m = await db_commands.count_all_users_kwarg(sex="Мужской") diff --git a/src/tgbot/services/event/templates_messages.py b/deprecated/templates_messages.py similarity index 92% rename from src/tgbot/services/event/templates_messages.py rename to deprecated/templates_messages.py index 47b9c04..060f0ca 100644 --- a/src/tgbot/services/event/templates_messages.py +++ b/deprecated/templates_messages.py @@ -1,8 +1,3 @@ -from typing import ( - Optional, - Union, -) - from aiogram import ( Bot, ) @@ -10,9 +5,6 @@ CallbackQuery, ) -from loader import ( - _, -) from src.tgbot.keyboards.inline.poster_inline import ( cancel_event_keyboard, create_moderate_ik, @@ -23,8 +15,8 @@ class TemplateEvent: def __init__(self) -> None: - self.message_for_event = _( - "{} \n" + "Когда: {} \n" + "Где: {} \n\n" + "{}" + self.message_for_event = ( + "{} \n" + "Когда: {} \n" + "Где: {} \n\n" + "{}" ) def template_event(self) -> str: From bbc2a029fee4da68c67bea859e2014ce51648ef7 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:36:23 +0300 Subject: [PATCH 013/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20loader.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- loader.py | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 loader.py diff --git a/loader.py b/loader.py deleted file mode 100644 index a48e67f..0000000 --- a/loader.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging -from typing import ( - Any, -) - -from aiogram import ( - Bot, - Dispatcher, - types, -) -from aiogram.contrib.fsm_storage.memory import ( - MemoryStorage, -) -from aiogram.contrib.fsm_storage.redis import ( - RedisStorage2, -) -from apscheduler.schedulers.asyncio import ( - AsyncIOScheduler, -) -from nudenet import ( - NudeDetector, -) - -from src.tgbot.config import load_config -from src.infrastructure.YandexMap.api import ( - Client, -) -from src.infrastructure.yoomoney import ( - YooMoneyWallet, -) -from src.tgbot.services.app.language_ware import setup_middleware - -bot = Bot(token=load_config().tg_bot.token, parse_mode=types.ParseMode.HTML) -storage = RedisStorage2() if load_config().tg_bot.use_redis else MemoryStorage() -dp = Dispatcher(bot, storage=storage) -client = Client(api_key=load_config().misc.yandex_api_key) -job_defaults = dict(coalesce=False, max_instances=3) -scheduler = AsyncIOScheduler( - timezone=load_config().tg_bot.timezone, job_defaults=job_defaults -) -wallet = YooMoneyWallet(access_token=load_config().misc.yoomoney_key) -detector = NudeDetector() - -i18n = setup_middleware(dp) -_: Any = i18n.gettext - -logger = logging.getLogger(__name__) From 178534df66d64f1b113b4ec95dfaadc8ccfdac48 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:37:20 +0300 Subject: [PATCH 014/148] =?UTF-8?q?=F0=9F=93=9D=20Update=20"About"=20secti?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index ee1064c..7b8c81a 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,7 @@ ### :monocle_face: About -| 🏗️ The project has moved elsewhere, and currently available at [que.group](https://github.com/QueGroup) | -|----------------------------------------------------------------------------------------------------------| - -Open-source Dating Telegram bot built on aiogram 2.x to facilitate the search for new connections. The bot incorporates +Open-source Dating Telegram bot built on aiogram 3.x to facilitate the search for new connections. The bot incorporates a classic profile browsing system, along with filtering options and event organization capabilities. Users can create and join events, fostering community engagement. Additionally, the project is closely integrated with neural networks, enhancing its capabilities. From 70e5a3488348b435626b310c7fc4593079c080b0 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:38:42 +0300 Subject: [PATCH 015/148] =?UTF-8?q?=F0=9F=9A=A7=E2=AC=86=EF=B8=8F=20Workin?= =?UTF-8?q?g=20on=20dependencies=20in=20the=20process?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 140 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 22 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9872150..70b05bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,123 @@ -aiogram==2.25.1 -environs==10.2.0 +aiofiles==23.2.1 +aiogram==3.4.1 +aiohttp==3.9.3 +aiosignal==1.3.1 +annotated-types==0.6.0 +APScheduler==3.10.4 +asgiref==3.7.2 +async-lru==2.0.4 +async-timeout==4.0.3 asyncpg==0.29.0 -Django==4.2 +attrs==23.2.0 +Babel==2.9.1 +better-profanity==0.7.0 +betterlogging==0.2.1 +black==23.12.1 +boolean.py==4.0 +build==1.0.3 +CacheControl==0.14.0 +certifi==2024.2.2 +cfgv==3.4.0 +chardet==5.2.0 +charset-normalizer==3.3.2 +cleo==2.1.0 +click==8.1.7 +colorama==0.4.6 +coloredlogs==15.0.1 +crashtest==0.4.1 +cyclonedx-python-lib==6.4.1 +defusedxml==0.7.1 +deptry==0.12.0 +distlib==0.3.8 +dulwich==0.21.7 +environs==10.2.0 +fastjsonschema==2.19.1 +filelock==3.13.1 +flake8==7.0.0 +flatbuffers==23.5.26 +frozenlist==1.4.1 +html5lib==1.1 +humanfriendly==10.0 +identify==2.5.33 +idna==3.6 +importlib-metadata==7.0.1 +iniconfig==2.0.0 +installer==0.7.0 +isort==5.13.2 +jaraco.classes==3.3.1 +jsonfield==3.1.0 +keyring==24.3.0 +license-expression==30.2.0 +magic-filter==1.0.12 +markdown-it-py==3.0.0 +marshmallow==3.20.2 +mccabe==0.7.0 +mdurl==0.1.2 +more-itertools==10.2.0 +mpmath==1.3.0 +msgpack==1.0.7 +multidict==6.0.5 +mypy==1.8.0 +mypy-extensions==1.0.0 +nodeenv==1.8.0 +nudenet==3.0.8 +numpy==1.26.3 +onnxruntime==1.17.0 +opencv-python-headless==4.9.0.80 +packageurl-python==0.13.4 +packaging==23.2 +pathspec==0.12.1 +pbr==6.0.0 +pexpect==4.9.0 +pip-api==0.0.30 +pip-requirements-parser==32.0.1 +pip_audit==2.7.0 +pkginfo==1.9.6 +platformdirs==4.2.0 +pluggy==1.4.0 +poetry==1.7.1 +poetry-core==1.8.1 +poetry-plugin-export==1.6.0 +pre-commit==3.6.0 +protobuf==4.25.2 psycopg2-binary==2.9.9 -jsonfield~=3.1.0 -asgiref==3.7.2 -uuid~=1.30 -pip==23.3.2 -setuptools==69.0.3 -django-jazzmin~=2.6.0 -better_profanity~=0.7.0 +ptyprocess==0.7.0 +py-serializable==1.0.0 +pycodestyle==2.11.1 +pydantic==2.5.3 +pydantic_core==2.14.6 +pyflakes==3.2.0 +Pygments==2.17.2 +pyparsing==3.1.1 +pyproject_hooks==1.0.0 +pyreadline3==3.4.1 pytest==7.4.4 -aiohttp<3.9.0 -async-lru==2.0.4 pytest-asyncio==0.23.3 -APScheduler==3.10.4 -NudeNet==3.0.8 -yarl==1.9.4 -aiofiles==23.2.1 -pre-commit==3.6.0 -testresources -pydantic==1.10.13 +python-dotenv==1.0.1 +pytz==2024.1 +pywin32-ctypes==0.2.2 +PyYAML==6.0.1 +rapidfuzz==3.6.1 redis==5.0.1 -betterlogging~=0.2.1 -flake8==7.0.0 -black==23.12.1 \ No newline at end of file +requests==2.31.0 +requests-toolbelt==1.0.0 +rich==13.7.0 +ruff==0.1.15 +shellingham==1.5.4 +six==1.16.0 +sortedcontainers==2.4.0 +sqlparse==0.4.4 +sympy==1.12 +testresources==2.0.1 +toml==0.10.2 +tomlkit==0.12.3 +trove-classifiers==2024.1.31 +typing_extensions==4.9.0 +tzdata==2023.4 +tzlocal==5.2 +urllib3==2.2.0 +uuid==1.30 +virtualenv==20.25.0 +webencodings==0.5.1 +yarl==1.9.4 +zipp==3.17.0 From 8cf837fb91ae8984e89eea486d6dc935c09d2c1c Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:40:09 +0300 Subject: [PATCH 016/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20deprecated=20te?= =?UTF-8?q?sts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/conftest.py | 0 test/test_api/__init__.py | 0 test/test_api/test_location.py | 66 ------------- test/test_functions/__init__.py | 0 test/test_functions/test_event_functions.py | 103 -------------------- test/test_handlers/__init__.py | 0 test/test_handlers/test_echo.py | 20 ---- test/test_handlers/test_group_start.py | 22 ----- 8 files changed, 211 deletions(-) delete mode 100644 test/conftest.py delete mode 100644 test/test_api/__init__.py delete mode 100644 test/test_api/test_location.py delete mode 100644 test/test_functions/__init__.py delete mode 100644 test/test_functions/test_event_functions.py delete mode 100644 test/test_handlers/__init__.py delete mode 100644 test/test_handlers/test_echo.py delete mode 100644 test/test_handlers/test_group_start.py diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_api/__init__.py b/test/test_api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_api/test_location.py b/test/test_api/test_location.py deleted file mode 100644 index ca87432..0000000 --- a/test/test_api/test_location.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import ( - NoReturn, - Tuple, -) - -import aiohttp -import pytest - -from loader import ( - client, -) -from src.infrastructure.YandexMap.api import Client -from src.infrastructure.YandexMap.exceptions import UnexpectedResponse, InvalidKey, NothingFound -from src.tgbot.config import load_config - - -@pytest.mark.asyncio -async def test_request_success() -> NoReturn: - async with aiohttp.ClientSession() as session: - async with session.get( - url="https://geocode-maps.yandex.ru/1.x/", - params=dict( - format="json", - apikey=load_config().misc.yandex_api_key, - geocode="Detroit", - ), - ) as response: - assert response.status == 200 - data = await response.json() - assert isinstance(data, dict) - assert "response" in data - - -@pytest.mark.asyncio -async def test_request_invalid_key() -> None: - with pytest.raises(InvalidKey): - cl = Client(api_key="invalid_key") - await cl._request("Detroit") - - -@pytest.mark.asyncio -async def test_request_unexpected_response() -> None: - with pytest.raises(UnexpectedResponse): - cl = Client(api_key="valid_key") - await cl._request("InvalidAddress") - - -@pytest.mark.asyncio -async def test_coordinates_success() -> NoReturn: - coordinates = await client.coordinates("Detroit") - assert isinstance(coordinates, Tuple) - assert len(coordinates) == 2 - assert all(isinstance(coord, str) for coord in coordinates) - - -@pytest.mark.asyncio -async def test_address_success() -> NoReturn: - coordinates = await client.coordinates("Detroit") - address = await client.address(*coordinates) - assert isinstance(address, str) - - -@pytest.mark.asyncio -async def test_address_nothing_found() -> None: - with pytest.raises(NothingFound): - await client.address("100.0", "200.0") diff --git a/test/test_functions/__init__.py b/test/test_functions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_functions/test_event_functions.py b/test/test_functions/test_event_functions.py deleted file mode 100644 index 964f340..0000000 --- a/test/test_functions/test_event_functions.py +++ /dev/null @@ -1,103 +0,0 @@ -from datetime import ( - datetime, - timedelta, -) -from typing import ( - NoReturn, -) -from unittest import ( - mock, -) -from unittest.mock import ( - AsyncMock, - Mock, - patch, -) - -import pytest - -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.services.event import ( - extra_features, -) - - -@pytest.mark.asyncio -async def test_check_event_date_past_date() -> None: - telegram_id = 1 - event_time = datetime.now() - timedelta(days=10) - with patch.object( - db_commands, - "select_user_meetings", - new=AsyncMock(return_value={"time_event": event_time.strftime("%d-%m-%Y")}), - ): - update_mock = AsyncMock() - with patch.object( - db_commands, "update_user_meetings_data", new=update_mock - ) as mock: - await extra_features.check_event_date(telegram_id) - mock.assert_called_once_with( - telegram_id=telegram_id, - is_admin=False, - verification_status=False, - is_active=False, - ) - - -@pytest.mark.asyncio -async def test_check_event_date_future_date() -> None: - telegram_id = 1 - event_time = datetime.now() + timedelta(days=10) - with patch.object( - db_commands, - "select_user_meetings", - new=AsyncMock(return_value={"time_event": event_time.strftime("%d-%m-%Y")}), - ): - update_mock = AsyncMock() - with patch.object( - db_commands, "update_user_meetings_data", new=update_mock - ) as mock: - await extra_features.check_event_date(telegram_id) - mock.assert_called_once_with( - telegram_id=telegram_id, - is_admin=True, - verification_status=True, - is_active=True, - ) - - -@pytest.mark.asyncio -async def test_get_next_registration() -> NoReturn: - db_command = Mock() - db_command.select_user.return_value = {"telegram_id": 1, "events": []} - result = await extra_features.get_next_registration(telegram_id=1) - assert result == [] - - -@pytest.mark.asyncio -async def test_get_next_random_event_id() -> NoReturn: - # Mock search_event_forms() для возврата тестовых данных - mock_event_forms = [ - {"telegram_id": 1}, - {"telegram_id": 2}, - {"telegram_id": 3}, - ] - db_commands.search_event_forms = mock.AsyncMock(return_value=mock_event_forms) - - db_commands.check_returned_event_id = mock.AsyncMock(return_value=False) - - event_id = await extra_features.get_next_random_event_id(telegram_id=1) - assert event_id == 2 - - await db_commands.check_returned_event_id(telegram_id=1, id_of_events_seen=2) - - with pytest.raises(ValueError) as exc_info: - await extra_features.get_next_random_event_id(telegram_id=1) - assert str(exc_info.value) == "No upcoming events found" - - db_commands.check_returned_event_id = mock.AsyncMock(return_value=True) - - event_id = await extra_features.get_next_random_event_id(telegram_id=2) - assert event_id == 3 diff --git a/test/test_handlers/__init__.py b/test/test_handlers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_handlers/test_echo.py b/test/test_handlers/test_echo.py deleted file mode 100644 index 777c568..0000000 --- a/test/test_handlers/test_echo.py +++ /dev/null @@ -1,20 +0,0 @@ -from unittest.mock import ( - AsyncMock, -) - -from aiogram.utils.markdown import ( - hcode, -) -import pytest - -from src.tgbot.handlers.echo_handler import ( - bot_echo, -) - - -@pytest.mark.asyncio -async def test_echo_handler() -> None: - text_mock = ["Эхо без состояния.", "Сообщение: ", hcode("repeat me")] - message_mock = AsyncMock(text=text_mock) - await bot_echo(message=message_mock) - message_mock.answer.assert_called_with(text_mock) diff --git a/test/test_handlers/test_group_start.py b/test/test_handlers/test_group_start.py deleted file mode 100644 index a5eec3b..0000000 --- a/test/test_handlers/test_group_start.py +++ /dev/null @@ -1,22 +0,0 @@ -from unittest.mock import ( - AsyncMock, -) - -from src.tgbot.handlers.groups.start import ( - start_group_handler, -) -import pytest - -from loader import ( - _, -) - - -@pytest.mark.asyncio -async def test_start_group_handler() -> None: - text_mock = _( - "Привет, я бот, проекта Que Group, для верификации анкет для знакомств\n\n" - ) - message_mock = AsyncMock(text=text_mock) - await start_group_handler(message=message_mock) - message_mock.answer.assert_called_with(text_mock) From 475610ad4239f9ce3452361bfd96c750348fc99c Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:40:29 +0300 Subject: [PATCH 017/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/de/LC_MESSAGES/dating.po | 1418 ---------------------------- locales/en/LC_MESSAGES/dating.po | 1422 ---------------------------- locales/in/LC_MESSAGES/dating.po | 1408 ---------------------------- locales/ru/LC_MESSAGES/dating.po | 1511 ------------------------------ 4 files changed, 5759 deletions(-) delete mode 100644 locales/de/LC_MESSAGES/dating.po delete mode 100644 locales/en/LC_MESSAGES/dating.po delete mode 100644 locales/in/LC_MESSAGES/dating.po delete mode 100644 locales/ru/LC_MESSAGES/dating.po diff --git a/locales/de/LC_MESSAGES/dating.po b/locales/de/LC_MESSAGES/dating.po deleted file mode 100644 index ef04a6b..0000000 --- a/locales/de/LC_MESSAGES/dating.po +++ /dev/null @@ -1,1418 +0,0 @@ -# German translations for PROJECT. -# Copyright (C) 2022 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2022. -# -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-08-28 14:39+0300\n" -"PO-Revision-Date: 2023-08-24 00:07+0300\n" -"Last-Translator: \n" -"Language: de_ID\n" -"Language-Team: id_ID \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.1\n" - -#: functions/dating/reaction_strategies.py:46 -#: functions/dating/reaction_strategies.py:171 -msgid "На данный момент у нас нет подходящих анкет для вас" -msgstr "Wir haben derzeit keine geeigneten Profile für Sie" - -#: functions/dating/reaction_strategies.py:52 -msgid "У вас достигнут лимит на просмотры анкет" -msgstr "Sie haben Ihr Limit an Profilansichten erreicht" - -#: functions/dating/reaction_strategies.py:61 -msgid "Кому-то понравилась твоя анкета" -msgstr "Ihr Profil hat jemandem gefallen" - -#: functions/dating/reaction_strategies.py:103 handlers/users/view_event.py:51 -msgid "" -"Рад был помочь, {fullname}!\n" -"Надеюсь, ты нашел кого-то благодаря мне" -msgstr "" -"Es war mir eine Freude, {fullname} zu helfen!\n" -"Ich hoffe, Sie haben jemanden dank mir gefunden" - -#: functions/dating/reaction_strategies.py:126 -msgid "Отлично! Надеюсь вы хорошо проведете время ;) Начинай общаться 👉" -msgstr "Toll! Ich hoffe, du hast eine gute Zeit ;) Chatten beginnen 👉" - -#: functions/dating/reaction_strategies.py:187 -msgid "Выберите причину жалобы:" -msgstr "" - -#: functions/dating/reaction_strategies.py:204 -msgid "" -"Жалоба от пользователя: [@{username} | {tg_id}]" -"\n" -"\n" -"На пользователя: [{owner_id}]\n" -"Причина жалобы: {reason}\n" -"Количество жалоб на пользователя: {counter_of_report}" -msgstr "" - -#: functions/dating/send_form_func.py:25 -msgid "" -"{}, {} лет, {} {verification}\n" -"\n" -msgstr "" -"{}, {} Jahre, {} {verification}\n" -"\n" - -#: functions/dating/send_form_func.py:28 -msgid "{commentary}" -msgstr "{commentary}" - -#: functions/dating/send_form_func.py:36 -msgid "Инстаграм - {instagram}\n" -msgstr "instagram - {instagram}\n" - -#: functions/dating/send_form_func.py:48 -msgid "" -"{}\n" -"\n" -"{}" -msgstr "" -"{}\n" -"\n" -"{}" - -#: functions/dating/send_form_func.py:57 -msgid "" -"{}\n" -"\n" -"Инстаграм - {instagram}\n" -msgstr "" -"{}\n" -"\n" -"Instagram - {instagram}\n" - -#: functions/event/extra_features.py:86 -msgid "На данный момент у нас нет подходящих мероприятий для вас" -msgstr "Derzeit haben wir keine passenden Veranstaltungen für Sie" - -#: functions/event/templates_messages.py:17 -msgid "" -"{} \n" -"Когда: {} \n" -"Где: {} \n" -"\n" -"{}" -msgstr "" -"{} \n" -"Wann: {} \n" -"Ort: {} \n" -"\n" -"{}" - -#: functions/main_app/app_scheduler.py:12 -msgid "Несколько {} из города {} хотят познакомиться с тобой прямо сейчас" -msgstr "Mehrere {} aus der Stadt {} möchten Sie jetzt treffen" - -#: functions/main_app/auxiliary_tools.py:71 -msgid "" -"{name}, {age} лет, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Партнерка:\n" -"Количество приглашенных друзей: {reff}\n" -"Реферальная ссылка:\n" -" {link}" -msgstr "" -"{name}, {age} Jahre, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Partner:.\n" -"Anzahl der eingeladenen Freunde: {reff}\n" -"Empfehlungslink:\n" -" {link}" - -#: functions/main_app/auxiliary_tools.py:96 -msgid "" -"Фильтр по подбору партнеров:\n" -"\n" -"🚻 Необходимы пол партнера: {}\n" -"🔞 Возрастной диапазон: {}-{} лет\n" -"\n" -"🏙️ Город партнера: {}" -msgstr "" -"Filter nach Partnerauswahl:\n" -"\n" -"🚻 Geschlecht des Partners erforderlich: {}\n" -"🔞 Altersbereich: {}-{} Jahre alt\n" -"\n" -"🏙️ Partnerstadt: {}" - -#: functions/main_app/auxiliary_tools.py:121 -msgid "" -"Приветствую вас, {fullname}!!\n" -"\n" -"{heart} QueDateBot - платформа для поиска новых знакомств.\n" -"\n" -"🪧 Новости о проекте вы можете прочитать в нашем канале - " -"https://t.me/QueDateGroup \n" -"\n" -"🤝 Сотрудничество: \n" -"Если у вас есть предложение о сотрудничестве, пишите агенту поддержки - " -"@{supports}\n" -"\n" -msgstr "" -"Grüße, {fullname}!!!\n" -"\n" -"{heart} QueDateBot ist eine Plattform zum Finden neuer " -"Partnerschaften.\n" -"\n" -"🪧 Sie können Neuigkeiten über das Projekt in unserem Kanal lesen - " -"https://t.me/QueDateGroup \n" -"\n" -"🤝 Zusammenarbeit: \n" -"Wenn Sie einen Vorschlag für eine Zusammenarbeit haben, schreiben Sie an " -"den Support-Agenten - @{supports}\n" -"\n" - -#: functions/main_app/auxiliary_tools.py:168 -msgid "" -"По вашей ссылке зарегистрировался пользователь {}!\n" -"Вы получаете дополнительных 15 ❤️" -msgstr "" -"Ihr Link wurde von einem Benutzer {} verfolgt!\n" -"Sie erhalten 15 zusätzliche ❤️" - -#: functions/main_app/auxiliary_tools.py:194 -msgid "" -"Регистрация успешно завершена! \n" -"\n" -" {}, {} лет, {}\n" -"\n" -"О себе - {}" -msgstr "" -"Registrierung erfolgreich abgeschlossen! \n" -"\n" -" {}, {} Jahre, {}\n" -"\n" -"Über mich {}" - -#: functions/main_app/auxiliary_tools.py:215 -#: functions/main_app/auxiliary_tools.py:281 -msgid "Фото принято!" -msgstr "Foto akzeptiert!" - -#: functions/main_app/auxiliary_tools.py:219 -#: functions/main_app/auxiliary_tools.py:254 -#: functions/main_app/auxiliary_tools.py:290 -msgid "" -"Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" -"Если ошибка осталась, напишите агенту поддержки." -msgstr "" -"Es ist ein Fehler aufgetreten! Bitte versuchen Sie es erneut oder senden " -"Sie ein anderes Foto.\n" -"Wenn der Fehler weiterhin besteht, schreiben Sie bitte unserem Support-" -"Agenten." - -#: functions/main_app/auxiliary_tools.py:242 -msgid "" -"Во время проверки вашего фото мы обнаружили подозрительный контент!\n" -"Поэтому мы чуть-чуть подкорректировали вашу фотографию" -msgstr "" -"Während der Überprüfung Ihres Fotos haben wir verdächtige Inhalte " -"festgestellt!\n" -"Deshalb haben wir Ihr Foto ein wenig angepasst" - -#: functions/main_app/auxiliary_tools.py:262 -#, fuzzy -msgid "" -"Фото принято!\n" -"Выберите, что вы хотите изменить: " -msgstr "Wählen Sie aus, was Sie ändern möchten: " - -#: functions/main_app/auxiliary_tools.py:285 -msgid "Выберите, что вы хотите изменить: " -msgstr "Wählen Sie aus, was Sie ändern möchten: " - -#: functions/main_app/auxiliary_tools.py:336 -msgid "" -"Руководство по боту: \n" -"Страница №{}" -msgstr "" -"Bot-Anleitung:\n" -"Seite Nr. {}" - -#: functions/main_app/auxiliary_tools.py:352 -msgid "" -"Вы попали в раздел Информации бота, здесь вы можете посмотреть: " -"статистику,изменить язык, а также посмотреть наш брендбук.\n" -"\n" -"🌐 Дней работаем: {}\n" -"👤 Всего пользователей: {}\n" -msgstr "" -"Sie befinden sich im Abschnitt Informationen des Bots. Hier können" -" Sie folgendes anzeigen: Statistiken, Sprache ändern und unseren " -"Markenleitfaden einsehen.\n" -"\n" -"🌐 Tage aktiv: {}\n" -"👤 Gesamtanzahl Benutzer: {}\n" - -#: functions/main_app/determin_location.py:32 -msgid "" -"Я нашел такой адрес:\n" -"{city}\n" -"Если все правильно, то подтвердите" -msgstr "" -"Ich habe diese Adresse gefunden:\n" -"{city}\n" -"Wenn alles korrekt ist, bestätigen Sie" - -#: handlers/echo_handler.py:13 -msgid "Эхо без состояния." -msgstr "Echo ohne Zustand." - -#: handlers/echo_handler.py:38 -msgid "Меню: " -msgstr "Menü: " - -#: handlers/admins/monitoring.py:14 -msgid "Чтобы начать мониторинг нажмите на кнопку ниже" -msgstr "Um die Überwachung zu starten, klicken Sie auf die Schaltfläche unten" - -#: handlers/admins/monitoring.py:32 -msgid "Анкета пользователя была заблокирована" -msgstr "Das Profil des Benutzers wurde blockiert" - -#: handlers/admins/advert/advertisement.py:21 -msgid "" -"📧 Рассылка\n" -"Пришлите текст для рассылки либо фото с текстом для рассылки! Чтобы " -"отредактировать, используйте встроенный редактор телеграма!\n" -msgstr "" -"📧 Newsletter\n" -"Senden Sie einen Text zum Versenden oder ein Foto mit Text zum Versenden!" -" Verwenden Sie zum Bearbeiten den eingebauten Telegramm-Editor!\n" - -#: handlers/admins/advert/mailing/create.py:34 -#: handlers/admins/advert/mailing/create.py:124 -#: handlers/admins/advert/mailing/create.py:267 -msgid "Начинаю рассылку!" -msgstr "Beginne mit der Massenversand!" - -#: handlers/admins/advert/mailing/create.py:49 -#: handlers/admins/advert/mailing/create.py:137 -#: handlers/admins/advert/mailing/create.py:192 -#: handlers/admins/advert/mailing/create.py:281 -msgid "" -"Сообщение не дошло в чат {chat} Причина: \n" -"{err}" -msgstr "" -"Die Nachricht wurde nicht an den Chat {chat} gesendet. Grund: \n" -"{err}" - -#: handlers/admins/advert/mailing/create.py:55 -#: handlers/admins/advert/mailing/create.py:143 -#: handlers/admins/advert/mailing/create.py:198 -#: handlers/admins/advert/mailing/create.py:287 -msgid "Рассылка проведена успешно! Ее получили: {count} чатов!\n" -msgstr "" -"Die Massenversand wurde erfolgreich durchgeführt! {count} Chats haben es " -"erhalten!\n" - -#: handlers/admins/advert/mailing/create.py:61 -msgid "Пришлите мне название кнопки!" -msgstr "Bitte senden Sie mir den Namen der Taste!" - -#: handlers/admins/advert/mailing/create.py:70 -msgid "Название кнопки принято! Теперь отправьте мне ссылку для этой кнопки!" -msgstr "" -"Der Name der Taste wurde akzeptiert! Bitte senden Sie mir jetzt den Link " -"für diese Taste!" - -#: handlers/admins/advert/mailing/create.py:90 -#: handlers/admins/advert/mailing/create.py:232 -msgid "" -"Вот так будет выглядеть сообщение: \n" -"\n" -"{text}\n" -"\n" -msgstr "" -"So wird die Nachricht aussehen: \n" -"\n" -"{text}\n" -"\n" - -#: handlers/admins/advert/mailing/create.py:97 -#: handlers/admins/advert/mailing/create.py:239 -msgid "Вы подтверждаете отправку?" -msgstr "Möchten Sie den Versand bestätigen?" - -#: handlers/admins/advert/mailing/create.py:104 -#: handlers/admins/advert/mailing/create.py:246 -msgid "" -"Произошла ошибка! Скорее всего, неправильно введена ссылка! Попробуйте " -"еще раз." -msgstr "" -"Es ist ein Fehler aufgetreten! Möglicherweise wurde der Link falsch " -"eingegeben! Versuchen Sie es erneut." - -#: handlers/admins/advert/mailing/create.py:157 -msgid "" -"Вот сообщение: \n" -"\n" -"{text}\n" -"\n" -" Вы подтверждаете отправку? Или хотите что-то добавить?" -msgstr "" -"Hier ist die Nachricht: \n" -"\n" -"{text}\n" -"\n" -" Bestätigen Sie die Übermittlung? Oder möchten Sie etwas hinzufügen?" - -#: handlers/admins/settings/tech_works.py:26 -msgid "Чтобы включить/выключить технические работы, нажмите на кнопку ниже" -msgstr "" -"Um Wartungsarbeiten ein- oder auszuschalten, klicken Sie auf die " -"Schaltfläche unten" - -#: handlers/admins/settings/tech_works.py:36 -msgid "Технические работы включены" -msgstr "Wartungsarbeiten sind aktiviert" - -#: handlers/admins/settings/tech_works.py:44 -msgid "Технические работы выключены" -msgstr "Wartungsarbeiten sind deaktiviert" - -#: handlers/groups/event_moderate.py:24 -msgid "Принято!" -msgstr "Akzeptiert!" - -#: handlers/groups/event_moderate.py:33 -msgid "Ваше мероприятие прошло модерацию" -msgstr "Ihre Veranstaltung wurde moderiert" - -#: handlers/groups/event_moderate.py:40 -msgid "Отклонено!" -msgstr "Abgelehnt!" - -#: handlers/groups/event_moderate.py:44 -msgid "К сожалению ваше мероприятие не прошло модерацию" -msgstr "Leider hat Ihre Veranstaltung die Moderation nicht bestanden" - -#: handlers/groups/start.py:12 -msgid "" -"Привет, я бот, проекта Que Group, для верификации анкет для " -"знакомств\n" -"\n" -msgstr "" -"Hallo, ich bin ein Bot des Que Group-Projekts zur Überprüfung von " -"Profilen für Dating\n" -"\n" - -#: handlers/users/back.py:43 -msgid "Вы забанены!" -msgstr "Sie sind gesperrt!" - -#: handlers/users/back.py:50 -msgid "Вы вернулись в меню фильтров" -msgstr "Sie sind zurück im Filtermenü" - -#: handlers/users/brandbook_handler.py:23 -msgid "" -"Руководство по боту: \n" -"Страница №1" -msgstr "" -"Bot-Anleitung:\n" -"Seite Nr. 1" - -#: handlers/users/buy_unban.py:17 -msgid "" -"💳 Сейчас вам нужно выбрать способ оплаты\n" -"\n" -"├Стоимость разблокировки - 99₽\n" -"├Оплата обычно приходить в течение 1-3 минут\n" -"├Если у вас нет Yoomoney или нет возможности\n" -"├оплатить, напишите агенту поддержки" -msgstr "" -"💳 Jetzt müssen Sie eine Zahlungsmethode auswählen\n" -"\n" -"├Entsperrkosten - 99₽\n" -"├Die Zahlung erfolgt normalerweise innerhalb von 1-3 Minuten\n" -"├Wenn Sie kein Yoomoney haben oder nicht in der Lage sind zu bezahlen,\n" -"├schreiben Sie dem Support-Agenten" - -#: handlers/users/buy_unban.py:38 -msgid "После оплаты нажмите 🔄 Проверить оплату" -msgstr "Nach der Zahlung klicken Sie auf 🔄 Zahlung bestätigen" - -#: handlers/users/buy_unban.py:57 -msgid "Поздравляем! Вы были разрабанены" -msgstr "Glückwunsch! Du wurdest entfreundet" - -#: handlers/users/buy_unban.py:65 -msgid "" -"Оплата не прошла! Подождите минут 10, а затем еще раз попробуйте нажать " -"кнопку ниже" -msgstr "" -"Zahlung fehlgeschlagen! Warten Sie etwa 10 Minuten und versuchen Sie es " -"dann noch einmal mit der Schaltfläche unten" - -#: handlers/users/change_datas.py:35 -msgid "Ваши данные: \n" -msgstr "Ihre Angaben: .\n" - -#: handlers/users/change_datas.py:40 -msgid "Введите новое имя" -msgstr "Geben Sie einen neuen Namen ein" - -#: handlers/users/change_datas.py:53 -#, fuzzy -msgid "" -"Ваше новое имя: {censored}\n" -"Выберите, что вы хотите изменить: " -msgstr "" -"Daten erfolgreich geändert.\n" -"Wählen Sie aus, was Sie ändern möchten: " - -#: handlers/users/change_datas.py:63 -msgid "" -"Произошла неизвестная ошибка. Попробуйте ещё раз\n" -"Возможно, Ваше сообщение слишком большое" -msgstr "" -"Es ist ein unbekannter Fehler aufgetreten. Bitte versuchen Sie es erneut\n" -"Ihre Nachricht ist möglicherweise zu groß" - -#: handlers/users/change_datas.py:76 -msgid "Введите новый возраст" -msgstr "Neues Alter eingeben" - -#: handlers/users/change_datas.py:90 -#, fuzzy -msgid "" -"Ваш новый возраст: {messages}\n" -"Выберите, что вы хотите изменить: " -msgstr "Dein neues Alter: {messages}" - -#: handlers/users/change_datas.py:99 handlers/users/registration.py:163 -msgid "Вы ввели недопустимое число, попробуйте еще раз" -msgstr "Sie haben eine ungültige Zahl eingegeben. Bitte versuchen Sie es erneut" - -#: handlers/users/change_datas.py:104 -msgid "Вы ввели не число, попробуйте еще раз" -msgstr "Sie haben eine ungültige Zahl eingegeben. Bitte versuchen Sie es erneut" - -#: handlers/users/change_datas.py:112 -msgid "Введите новый город" -msgstr "Geben Sie eine neue Stadt ein" - -#: handlers/users/change_datas.py:124 -msgid "Мы не смогли найти город {city}. Попробуйте ещё раз" -msgstr "Wir konnten die Stadt {city} nicht finden. Erneut versuchen" - -#: handlers/users/change_datas.py:134 -msgid "" -"Данные успешно изменены.\n" -"Выберите, что вы хотите изменить: " -msgstr "" -"Daten erfolgreich geändert.\n" -"Wählen Sie aus, was Sie ändern möchten: " - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♂️ Мужской" -msgstr "👱🏻‍♂️ Männlich" - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♀️ Женский" -msgstr "👱🏻‍♀️ Weiblich" - -#: handlers/users/change_datas.py:145 -msgid "Выберите новый пол: " -msgstr "Wählen Sie ein neues Geschlecht: " - -#: handlers/users/change_datas.py:155 -#, fuzzy -msgid "" -"Ваш новый пол: {}\n" -"Выберите, что вы хотите изменить: " -msgstr "Wählen Sie aus, was Sie ändern möchten: " - -#: handlers/users/change_datas.py:167 -msgid "Отправьте мне новую фотографию" -msgstr "Schicken Sie mir ein neues Foto" - -#: handlers/users/change_datas.py:191 handlers/users/registration.py:244 -msgid "Произошла ошибка, проверьте настройки конфиденциальности" -msgstr "" -"Es ist ein Fehler aufgetreten. Überprüfen Sie Ihre " -"Datenschutzeinstellungen" - -#: handlers/users/change_datas.py:232 -#, fuzzy -msgid "Отправьте сообщение о себе" -msgstr "Senden Sie mir eine Sprachnachricht" - -#: handlers/users/change_datas.py:247 -#, fuzzy -msgid "" -"Комментарий принят!\n" -"Выберите, что вы хотите изменить: " -msgstr "Kommentar angenommen! Wählen Sie, wen Sie suchen möchten: " - -#: handlers/users/change_datas.py:254 -msgid "" -"Произошла ошибка! Попробуйте еще раз изменить описание. Возможно, Ваше " -"сообщение слишком большое\n" -"Если ошибка осталась, напишите в поддержку." -msgstr "" -"Es ist ein Fehler aufgetreten! Versuchen Sie, die Beschreibung erneut zu " -"ändern. Ihre Nachricht ist möglicherweise zu groß.\n" -"Wenn der Fehler weiterhin besteht, wenden Sie sich bitte an den Support." - -#: handlers/users/change_datas.py:267 -msgid "" -"Напишите имя своего аккаунта\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" -"Schreiben Sie Ihren Kontonamen\n" -"\n" -"Beispiele:\n" -"@unknown\n" -"https://www.instagram.com/unknown" - -#: handlers/users/change_datas.py:289 -msgid "Ваш аккаунт успешно добавлен" -msgstr "Ihr Konto wurde erfolgreich hinzugefügt" - -#: handlers/users/change_datas.py:293 handlers/users/verification.py:45 -msgid "Вы были возвращены в меню" -msgstr "Sie wurden zum Menü zurückgeführt" - -#: handlers/users/change_datas.py:297 -msgid "" -"Вы ввели неправильную ссылку или имя аккаунта.\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" -"Sie haben einen falschen Link oder einen falschen Benutzernamen " -"eingegeben.\n" -"\n" -"Beispiele:\n" -"@unknown\n" -"https://www.instagram.com/unknown" - -#: handlers/users/change_datas.py:304 -msgid "Возникла ошибка. Попробуйте еще раз" -msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut" - -#: handlers/users/change_event_datas.py:16 -msgid "Вы перешли в меню изменения данных мероприятия" -msgstr "Sie haben das Menü zur Änderung der Veranstaltungsdaten aufgerufen" - -#: handlers/users/change_event_datas.py:23 -msgid "Напишите новое название вашего мероприятия" -msgstr "Geben Sie den neuen Namen Ihrer Veranstaltung ein" - -#: handlers/users/change_event_datas.py:35 -#: handlers/users/change_event_datas.py:53 -msgid "Данные изменены" -msgstr "Daten wurden geändert" - -#: handlers/users/change_event_datas.py:41 -msgid "Напишите новое описание вашего мероприятия" -msgstr "Geben Sie eine neue Beschreibung für Ihre Veranstaltung ein" - -#: handlers/users/event_handler.py:31 -msgid "Вы перешли в меню афиш" -msgstr "Sie haben das Menü für Plakate aufgerufen" - -#: handlers/users/event_handler.py:48 -msgid "Введите название мероприятие" -msgstr "Geben Sie den Namen der Veranstaltung ein" - -#: handlers/users/event_handler.py:55 -msgid "" -"Вы уже создали мероприятие, которое проходит модерацию. Дождитесь " -"проверки, пожалуйста" -msgstr "" -"Sie haben bereits eine Veranstaltung erstellt, die gerade moderiert wird." -" Bitte warten Sie auf die Überprüfung" - -#: handlers/users/event_handler.py:63 -msgid "" -"Прочитайте сообщение и не нажимайте на кнопку, пока ваше мероприятие не " -"пройдет модерацию" -msgstr "" -"Lesen Sie die Nachricht und drücken Sie nicht auf die Schaltfläche, bis " -"Ihre Veranstaltung moderiert wurde" - -#: handlers/users/event_handler.py:78 -msgid "Пожалуйста, выберите дату: " -msgstr "🕕 ein Datum festlegen: " - -#: handlers/users/event_handler.py:83 -msgid "" -"Длинна вашего сообщение превышает допустимую.\n" -"Попробуйте ещё раз" -msgstr "" -"Die Länge Ihrer Nachricht überschreitet die zulässige Länge.\n" -"Erneut versuchen" - -#: handlers/users/event_handler.py:99 -msgid "Вы не можете проводить мероприятие в прошлом" -msgstr "Sie können keine Veranstaltung in der Vergangenheit abhalten" - -#: handlers/users/event_handler.py:105 -msgid "Теперь напишите место проведения" -msgstr "Geben Sie jetzt den Veranstaltungsort ein" - -#: handlers/users/event_handler.py:126 -msgid "Вы ввели слишком длинное название городаПопробуйте ещё раз." -msgstr "Sie haben einen zu langen Städtenamen eingegebenVersuchen Sie es erneut." - -#: handlers/users/event_handler.py:132 -msgid "" -"Произошла неизвестная ошибка! Попробуйте еще раз.\n" -"Вероятнее всего вы ввели город неправильно" -msgstr "" -"Ein unbekannter Fehler ist aufgetreten! Versuchen Sie es erneut.\n" -"Sie haben wahrscheinlich die Stadt falsch eingegeben" - -#: handlers/users/event_handler.py:142 -msgid "Хорошо, теперь напишите короткое или длинное описание вашего мероприятия" -msgstr "" -"Ok, schreiben Sie jetzt eine kurze oder lange Beschreibung Ihrer " -"Veranstaltung" - -#: handlers/users/event_handler.py:158 -msgid "И напоследок, пришлите постер вашего мероприятия" -msgstr "Und zuletzt senden Sie das Plakat Ihrer Veranstaltung" - -#: handlers/users/event_handler.py:164 -msgid "Ваше сообщение слишком длинное.Попробуйте написать короче" -msgstr "Ihre Nachricht ist zu lang, versuchen Sie, sie kürzer zu schreiben" - -#: handlers/users/event_handler.py:178 -msgid "Фото принято" -msgstr "Foto angenommen" - -#: handlers/users/event_handler.py:200 -msgid "Ваше мероприятие отправлено на модерацию" -msgstr "Ihre Veranstaltung wurde zur Moderation gesendet" - -#: handlers/users/event_list.py:23 -msgid "На данный момент вы никуда не записались" -msgstr "Sie sind derzeit nirgendwo eingeschrieben" - -#: handlers/users/filters.py:23 handlers/users/filters.py:29 -msgid "Вы перешли в раздел с фильтрами" -msgstr "Sie haben den Abschnitt mit Filtern aufgerufen" - -#: handlers/users/filters.py:41 -msgid "Напишите минимальный возраст" -msgstr "Geben Sie das Mindestalter ein" - -#: handlers/users/filters.py:53 -msgid "Теперь введите максимальный возраст" -msgstr "Geben Sie nun das Höchstalter ein" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♂️ Парня" -msgstr "👱🏻‍♂️ Kerl" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♀️ Девушку" -msgstr "👱🏻‍♀️ Ein Mädchen" - -#: handlers/users/filters.py:76 -msgid "Выберите, кого вы хотите найти:" -msgstr "Wählen Sie, wen Sie suchen möchten:" - -#: handlers/users/filters.py:84 handlers/users/filters.py:111 -msgid "Данные сохранены" -msgstr "Daten wurden gespeichert" - -#: handlers/users/filters.py:92 -msgid "Напишите город вашего будущего партнера" -msgstr "Schreiben Sie die Stadt Ihres zukünftigen Partners" - -#: handlers/users/filters.py:103 handlers/users/filters.py:144 -msgid "Произошла ошибка, попробуйте еще раз" -msgstr "Es ist ein Fehler aufgetreten, bitte versuchen Sie es erneut" - -#: handlers/users/filters.py:124 -msgid "Вы перешли в меню настроек фильтров для мероприятий" -msgstr "" -"Sie haben das Menü für die Filtereinstellungen für Veranstaltungen " -"geöffnet" - -#: handlers/users/filters.py:132 -msgid "Напишите город, в котором бы хотели сходить куда-нибудь" -msgstr "Schreiben Sie die Stadt, in der Sie gerne etwas unternehmen möchten" - -#: handlers/users/registration.py:42 -msgid "Пройдите опрос, чтобы зарегистрироваться" -msgstr "Beantworten Sie die Fragen, um sich zu registrieren" - -#: handlers/users/registration.py:52 -msgid "" -"Вы уже зарегистрированы, если вам нужно изменить анкету, то нажмите на " -"кнопку ниже" -msgstr "" -"Sie sind bereits registriert. Wenn Sie Ihr Profil ändern möchten, klicken" -" Sie auf die folgende Schaltfläche" - -#: handlers/users/registration.py:66 -msgid "Выберите пол" -msgstr "Wählen Sie Ihr Geschlecht" - -#: handlers/users/registration.py:88 -msgid "Теперь расскажите о себе:\n" -msgstr "Jetzt erzählen Sie mir etwas über sich selbst:\n" - -#: handlers/users/registration.py:105 -msgid "Комментарий принят! Выберите, кого вы хотите найти: " -msgstr "Kommentar angenommen! Wählen Sie, wen Sie suchen möchten: " - -#: handlers/users/registration.py:111 -msgid "" -"Произошла неизвестная ошибка! Попробуйте изменить комментарий позже в " -"разделе \"Меню\"\n" -"\n" -"Выберите, кого вы хотите найти: " -msgstr "" -"Es ist ein unbekannter Fehler aufgetreten! Versuchen Sie, Ihren Kommentar" -" später im Abschnitt Menü zu ändern\n" -"\n" -"Wählen Sie aus, wen Sie suchen möchten: " - -#: handlers/users/registration.py:125 -msgid "Отлично! Теперь напишите мне ваше имя, которое будут все видеть в анкете" -msgstr "" -"Super! Schreiben Sie mir jetzt Ihren Namen, den alle in Ihrem Profil " -"sehen werden" - -#: handlers/users/registration.py:145 -msgid "Введите сколько вам лет:" -msgstr "Geben Sie an, wie alt Sie sind:" - -#: handlers/users/registration.py:170 -msgid "Вы ввели не число" -msgstr "Sie haben keine Zahl eingegeben" - -#: handlers/users/registration.py:175 -msgid "Нажмите на кнопку ниже, чтобы определить ваш местоположение!" -msgstr "Klicken Sie auf die Schaltfläche unten, um Ihren Standort zu finden!" - -#: handlers/users/registration.py:188 -msgid "Мы не смогли найти такой город, попробуйте еще раз" -msgstr "Wir konnten eine solche Stadt nicht finden, versuchen Sie es erneut" - -#: handlers/users/registration.py:211 handlers/users/registration.py:224 -msgid "" -"И напоследок, Пришлите мне вашу фотографию (отправлять надо сжатое " -"изображение, а не как документ)" -msgstr "" -"Und schließlich: Schicken Sie mir ein Foto von sich (als komprimiertes " -"Bild, nicht als Dokument)" - -#: handlers/users/sponsor.py:10 -msgid "" -"Наш проект работает на Open Source и мы будем рады,если вы нам " -"поможете развивать проект.\n" -"\n" -"С помощью кнопки 💰 Донат вы можете отправить своё пожертвование" -msgstr "" -"Unser Projekt basiert auf Open Source und wir wären dankbar, wenn " -"Sie uns helfen, das Projekt weiterzuentwickeln.\n" -"\n" -"Mit der Schaltfläche 💰 Spenden können Sie Ihre Spende senden" - -#: handlers/users/start_handler.py:27 -msgid "Вам необходимо зарегистрировать агента(ов) тех поддержки" -msgstr "Sie müssen Ihre(n) Support-Agent(en) registrieren" - -#: handlers/users/start_handler.py:36 -msgid "Вас нет в базе данной" -msgstr "Sie sind nicht in der Datenbank" - -#: handlers/users/start_handler.py:42 handlers/users/start_handler.py:47 -msgid "Выберите язык" -msgstr "Wählen Sie eine Sprache aus" - -#: handlers/users/start_handler.py:57 -msgid "Язык был успешно изменен. Введите команду /start" -msgstr "Die Sprache wurde erfolgreich geändert. Geben Sie den Befehl /start ein" - -#: handlers/users/start_handler.py:61 -msgid "Произошла какая-то ошибка. Введите команду /start и попробуйте еще раз" -msgstr "" -"Es ist eine Art Fehler aufgetreten. Geben Sie den Befehl /start ein und " -"versuchen Sie es erneut" - -#: handlers/users/support_handler.py:20 -msgid "Хотите связаться с тех поддержкой? Нажмите на кнопку ниже!" -msgstr "" -"Möchten Sie den technischen Support kontaktieren? Klicken Sie auf die " -"Schaltfläche unten!" - -#: handlers/users/support_handler.py:25 handlers/users/support_handler.py:52 -msgid "К сожалению, сейчас нет свободных операторов. Попробуйте позже." -msgstr "" -"Leider sind im Moment keine freien Operator verfügbar. Bitte versuchen " -"Sie es später erneut." - -#: handlers/users/support_handler.py:41 -msgid "Вы обратились в техническую поддержку. Ждем ответа от оператора!" -msgstr "" -"Sie haben sich an den technischen Support gewendet. Wir warten auf eine " -"Antwort vom Operator!" - -#: handlers/users/support_handler.py:64 -msgid "С вами хочет связаться пользователь {full_name}" -msgstr "Der Benutzer {full_name} möchte mit Ihnen Kontakt aufnehmen" - -#: handlers/users/support_handler.py:79 -msgid "К сожалению, пользователь уже передумал." -msgstr "Leider hat der Benutzer seine Meinung geändert." - -#: handlers/users/support_handler.py:91 -msgid "" -"Вы на связи с пользователем!\n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" -"Sie sind mit dem Benutzer verbunden!\n" -"Klicken Sie auf die Schaltfläche, um das Gespräch zu beenden." - -#: handlers/users/support_handler.py:100 -msgid "" -"Техподдержка на связи! Можете писать сюда свое сообщение. \n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" -"Der technische Support ist online! Sie können hier Ihre Nachricht " -"schreiben. \n" -"Klicken Sie auf die Schaltfläche, um das Gespräch zu beenden." - -#: handlers/users/support_handler.py:115 -msgid "Дождитесь ответа оператора или отмените сеанс" -msgstr "Warten Sie auf die Antwort des Operators oder brechen Sie die Sitzung ab" - -#: handlers/users/support_handler.py:131 -msgid "Пользователь завершил сеанс техподдержки" -msgstr "Der Benutzer hat die technische Support-Sitzung beendet" - -#: handlers/users/support_handler.py:135 -msgid "Вы завершили сеанс и были возвращены в главное меню" -msgstr "Sie haben die Sitzung beendet und sind zum Hauptmenü zurückgekehrt" - -#: handlers/users/user_profile.py:23 -msgid "" -"Ваша анкета удалена!\n" -"Я надеюсь вы кого-нибудь нашли" -msgstr "" -"Ihr Profil wurde gelöscht!\n" -"Ich hoffe, Sie haben jemanden gefunden" - -#: handlers/users/verification.py:17 -msgid "Чтобы пройти верификацию вам нужно отправить свой контакт" -msgstr "Um sich zu verifizieren, müssen Sie Ihre Kontaktdaten senden" - -#: handlers/users/verification.py:33 -msgid "" -"Спасибо, {contact_full_name}.\n" -"Ваш номер {contact_phone_number} был получен." -msgstr "" -"Danke, {contact_full_name}.\n" -"Ihre Telefonnummer {contact_phone_number} wurde empfangen." - -#: handlers/users/verification.py:50 -msgid "Ваш номер недействителен, попробуйте еще раз." -msgstr "Ihre Telefonnummer ist ungültig. Bitte versuchen Sie es erneut." - -#: handlers/users/view_event.py:27 -msgid "На данный момент вы просмотрели все существующие анкеты" -msgstr "Sie haben derzeit alle vorhandenen Profile angesehen" - -#: handlers/users/view_ques.py:73 -#, fuzzy -msgid "Жалоба успешно отправлена" -msgstr "Ihr Konto wurde erfolgreich hinzugefügt" - -#: handlers/users/view_ques.py:112 -msgid "" -"Слишком много ❤️ за сегодня.\n" -"\n" -"Пригласи друзей и получи больше ❤️\n" -"\n" -"https://t.me/{}?start={}" -msgstr "" -"Zu viele ❤️ für heute.\n" -"\n" -"Laden Sie Ihre Freunde ein und erhalten Sie mehr ❤️\n" -"\n" -"https://t.me/{}?start={}" - -#: keyboards/admin/inline/customers.py:12 -msgid "🔍 Найти пользователя" -msgstr "🔍 Benutzer finden" - -#: keyboards/admin/inline/customers.py:23 -msgid "🟢 Разблокировать" -msgstr "🟢 Freischalten" - -#: keyboards/admin/inline/customers.py:28 -msgid "🚫 Заблокировать" -msgstr "🚫 Freischalten" - -#: keyboards/admin/inline/mailing.py:8 -msgid "📧 Рассылка" -msgstr "📧 Verteiler" - -#: keyboards/admin/inline/mailing.py:16 keyboards/admin/inline/mailing.py:31 -#: keyboards/inline/admin_inline.py:9 -msgid "Подтвердить отправку" -msgstr "Senden bestätigen" - -#: keyboards/admin/inline/mailing.py:19 -msgid "Добавить кнопку" -msgstr "Schaltfläche hinzufügen" - -#: keyboards/admin/inline/mailing.py:21 keyboards/admin/inline/mailing.py:33 -#: keyboards/inline/cancel_inline.py:8 -msgid "Отмена" -msgstr "Abbrechen" - -#: keyboards/admin/inline/payments.py:9 -msgid "⚙️ Настройки" -msgstr "⚙️ Einstellungen" - -#: keyboards/admin/inline/payments.py:11 -msgid "📝 Статистика" -msgstr "📝 Statistik" - -#: keyboards/admin/inline/ref.py:8 -msgid "📈 Статистика" -msgstr "📝 Statistik" - -#: keyboards/admin/inline/ref.py:9 keyboards/admin/inline/setting.py:8 -msgid "*️⃣ Добавить" -msgstr "*️⃣ Hinzufügen" - -#: keyboards/admin/inline/ref.py:10 keyboards/admin/inline/setting.py:9 -msgid "❌ Удалить" -msgstr "❌ Löschen" - -#: keyboards/admin/inline/ref.py:11 keyboards/admin/inline/setting.py:10 -msgid "◀️ Назад" -msgstr "◀️ Zurück" - -#: keyboards/admin/inline/reply_menu.py:9 -msgid "🙅🏻‍♂️ Отменить" -msgstr "🙅🏻‍♂️ Abbrechen" - -#: keyboards/admin/inline/reply_menu.py:17 -msgid "👮‍♂️ Админ Состав" -msgstr "👮‍♂️ Admin-Team" - -#: keyboards/admin/inline/reply_menu.py:19 -msgid "📞 Сменить контакты" -msgstr "📞 Kontakte ändern" - -#: keyboards/admin/inline/reply_menu.py:29 -msgid "🗒 Выгрузить юзеров | .txt" -msgstr "🗒 Benutzer exportieren | .txt" - -#: keyboards/admin/inline/reply_menu.py:32 -msgid "🗒 Выгрузить конфиги и логи" -msgstr "🗒 Konfigurationen und Logs exportieren" - -#: keyboards/default/admin_default.py:8 -msgid "Рассылка" -msgstr "Verteiler" - -#: keyboards/default/admin_default.py:9 -msgid "Сообщение по id" -msgstr "Nachricht nach ID" - -#: keyboards/default/admin_default.py:10 -msgid "Посчитать людей и чаты" -msgstr "Zähle Personen und Chats" - -#: keyboards/default/admin_default.py:11 -msgid "Мониторинг" -msgstr "Überwachung" - -#: keyboards/default/admin_default.py:12 -msgid "Тех.Работа" -msgstr "Technische Arbeit" - -#: keyboards/default/get_contact_default.py:8 -msgid "📱 Отправить" -msgstr "📱 Senden" - -#: keyboards/default/get_location_default.py:9 -msgid "🗺 Определить автоматически" -msgstr "🗺 Automatisch ermitteln" - -#: keyboards/default/get_photo.py:8 -msgid "Взять из профиля" -msgstr "Aus dem Profil übernehmen" - -#: keyboards/inline/admin_inline.py:18 -msgid "Включить" -msgstr "Einschalten" - -#: keyboards/inline/admin_inline.py:21 -msgid "Выключить" -msgstr "Ausschalten" - -#: keyboards/inline/admin_inline.py:33 -msgid "Разблокировать" -msgstr "Freischalten" - -#: keyboards/inline/back_inline.py:8 -#: keyboards/inline/change_data_profile_inline.py:15 -#: keyboards/inline/filters_inline.py:42 keyboards/inline/language_inline.py:22 -#: keyboards/inline/poster_inline.py:31 keyboards/inline/poster_inline.py:49 -#: keyboards/inline/poster_inline.py:63 -#: keyboards/inline/registration_inline.py:12 -#: keyboards/inline/settings_menu.py:12 keyboards/inline/sponsor_inline.py:10 -#: keyboards/inline/sponsor_inline.py:21 -msgid "⏪️ Вернуться в меню" -msgstr "⏪️ Zurück zum Menü" - -#: keyboards/inline/cancel_inline.py:16 -#: keyboards/inline/change_data_profile_inline.py:28 -msgid "❌ Остановить" -msgstr "❌ Beenden" - -#: keyboards/inline/change_data_profile_inline.py:8 -msgid "👤 Имя" -msgstr "👤 Name" - -#: keyboards/inline/change_data_profile_inline.py:9 -msgid "⚧ Пол" -msgstr "⚧ Geschlecht" - -#: keyboards/inline/change_data_profile_inline.py:10 -msgid "📅 Возраст" -msgstr "📅 Alter" - -#: keyboards/inline/change_data_profile_inline.py:11 -msgid "🏙 Город" -msgstr "🏙 Stadt" - -#: keyboards/inline/change_data_profile_inline.py:12 -msgid "📷 Фото" -msgstr "📷 Foto" - -#: keyboards/inline/change_data_profile_inline.py:13 -msgid "📝 О себе" -msgstr "📝 Über mich" - -#: keyboards/inline/filters_inline.py:9 -msgid "🎉 Мероприятия" -msgstr "🎉 Veranstaltungen" - -#: keyboards/inline/filters_inline.py:12 -msgid "❤️ Знакомства" -msgstr "❤️ Dating" - -#: keyboards/inline/filters_inline.py:14 keyboards/inline/filters_inline.py:31 -#: keyboards/inline/guide_inline.py:15 -msgid "⏪️ Назад" -msgstr "⏪️ Zurück" - -#: keyboards/inline/filters_inline.py:23 -msgid "🏙️ Город партнера" -msgstr "🏙️ Stadt deines Partners" - -#: keyboards/inline/filters_inline.py:26 -msgid "🔞 Возр.диапазон" -msgstr "🔞 Altersgruppe" - -#: keyboards/inline/filters_inline.py:29 -msgid "🚻 Пол партнера" -msgstr "Geschlecht des partners" - -#: keyboards/inline/filters_inline.py:40 -msgid "🏙️ Город" -msgstr "🏙️ Stadt" - -#: keyboards/inline/guide_inline.py:21 -msgid "Вперед ➡️" -msgstr "Los ➡️" - -#: keyboards/inline/guide_inline.py:25 -msgid "❌ Закрыть" -msgstr "❌ Schließen" - -#: keyboards/inline/language_inline.py:13 -msgid "🇷🇺 Русский" -msgstr "🇷🇺 Russisch" - -#: keyboards/inline/language_inline.py:14 -msgid "🇩🇪 Немецкий" -msgstr "🇩🇪 Deutsch" - -#: keyboards/inline/language_inline.py:15 -msgid "🇬🇧 Английский" -msgstr "🇬🇧 Englisch" - -#: keyboards/inline/language_inline.py:16 -msgid "🇮🇩 Индонезийский" -msgstr "🇮🇩 Südseesprachen" - -#: keyboards/inline/main_menu_inline.py:26 -msgid "➕ Регистрация" -msgstr "➕ Registrierung" - -#: keyboards/inline/main_menu_inline.py:28 keyboards/inline/settings_menu.py:10 -msgid "🌐 Язык" -msgstr "🌐 Sprache" - -#: keyboards/inline/main_menu_inline.py:30 -msgid "👤 Моя анекта" -msgstr "👤 Mein Profil" - -#: keyboards/inline/main_menu_inline.py:32 -msgid "⚙️ Фильтры" -msgstr "⚙️ Filter" - -#: keyboards/inline/main_menu_inline.py:33 -msgid "💌 Найти пару" -msgstr "💌 finden ein Paar" - -#: keyboards/inline/main_menu_inline.py:34 -msgid "🗓️ Афиша" -msgstr "🗓️ Poster" - -#: keyboards/inline/main_menu_inline.py:35 -msgid "🆘 Поддержка" -msgstr "🆘 Support" - -#: keyboards/inline/main_menu_inline.py:37 -msgid "ℹ️ Информация" -msgstr "ℹ️ Informationen" - -#: keyboards/inline/menu_profile_inline.py:10 -msgid "✅ Верификация" -msgstr "✅ Richtigkeitsprüfung" - -#: keyboards/inline/menu_profile_inline.py:16 -msgid "🖊️ Изменить" -msgstr "🖊️ Ändern" - -#: keyboards/inline/menu_profile_inline.py:18 -msgid "🗑️ Удалить" -msgstr "🗑️ Löschen" - -#: keyboards/inline/menu_profile_inline.py:19 -msgid "⏪ Назад" -msgstr "⏪ Zurück" - -#: keyboards/inline/payments_inline.py:9 -msgid "💳 ЮMoney" -msgstr "💳 YouMoney" - -#: keyboards/inline/payments_inline.py:16 -msgid "💳 Оплатить" -msgstr "💳 Bezahlen" - -#: keyboards/inline/payments_inline.py:18 -msgid "🔄 Проверить оплату" -msgstr "🔄 Scheckzahlung" - -#: keyboards/inline/poster_inline.py:21 -msgid "✍️Создать афишу" -msgstr "Plakat erstellen" - -#: keyboards/inline/poster_inline.py:24 -msgid "🎭 Смотреть афиши" -msgstr "🎭 Poster beobachten" - -#: keyboards/inline/poster_inline.py:27 -msgid "📝 Мои записи" -msgstr "📝 Meine Aufzeichnungen" - -#: keyboards/inline/poster_inline.py:29 -msgid "📃 Моё событие" -msgstr "📃 Mein Ereignis" - -#: keyboards/inline/poster_inline.py:46 -msgid "✍️ Изменить" -msgstr "✍️ Ändern" - -#: keyboards/inline/poster_inline.py:58 -msgid "Название" -msgstr "Titel" - -#: keyboards/inline/poster_inline.py:60 -msgid "Описание" -msgstr "Beschreibung" - -#: keyboards/inline/poster_inline.py:73 -msgid "✅ Одобрить" -msgstr "✅ Genehmigen" - -#: keyboards/inline/poster_inline.py:76 -msgid "❌ Отклонить" -msgstr "Ablehnen" - -#: keyboards/inline/poster_inline.py:85 -msgid "Пойду!" -msgstr "Ich gehe hin!" - -#: keyboards/inline/poster_inline.py:88 -msgid "Не интересно" -msgstr "Nicht interessiert" - -#: keyboards/inline/poster_inline.py:91 keyboards/inline/poster_inline.py:103 -#: keyboards/inline/poster_inline.py:112 -msgid "⏪️ Остановить" -msgstr "⏪️ Stoppen" - -#: keyboards/inline/poster_inline.py:101 -msgid "❌ Отменить запись" -msgstr "❌ Eintrag abbrechen" - -#: keyboards/inline/questionnaires_inline.py:30 -#, fuzzy -msgid "💤 Остановить" -msgstr "❌ Beenden" - -#: keyboards/inline/questionnaires_inline.py:34 -msgid "🚫 Забанить" -msgstr "🚫 Blockieren" - -#: keyboards/inline/questionnaires_inline.py:38 -msgid "Следующий" -msgstr "Nächste Person" - -#: keyboards/inline/questionnaires_inline.py:74 -msgid "🚀 Смотреть" -msgstr "🚀 Ansehen" - -#: keyboards/inline/questionnaires_inline.py:82 -msgid "👉 Перейти в чат" -msgstr "👉 Zum Chat gehen" - -#: keyboards/inline/questionnaires_inline.py:89 -msgid "⏪️ Вернуться к просмотру анкет" -msgstr "⏪️ Zurück zur Profilansicht" - -#: keyboards/inline/registration_inline.py:9 -msgid "🖌️ Пройти опрос в боте" -msgstr "🖌️ Durchgehen Umfrage im Bot" - -#: keyboards/inline/registration_inline.py:21 -msgid "✅ Да все хорошо!" -msgstr "✅ Ja, alles ist gut!" - -#: keyboards/inline/settings_menu.py:8 -msgid "📚 Брендбук" -msgstr "📚 Markenbuch" - -#: keyboards/inline/settings_menu.py:9 -msgid "📞 Контакты" -msgstr "📞 Kontakte" - -#: keyboards/inline/support_inline.py:37 -msgid "Ответить пользователю" -msgstr "Dem Benutzer antworten" - -#: keyboards/inline/support_inline.py:45 -msgid "Написать оператору" -msgstr "Schreiben Sie an den Betreiber" - -#: keyboards/inline/support_inline.py:60 keyboards/inline/support_inline.py:72 -msgid "Завершить сеанс" -msgstr "Sitzung beenden" - -#: middlewares/BanCheck.py:44 -msgid "😢 Вы заблокированы!" -msgstr "😢 Sie sind blockiert!" - -#: middlewares/IsMaintenanceCheck.py:26 -msgid "Ведутся технические работы" -msgstr "Technische Arbeiten sind im Gange" - -#: middlewares/LinkCheck.py:38 -msgid "" -"Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " -"подпишитесь! Ссылки ниже: " -msgstr "" -"Sie haben nicht alle Kanäle abonniert! Um den Bot weiterhin zu nutzen, " -"abonnieren Sie bitte! Links unten: " - -#: utils/notify_admins.py:24 -msgid "Оповещение администрации..." -msgstr "" - -#: utils/notify_admins.py:28 -msgid "Бот был успешно запущен" -msgstr "" - -#: utils/statistics.py:16 -msgid "" -"📊 Статистика: \n" -"\n" -"└Сейчас в нашем боте {count_users} пользователей\n" -"└Из них:\n" -" ├{users_gender_m} пользователей мужского пола\n" -" ├{users_gender_f} пользователей женского пола\n" -" ├{users_city} пользователей из города {user_city}\n" -" ├{cs_uy} пользователей из других городов\n" -" ├{users_verified} верифицированных пользователей\n" -" ├{users_status} пользователей, создавшие анкету\n" -"└Дата создания бота - 10.08.2021" -msgstr "" -"📊 Statistik: \n" -"\n" -"└Derzeit sind {count_users} Benutzer in unserem Bot registriert.\n" -"└Davon:\n" -" ├{users_gender_m} männliche Benutzer\n" -" ├{users_gender_f} weibliche Benutzer\n" -" ├{users_city} Benutzer aus der Stadt {user_city}\n" -" ├{cs_uy} Benutzer aus anderen Städten\n" -" ├{users_verified} verifizierte Benutzer\n" -" ├{users_status} Benutzer, die ein Profil erstellt haben\n" -"└Bot-Erstellungsdatum - 10.08.2021" - -#~ msgid "🗒 Логи" -#~ msgstr "🗒 Logs" - -#~ msgid "Ваше новое имя: {censored}" -#~ msgstr "Ihr neuer Name ist {censored}" - -#~ msgid "Ваш новый пол: {}" -#~ msgstr "Dein neues Geschlecht: {}" - -#~ msgid "Отправьте мне новое описание анкеты:" -#~ msgstr "Senden Sie mir eine neue Profilbeschreibung:" - -#~ msgid "Комментарий принят!" -#~ msgstr "Kommentar akzeptiert!" - -#~ msgid "🫂 Пользователи" -#~ msgstr "🫂 Benutzer" - -#~ msgid "📊 Реклама" -#~ msgstr "📊 Werbung" - -#~ msgid "👀 Мониторинг" -#~ msgstr "👀 Überwachung" - -#~ msgid "🛑 Тех.Работа" -#~ msgstr "🛑 Tech.Arbeit" - -#~ msgid "Чат с админом не найден" -#~ msgstr "" - diff --git a/locales/en/LC_MESSAGES/dating.po b/locales/en/LC_MESSAGES/dating.po deleted file mode 100644 index c185a24..0000000 --- a/locales/en/LC_MESSAGES/dating.po +++ /dev/null @@ -1,1422 +0,0 @@ -# English translations for PROJECT. -# Copyright (C) 2022 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2022. -# -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-08-28 14:39+0300\n" -"PO-Revision-Date: 2023-08-21 16:03+0300\n" -"Last-Translator: \n" -"Language: en\n" -"Language-Team: en \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.1\n" - -#: functions/dating/reaction_strategies.py:46 -#: functions/dating/reaction_strategies.py:171 -msgid "На данный момент у нас нет подходящих анкет для вас" -msgstr "At the moment we don't have any suitable questionnaires for you" - -#: functions/dating/reaction_strategies.py:52 -msgid "У вас достигнут лимит на просмотры анкет" -msgstr "You have reached the limit for profile views" - -#: functions/dating/reaction_strategies.py:61 -msgid "Кому-то понравилась твоя анкета" -msgstr "Someone liked your profile" - -#: functions/dating/reaction_strategies.py:103 handlers/users/view_event.py:51 -msgid "" -"Рад был помочь, {fullname}!\n" -"Надеюсь, ты нашел кого-то благодаря мне" -msgstr "" -"Glad to help, {fullname}!\n" -"I hope you found someone thanks to me" - -#: functions/dating/reaction_strategies.py:126 -msgid "Отлично! Надеюсь вы хорошо проведете время ;) Начинай общаться 👉" -msgstr "Great! Hope you have a good time ;) Start chatting 👉" - -#: functions/dating/reaction_strategies.py:187 -msgid "Выберите причину жалобы:" -msgstr "" - -#: functions/dating/reaction_strategies.py:204 -msgid "" -"Жалоба от пользователя: [@{username} | {tg_id}]" -"\n" -"\n" -"На пользователя: [{owner_id}]\n" -"Причина жалобы: {reason}\n" -"Количество жалоб на пользователя: {counter_of_report}" -msgstr "" - -#: functions/dating/send_form_func.py:25 -msgid "" -"{}, {} лет, {} {verification}\n" -"\n" -msgstr "" -"{}, {} years old, {} {verification}\n" -"\n" - -#: functions/dating/send_form_func.py:28 -msgid "{commentary}" -msgstr "" - -#: functions/dating/send_form_func.py:36 -msgid "Инстаграм - {instagram}\n" -msgstr "Instagram - {instagram}\n" - -#: functions/dating/send_form_func.py:48 -msgid "" -"{}\n" -"\n" -"{}" -msgstr "" - -#: functions/dating/send_form_func.py:57 -msgid "" -"{}\n" -"\n" -"Инстаграм - {instagram}\n" -msgstr "" -"{}\n" -"\n" -"Instagram - {instagram}\n" - -#: functions/event/extra_features.py:86 -msgid "На данный момент у нас нет подходящих мероприятий для вас" -msgstr "We currently have no suitable events for you" - -#: functions/event/templates_messages.py:17 -msgid "" -"{} \n" -"Когда: {} \n" -"Где: {} \n" -"\n" -"{}" -msgstr "" -"{} \n" -"When: {} \n" -"Where: {} \n" -"\n" -"{}" - -#: functions/main_app/app_scheduler.py:12 -msgid "Несколько {} из города {} хотят познакомиться с тобой прямо сейчас" -msgstr "Several {} from {} want to meet you right now" - -#: functions/main_app/auxiliary_tools.py:71 -msgid "" -"{name}, {age} лет, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Партнерка:\n" -"Количество приглашенных друзей: {reff}\n" -"Реферальная ссылка:\n" -" {link}" -msgstr "" -"{name}, {age} years old, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Affiliate program:\n" -"Number of invited friends: {reff}\n" -"Referral link:\n" -" {link}" - -#: functions/main_app/auxiliary_tools.py:96 -msgid "" -"Фильтр по подбору партнеров:\n" -"\n" -"🚻 Необходимы пол партнера: {}\n" -"🔞 Возрастной диапазон: {}-{} лет\n" -"\n" -"🏙️ Город партнера: {}" -msgstr "" -"Partner Selection Filter:\n" -"\n" -"🚻 The gender of the partner is required: {}\n" -"🔞 Age range: {}-{} лет\n" -"\n" -"🏙️ Partner's city: {}" - -#: functions/main_app/auxiliary_tools.py:121 -msgid "" -"Приветствую вас, {fullname}!!\n" -"\n" -"{heart} QueDateBot - платформа для поиска новых знакомств.\n" -"\n" -"🪧 Новости о проекте вы можете прочитать в нашем канале - " -"https://t.me/QueDateGroup \n" -"\n" -"🤝 Сотрудничество: \n" -"Если у вас есть предложение о сотрудничестве, пишите агенту поддержки - " -"@{supports}\n" -"\n" -msgstr "" -"Welcome you, {fullname}!!\n" -"\n" -"{heart} QueDateBot - a platform for finding new acquaintances.\n" -"\n" -"🪧 You can read the news about the project in our channel " -"-https://t.me/QueDateGroup \n" -"\n" -"🤝 Cooperation: \n" -"If you have a cooperation offer, write to the support agent - @{supports}" -"\n" -"\n" - -#: functions/main_app/auxiliary_tools.py:168 -msgid "" -"По вашей ссылке зарегистрировался пользователь {}!\n" -"Вы получаете дополнительных 15 ❤️" -msgstr "" -"A user {} has registered using your link!\n" -"You receive an additional 15 ❤️" - -#: functions/main_app/auxiliary_tools.py:194 -msgid "" -"Регистрация успешно завершена! \n" -"\n" -" {}, {} лет, {}\n" -"\n" -"О себе - {}" -msgstr "" -"Registration completed successfully!\n" -"\n" -"{}, {} years old, {}\n" -"\n" -"About Me - {}" - -#: functions/main_app/auxiliary_tools.py:215 -#: functions/main_app/auxiliary_tools.py:281 -msgid "Фото принято!" -msgstr "Photo taken!" - -#: functions/main_app/auxiliary_tools.py:219 -#: functions/main_app/auxiliary_tools.py:254 -#: functions/main_app/auxiliary_tools.py:290 -msgid "" -"Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" -"Если ошибка осталась, напишите агенту поддержки." -msgstr "" -"An error has occurred! Try again or send another photo. \n" -"If the error remains, write to the support agent." - -#: functions/main_app/auxiliary_tools.py:242 -msgid "" -"Во время проверки вашего фото мы обнаружили подозрительный контент!\n" -"Поэтому мы чуть-чуть подкорректировали вашу фотографию" -msgstr "" -"During the verification of your photo, we detected suspicious content!\n" -"That's why we slightly adjusted your photograph" - -#: functions/main_app/auxiliary_tools.py:262 -#, fuzzy -msgid "" -"Фото принято!\n" -"Выберите, что вы хотите изменить: " -msgstr "Choose what you want to change: " - -#: functions/main_app/auxiliary_tools.py:285 -msgid "Выберите, что вы хотите изменить: " -msgstr "Choose what you want to change: " - -#: functions/main_app/auxiliary_tools.py:336 -msgid "" -"Руководство по боту: \n" -"Страница №{}" -msgstr "" -"Bot Manual: \n" -"Page #1" - -#: functions/main_app/auxiliary_tools.py:352 -msgid "" -"Вы попали в раздел Информации бота, здесь вы можете посмотреть: " -"статистику,изменить язык, а также посмотреть наш брендбук.\n" -"\n" -"🌐 Дней работаем: {}\n" -"👤 Всего пользователей: {}\n" -msgstr "" -"You are in the Information section of the bot, where you can view:" -" statistics, change the language, and also view our brandbook.\n" -"\n" -"🌐 Days of operation: {}\n" -"👤 Total users: {}\n" - -#: functions/main_app/determin_location.py:32 -#, fuzzy -msgid "" -"Я нашел такой адрес:\n" -"{city}\n" -"Если все правильно, то подтвердите" -msgstr "" -"I found this address:\n" -"{city}\n" -"If everything is correct then confirm" - -#: handlers/echo_handler.py:13 -msgid "Эхо без состояния." -msgstr "Echo without condition." - -#: handlers/echo_handler.py:38 -msgid "Меню: " -msgstr "Menu: " - -#: handlers/admins/monitoring.py:14 -msgid "Чтобы начать мониторинг нажмите на кнопку ниже" -msgstr "To start monitoring, click on the button below" - -#: handlers/admins/monitoring.py:32 -msgid "Анкета пользователя была заблокирована" -msgstr "The user's profile has been blocked" - -#: handlers/admins/advert/advertisement.py:21 -msgid "" -"📧 Рассылка\n" -"Пришлите текст для рассылки либо фото с текстом для рассылки! Чтобы " -"отредактировать, используйте встроенный редактор телеграма!\n" -msgstr "" -"📧 Рассылка\n" -"Send the text for mailing or a photo with the text for mailing! To edit, " -"use the built-in telegram editor!\n" - -#: handlers/admins/advert/mailing/create.py:34 -#: handlers/admins/advert/mailing/create.py:124 -#: handlers/admins/advert/mailing/create.py:267 -msgid "Начинаю рассылку!" -msgstr "I'm starting the newsletter!" - -#: handlers/admins/advert/mailing/create.py:49 -#: handlers/admins/advert/mailing/create.py:137 -#: handlers/admins/advert/mailing/create.py:192 -#: handlers/admins/advert/mailing/create.py:281 -msgid "" -"Сообщение не дошло в чат {chat} Причина: \n" -"{err}" -msgstr "" -"The message did not reach the chat {chat} Reason: \n" -"{err}" - -#: handlers/admins/advert/mailing/create.py:55 -#: handlers/admins/advert/mailing/create.py:143 -#: handlers/admins/advert/mailing/create.py:198 -#: handlers/admins/advert/mailing/create.py:287 -msgid "Рассылка проведена успешно! Ее получили: {count} чатов!\n" -msgstr "" -"The mailing was carried out successfully! It was received: {count} chats!" -"\n" - -#: handlers/admins/advert/mailing/create.py:61 -msgid "Пришлите мне название кнопки!" -msgstr "Send me the button name!" - -#: handlers/admins/advert/mailing/create.py:70 -msgid "Название кнопки принято! Теперь отправьте мне ссылку для этой кнопки!" -msgstr "The name of the button is accepted! Now send me the link for this button!" - -#: handlers/admins/advert/mailing/create.py:90 -#: handlers/admins/advert/mailing/create.py:232 -msgid "" -"Вот так будет выглядеть сообщение: \n" -"\n" -"{text}\n" -"\n" -msgstr "" -"This is how the message will look like: \n" -"\n" -"{text}\n" -"\n" - -#: handlers/admins/advert/mailing/create.py:97 -#: handlers/admins/advert/mailing/create.py:239 -msgid "Вы подтверждаете отправку?" -msgstr "Do you confirm the shipment?" - -#: handlers/admins/advert/mailing/create.py:104 -#: handlers/admins/advert/mailing/create.py:246 -msgid "" -"Произошла ошибка! Скорее всего, неправильно введена ссылка! Попробуйте " -"еще раз." -msgstr "" -"An error has occurred! Most likely, the link was entered incorrectly! " -"Tryone more time." - -#: handlers/admins/advert/mailing/create.py:157 -msgid "" -"Вот сообщение: \n" -"\n" -"{text}\n" -"\n" -" Вы подтверждаете отправку? Или хотите что-то добавить?" -msgstr "" -"Here is the message: \n" -"\n" -"{text}\n" -"\n" -" Do you confirm the shipment? Or do you want to add something?" - -#: handlers/admins/settings/tech_works.py:26 -msgid "Чтобы включить/выключить технические работы, нажмите на кнопку ниже" -msgstr "To start monitoring, click on the button below" - -#: handlers/admins/settings/tech_works.py:36 -msgid "Технические работы включены" -msgstr "Technical works are enabled" - -#: handlers/admins/settings/tech_works.py:44 -msgid "Технические работы выключены" -msgstr "Technical works are disabled" - -#: handlers/groups/event_moderate.py:24 -msgid "Принято!" -msgstr "Accepted!" - -#: handlers/groups/event_moderate.py:33 -msgid "Ваше мероприятие прошло модерацию" -msgstr "Your event has passed moderation" - -#: handlers/groups/event_moderate.py:40 -msgid "Отклонено!" -msgstr "Rejected!" - -#: handlers/groups/event_moderate.py:44 -msgid "К сожалению ваше мероприятие не прошло модерацию" -msgstr "Unfortunately, your event did not pass moderation" - -#: handlers/groups/start.py:12 -msgid "" -"Привет, я бот, проекта Que Group, для верификации анкет для " -"знакомств\n" -"\n" -msgstr "" -"Hello, I am a bot from the Que Group project, for profile verification" -" for dating\n" -"\n" - -#: handlers/users/back.py:43 -msgid "Вы забанены!" -msgstr "You are banned!" - -#: handlers/users/back.py:50 -msgid "Вы вернулись в меню фильтров" -msgstr "You have returned to the filter menu" - -#: handlers/users/brandbook_handler.py:23 -msgid "" -"Руководство по боту: \n" -"Страница №1" -msgstr "" -"Bot Manual: \n" -"Page #1" - -#: handlers/users/buy_unban.py:17 -msgid "" -"💳 Сейчас вам нужно выбрать способ оплаты\n" -"\n" -"├Стоимость разблокировки - 99₽\n" -"├Оплата обычно приходить в течение 1-3 минут\n" -"├Если у вас нет Yoomoney или нет возможности\n" -"├оплатить, напишите агенту поддержки" -msgstr "" -"💳 Now you need to choose a payment method\n" -"\n" -"├ Unlocking cost - 99₽\n" -"├ Payment usually arrives within 1-3 minutes\n" -"├ If you don't have Yoomoney or the possibility to\n" -"├ pay, write to the support agent" - -#: handlers/users/buy_unban.py:38 -msgid "После оплаты нажмите 🔄 Проверить оплату" -msgstr "After payment, click 🔄 Check payment" - -#: handlers/users/buy_unban.py:57 -msgid "Поздравляем! Вы были разрабанены" -msgstr "Congratulations! You have been unbanned" - -#: handlers/users/buy_unban.py:65 -msgid "" -"Оплата не прошла! Подождите минут 10, а затем еще раз попробуйте нажать " -"кнопку ниже" -msgstr "" -"Payment didn't go through! Wait for 10 minutes and then try pressing the " -"button below again" - -#: handlers/users/change_datas.py:35 -msgid "Ваши данные: \n" -msgstr "Your datas: \n" - -#: handlers/users/change_datas.py:40 -msgid "Введите новое имя" -msgstr "Input new name" - -#: handlers/users/change_datas.py:53 -#, fuzzy -msgid "" -"Ваше новое имя: {censored}\n" -"Выберите, что вы хотите изменить: " -msgstr "" -"The data has been successfully changed.\n" -"Choose what you want to modify: " - -#: handlers/users/change_datas.py:63 -msgid "" -"Произошла неизвестная ошибка. Попробуйте ещё раз\n" -"Возможно, Ваше сообщение слишком большое" -msgstr "" -"An unknown error has occurred. Try again\n" -"Perhaps your message is too large" - -#: handlers/users/change_datas.py:76 -msgid "Введите новый возраст" -msgstr "Input a new age" - -#: handlers/users/change_datas.py:90 -#, fuzzy -msgid "" -"Ваш новый возраст: {messages}\n" -"Выберите, что вы хотите изменить: " -msgstr "Your new age: {messages}" - -#: handlers/users/change_datas.py:99 handlers/users/registration.py:163 -msgid "Вы ввели недопустимое число, попробуйте еще раз" -msgstr "You entered an invalid number, try again" - -#: handlers/users/change_datas.py:104 -msgid "Вы ввели не число, попробуйте еще раз" -msgstr "You entered an invalid number, try again" - -#: handlers/users/change_datas.py:112 -msgid "Введите новый город" -msgstr "Input new city" - -#: handlers/users/change_datas.py:124 -msgid "Мы не смогли найти город {city}. Попробуйте ещё раз" -msgstr "We couldn't find the city {city}. Please try again" - -#: handlers/users/change_datas.py:134 -msgid "" -"Данные успешно изменены.\n" -"Выберите, что вы хотите изменить: " -msgstr "" -"The data has been successfully changed.\n" -"Choose what you want to modify: " - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♂️ Мужской" -msgstr "👱🏻‍♂️ Male" - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♀️ Женский" -msgstr "👱🏻‍♀️ Female" - -#: handlers/users/change_datas.py:145 -msgid "Выберите новый пол: " -msgstr "Choose new gender: " - -#: handlers/users/change_datas.py:155 -#, fuzzy -msgid "" -"Ваш новый пол: {}\n" -"Выберите, что вы хотите изменить: " -msgstr "Choose what you want to change: " - -#: handlers/users/change_datas.py:167 -msgid "Отправьте мне новую фотографию" -msgstr "Send me a new photo" - -#: handlers/users/change_datas.py:191 handlers/users/registration.py:244 -msgid "Произошла ошибка, проверьте настройки конфиденциальности" -msgstr "An error occurred, please check your privacy settings" - -#: handlers/users/change_datas.py:232 -#, fuzzy -msgid "Отправьте сообщение о себе" -msgstr "Send a voice message" - -#: handlers/users/change_datas.py:247 -#, fuzzy -msgid "" -"Комментарий принят!\n" -"Выберите, что вы хотите изменить: " -msgstr "Comment accepted! Choose who you want to find: " - -#: handlers/users/change_datas.py:254 -msgid "" -"Произошла ошибка! Попробуйте еще раз изменить описание. Возможно, Ваше " -"сообщение слишком большое\n" -"Если ошибка осталась, напишите в поддержку." -msgstr "" -"An error has occurred! Try again or send another photo. \n" -"If the error remains, write to the support agent." - -#: handlers/users/change_datas.py:267 -msgid "" -"Напишите имя своего аккаунта\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" -"Write your account name\n" -"\n" -"Examples:\n" -"@unknown\n" -"https://www.instagram.com/unknown" - -#: handlers/users/change_datas.py:289 -msgid "Ваш аккаунт успешно добавлен" -msgstr "Your account has been successfully added" - -#: handlers/users/change_datas.py:293 handlers/users/verification.py:45 -msgid "Вы были возвращены в меню" -msgstr "You have been returned to the menu" - -#: handlers/users/change_datas.py:297 -msgid "" -"Вы ввели неправильную ссылку или имя аккаунта.\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" - -#: handlers/users/change_datas.py:304 -msgid "Возникла ошибка. Попробуйте еще раз" -msgstr "An error has occurred. Try again" - -#: handlers/users/change_event_datas.py:16 -msgid "Вы перешли в меню изменения данных мероприятия" -msgstr "You have entered the event data change menu" - -#: handlers/users/change_event_datas.py:23 -msgid "Напишите новое название вашего мероприятия" -msgstr "Write the new name of your event" - -#: handlers/users/change_event_datas.py:35 -#: handlers/users/change_event_datas.py:53 -msgid "Данные изменены" -msgstr "Data saved" - -#: handlers/users/change_event_datas.py:41 -msgid "Напишите новое описание вашего мероприятия" -msgstr "Write a new description for your event" - -#: handlers/users/event_handler.py:31 -msgid "Вы перешли в меню афиш" -msgstr "You have entered the event menu" - -#: handlers/users/event_handler.py:48 -msgid "Введите название мероприятие" -msgstr "Enter the name of the event" - -#: handlers/users/event_handler.py:55 -msgid "" -"Вы уже создали мероприятие, которое проходит модерацию. Дождитесь " -"проверки, пожалуйста" -msgstr "" -"You have already created an event that is under moderation. Please wait " -"for verification" - -#: handlers/users/event_handler.py:63 -msgid "" -"Прочитайте сообщение и не нажимайте на кнопку, пока ваше мероприятие не " -"пройдет модерацию" -msgstr "" -"Read the message and do not press the button until your event passes " -"moderation" - -#: handlers/users/event_handler.py:78 -msgid "Пожалуйста, выберите дату: " -msgstr "Select date: " - -#: handlers/users/event_handler.py:83 -msgid "" -"Длинна вашего сообщение превышает допустимую.\n" -"Попробуйте ещё раз" -msgstr "" - -#: handlers/users/event_handler.py:99 -msgid "Вы не можете проводить мероприятие в прошлом" -msgstr "You cannot hold an event in the past" - -#: handlers/users/event_handler.py:105 -msgid "Теперь напишите место проведения" -msgstr "Now write the venue" - -#: handlers/users/event_handler.py:126 -msgid "Вы ввели слишком длинное название городаПопробуйте ещё раз." -msgstr "You entered a city name that is too long. Please try again." - -#: handlers/users/event_handler.py:132 -msgid "" -"Произошла неизвестная ошибка! Попробуйте еще раз.\n" -"Вероятнее всего вы ввели город неправильно" -msgstr "" -"An unknown error has occurred! Try again.\n" -"Most likely you entered the city incorrectly" - -#: handlers/users/event_handler.py:142 -msgid "Хорошо, теперь напишите короткое или длинное описание вашего мероприятия" -msgstr "Alright, now write a short or long description of your event" - -#: handlers/users/event_handler.py:158 -msgid "И напоследок, пришлите постер вашего мероприятия" -msgstr "And finally, send me your photo" - -#: handlers/users/event_handler.py:164 -msgid "Ваше сообщение слишком длинное.Попробуйте написать короче" -msgstr "Your message is too long. Try to write shorter" - -#: handlers/users/event_handler.py:178 -msgid "Фото принято" -msgstr "Photo taken" - -#: handlers/users/event_handler.py:200 -msgid "Ваше мероприятие отправлено на модерацию" -msgstr "Your event has been submitted for moderation" - -#: handlers/users/event_list.py:23 -msgid "На данный момент вы никуда не записались" -msgstr "At the moment we don't have any suitable questionnaires for you" - -#: handlers/users/filters.py:23 handlers/users/filters.py:29 -msgid "Вы перешли в раздел с фильтрами" -msgstr "You have entered the section with filters" - -#: handlers/users/filters.py:41 -msgid "Напишите минимальный возраст" -msgstr "Write the minimum age" - -#: handlers/users/filters.py:53 -msgid "Теперь введите максимальный возраст" -msgstr "The data is saved, now enter the maximum age" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♂️ Парня" -msgstr "👱🏻‍♂️ Guy" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♀️ Девушку" -msgstr "👱🏻‍♀️ Girl" - -#: handlers/users/filters.py:76 -msgid "Выберите, кого вы хотите найти:" -msgstr "Choose who you want to find:" - -#: handlers/users/filters.py:84 handlers/users/filters.py:111 -msgid "Данные сохранены" -msgstr "Data saved" - -#: handlers/users/filters.py:92 -msgid "Напишите город вашего будущего партнера" -msgstr "Write the city of your future partner" - -#: handlers/users/filters.py:103 handlers/users/filters.py:144 -msgid "Произошла ошибка, попробуйте еще раз" -msgstr "An error has occurred, please try again" - -#: handlers/users/filters.py:124 -msgid "Вы перешли в меню настроек фильтров для мероприятий" -msgstr "You have entered the event filters settings menu" - -#: handlers/users/filters.py:132 -msgid "Напишите город, в котором бы хотели сходить куда-нибудь" -msgstr "Write the city where you would like to go somewhere" - -#: handlers/users/registration.py:42 -msgid "Пройдите опрос, чтобы зарегистрироваться" -msgstr "Take the survey to register" - -#: handlers/users/registration.py:52 -msgid "" -"Вы уже зарегистрированы, если вам нужно изменить анкету, то нажмите на " -"кнопку ниже" -msgstr "" - -#: handlers/users/registration.py:66 -msgid "Выберите пол" -msgstr "Choose a gender" - -#: handlers/users/registration.py:88 -msgid "Теперь расскажите о себе:\n" -msgstr "Now tell us about yourself:\n" - -#: handlers/users/registration.py:105 -msgid "Комментарий принят! Выберите, кого вы хотите найти: " -msgstr "Comment accepted! Choose who you want to find: " - -#: handlers/users/registration.py:111 -msgid "" -"Произошла неизвестная ошибка! Попробуйте изменить комментарий позже в " -"разделе \"Меню\"\n" -"\n" -"Выберите, кого вы хотите найти: " -msgstr "" -"An unknown error occurred! Try changing the comment later in the \"Menu\"" -" section\n" -"\n" -"Choose who you want to find: " - -#: handlers/users/registration.py:125 -msgid "Отлично! Теперь напишите мне ваше имя, которое будут все видеть в анкете" -msgstr "" -"Great! Now write me your name, which everyone will see in the " -"questionnaire" - -#: handlers/users/registration.py:145 -msgid "Введите сколько вам лет:" -msgstr "Input your age:" - -#: handlers/users/registration.py:170 -msgid "Вы ввели не число" -msgstr "You didn't input a number" - -#: handlers/users/registration.py:175 -msgid "Нажмите на кнопку ниже, чтобы определить ваш местоположение!" -msgstr "Press the button below to determine your location!" - -#: handlers/users/registration.py:188 -#, fuzzy -msgid "Мы не смогли найти такой город, попробуйте еще раз" -msgstr "We couldn't find the city {city}. Please try again" - -#: handlers/users/registration.py:211 handlers/users/registration.py:224 -msgid "" -"И напоследок, Пришлите мне вашу фотографию (отправлять надо сжатое " -"изображение, а не как документ)" -msgstr "" -"And finally, send me your photo (send a compressed image, not as a " -"document)" - -#: handlers/users/sponsor.py:10 -msgid "" -"Наш проект работает на Open Source и мы будем рады,если вы нам " -"поможете развивать проект.\n" -"\n" -"С помощью кнопки 💰 Донат вы можете отправить своё пожертвование" -msgstr "" -"Our project works on Open Source and we will be glad if you help " -"us develop the project.\n" -"\n" -"Using the button 💰 Donat you can send your donation" - -#: handlers/users/start_handler.py:27 -msgid "Вам необходимо зарегистрировать агента(ов) тех поддержки" -msgstr "You need to register support agent(s)" - -#: handlers/users/start_handler.py:36 -msgid "Вас нет в базе данной" -msgstr "You are not in the database" - -#: handlers/users/start_handler.py:42 handlers/users/start_handler.py:47 -msgid "Выберите язык" -msgstr "Select language" - -#: handlers/users/start_handler.py:57 -msgid "Язык был успешно изменен. Введите команду /start" -msgstr "The language has been successfully changed. Enter the /start command" - -#: handlers/users/start_handler.py:61 -msgid "Произошла какая-то ошибка. Введите команду /start и попробуйте еще раз" -msgstr "An error has occurred, please try again" - -#: handlers/users/support_handler.py:20 -msgid "Хотите связаться с тех поддержкой? Нажмите на кнопку ниже!" -msgstr "Do you want to contact technical support? Click on the button below!" - -#: handlers/users/support_handler.py:25 handlers/users/support_handler.py:52 -msgid "К сожалению, сейчас нет свободных операторов. Попробуйте позже." -msgstr "Unfortunately, there are no free operators right now. Try again later." - -#: handlers/users/support_handler.py:41 -msgid "Вы обратились в техническую поддержку. Ждем ответа от оператора!" -msgstr "" -"You have contacted technical support. We are waiting for a response from " -"the operator!" - -#: handlers/users/support_handler.py:64 -msgid "С вами хочет связаться пользователь {full_name}" -msgstr "The user {full_name} wants to contact you" - -#: handlers/users/support_handler.py:79 -msgid "К сожалению, пользователь уже передумал." -msgstr "Unfortunately, the user has already changed his mind." - -#: handlers/users/support_handler.py:91 -msgid "" -"Вы на связи с пользователем!\n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" -"You are in touch with the user!\n" -"To end the communication, click on the button." - -#: handlers/users/support_handler.py:100 -msgid "" -"Техподдержка на связи! Можете писать сюда свое сообщение. \n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" -"Technical support is in touch! You can write your message here. \n" -"To end the communication, click on the button." - -#: handlers/users/support_handler.py:115 -msgid "Дождитесь ответа оператора или отмените сеанс" -msgstr "Wait for the operator's response or cancel the session" - -#: handlers/users/support_handler.py:131 -msgid "Пользователь завершил сеанс техподдержки" -msgstr "The user has completed the technical support session" - -#: handlers/users/support_handler.py:135 -msgid "Вы завершили сеанс и были возвращены в главное меню" -msgstr "You have completed the session and have been returned to the main menu" - -#: handlers/users/user_profile.py:23 -msgid "" -"Ваша анкета удалена!\n" -"Я надеюсь вы кого-нибудь нашли" -msgstr "" -"Your profile has been deleted!\n" -"I hope you found someone" - -#: handlers/users/verification.py:17 -msgid "Чтобы пройти верификацию вам нужно отправить свой контакт" -msgstr "To pass verification, you need to send your contact" - -#: handlers/users/verification.py:33 -msgid "" -"Спасибо, {contact_full_name}.\n" -"Ваш номер {contact_phone_number} был получен." -msgstr "" -"Thank you, {contact_full_name}.\n" -"Your {contact_phone_number} number has been received." - -#: handlers/users/verification.py:50 -msgid "Ваш номер недействителен, попробуйте еще раз." -msgstr "Your number is invalid, try again." - -#: handlers/users/view_event.py:27 -msgid "На данный момент вы просмотрели все существующие анкеты" -msgstr "At the moment, you have viewed all existing profiles" - -#: handlers/users/view_ques.py:73 -#, fuzzy -msgid "Жалоба успешно отправлена" -msgstr "Your account has been successfully added" - -#: handlers/users/view_ques.py:112 -msgid "" -"Слишком много ❤️ за сегодня.\n" -"\n" -"Пригласи друзей и получи больше ❤️\n" -"\n" -"https://t.me/{}?start={}" -msgstr "" -"Too many ❤️ for today.\n" -"\n" -"Invite friends and get more ❤️\n" -"\n" -"https://t.me/{}?start={}" - -#: keyboards/admin/inline/customers.py:12 -#, fuzzy -msgid "🔍 Найти пользователя" -msgstr "Respond user" - -#: keyboards/admin/inline/customers.py:23 -#, fuzzy -msgid "🟢 Разблокировать" -msgstr "Unblock" - -#: keyboards/admin/inline/customers.py:28 -#, fuzzy -msgid "🚫 Заблокировать" -msgstr "Unblock" - -#: keyboards/admin/inline/mailing.py:8 -#, fuzzy -msgid "📧 Рассылка" -msgstr "Mailing" - -#: keyboards/admin/inline/mailing.py:16 keyboards/admin/inline/mailing.py:31 -#: keyboards/inline/admin_inline.py:9 -msgid "Подтвердить отправку" -msgstr "Confirm sending" - -#: keyboards/admin/inline/mailing.py:19 -msgid "Добавить кнопку" -msgstr "Add button" - -#: keyboards/admin/inline/mailing.py:21 keyboards/admin/inline/mailing.py:33 -#: keyboards/inline/cancel_inline.py:8 -msgid "Отмена" -msgstr "Cancel" - -#: keyboards/admin/inline/payments.py:9 -msgid "⚙️ Настройки" -msgstr "" - -#: keyboards/admin/inline/payments.py:11 -msgid "📝 Статистика" -msgstr "" - -#: keyboards/admin/inline/ref.py:8 -msgid "📈 Статистика" -msgstr "" - -#: keyboards/admin/inline/ref.py:9 keyboards/admin/inline/setting.py:8 -#, fuzzy -msgid "*️⃣ Добавить" -msgstr "🗑️ Delete" - -#: keyboards/admin/inline/ref.py:10 keyboards/admin/inline/setting.py:9 -#, fuzzy -msgid "❌ Удалить" -msgstr "🗑️ Delete" - -#: keyboards/admin/inline/ref.py:11 keyboards/admin/inline/setting.py:10 -#, fuzzy -msgid "◀️ Назад" -msgstr "⏪️ Back" - -#: keyboards/admin/inline/reply_menu.py:9 -#, fuzzy -msgid "🙅🏻‍♂️ Отменить" -msgstr "🖊️ Modify" - -#: keyboards/admin/inline/reply_menu.py:17 -msgid "👮‍♂️ Админ Состав" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:19 -#, fuzzy -msgid "📞 Сменить контакты" -msgstr "📞 Contacts" - -#: keyboards/admin/inline/reply_menu.py:29 -msgid "🗒 Выгрузить юзеров | .txt" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:32 -msgid "🗒 Выгрузить конфиги и логи" -msgstr "" - -#: keyboards/default/admin_default.py:8 -msgid "Рассылка" -msgstr "Mailing" - -#: keyboards/default/admin_default.py:9 -msgid "Сообщение по id" -msgstr "Message by id" - -#: keyboards/default/admin_default.py:10 -msgid "Посчитать людей и чаты" -msgstr "Count people and chats" - -#: keyboards/default/admin_default.py:11 -msgid "Мониторинг" -msgstr "Monitoring" - -#: keyboards/default/admin_default.py:12 -msgid "Тех.Работа" -msgstr "Tech.Works" - -#: keyboards/default/get_contact_default.py:8 -msgid "📱 Отправить" -msgstr "📱 Send" - -#: keyboards/default/get_location_default.py:9 -msgid "🗺 Определить автоматически" -msgstr "🗺 Detect automatically" - -#: keyboards/default/get_photo.py:8 -msgid "Взять из профиля" -msgstr "" - -#: keyboards/inline/admin_inline.py:18 -msgid "Включить" -msgstr "Enable" - -#: keyboards/inline/admin_inline.py:21 -msgid "Выключить" -msgstr "Disable" - -#: keyboards/inline/admin_inline.py:33 -msgid "Разблокировать" -msgstr "Unblock" - -#: keyboards/inline/back_inline.py:8 -#: keyboards/inline/change_data_profile_inline.py:15 -#: keyboards/inline/filters_inline.py:42 keyboards/inline/language_inline.py:22 -#: keyboards/inline/poster_inline.py:31 keyboards/inline/poster_inline.py:49 -#: keyboards/inline/poster_inline.py:63 -#: keyboards/inline/registration_inline.py:12 -#: keyboards/inline/settings_menu.py:12 keyboards/inline/sponsor_inline.py:10 -#: keyboards/inline/sponsor_inline.py:21 -msgid "⏪️ Вернуться в меню" -msgstr "⏪ Return to the menu" - -#: keyboards/inline/cancel_inline.py:16 -#: keyboards/inline/change_data_profile_inline.py:28 -#, fuzzy -msgid "❌ Остановить" -msgstr "⏪️ Stop" - -#: keyboards/inline/change_data_profile_inline.py:8 -msgid "👤 Имя" -msgstr "👤 Name" - -#: keyboards/inline/change_data_profile_inline.py:9 -msgid "⚧ Пол" -msgstr "⚧ Gender" - -#: keyboards/inline/change_data_profile_inline.py:10 -msgid "📅 Возраст" -msgstr "📅 Age" - -#: keyboards/inline/change_data_profile_inline.py:11 -msgid "🏙 Город" -msgstr "🏙 City" - -#: keyboards/inline/change_data_profile_inline.py:12 -msgid "📷 Фото" -msgstr "📷 Photo" - -#: keyboards/inline/change_data_profile_inline.py:13 -msgid "📝 О себе" -msgstr "📝 About me" - -#: keyboards/inline/filters_inline.py:9 -msgid "🎉 Мероприятия" -msgstr "🎉 Events" - -#: keyboards/inline/filters_inline.py:12 -msgid "❤️ Знакомства" -msgstr "❤️ Dating" - -#: keyboards/inline/filters_inline.py:14 keyboards/inline/filters_inline.py:31 -#: keyboards/inline/guide_inline.py:15 -msgid "⏪️ Назад" -msgstr "⏪️ Back" - -#: keyboards/inline/filters_inline.py:23 -msgid "🏙️ Город партнера" -msgstr "🏙️ Partner City" - -#: keyboards/inline/filters_inline.py:26 -msgid "🔞 Возр.диапазон" -msgstr "🔞 Age range" - -#: keyboards/inline/filters_inline.py:29 -msgid "🚻 Пол партнера" -msgstr "🚻 Partner's gender" - -#: keyboards/inline/filters_inline.py:40 -msgid "🏙️ Город" -msgstr "🏙️ City" - -#: keyboards/inline/guide_inline.py:21 -msgid "Вперед ➡️" -msgstr "Forward ➡️" - -#: keyboards/inline/guide_inline.py:25 -msgid "❌ Закрыть" -msgstr "❌ Close" - -#: keyboards/inline/language_inline.py:13 -msgid "🇷🇺 Русский" -msgstr "🇷🇺 Russian" - -#: keyboards/inline/language_inline.py:14 -msgid "🇩🇪 Немецкий" -msgstr "🇩🇪 Deutsche" - -#: keyboards/inline/language_inline.py:15 -msgid "🇬🇧 Английский" -msgstr "🇬🇧 English" - -#: keyboards/inline/language_inline.py:16 -msgid "🇮🇩 Индонезийский" -msgstr "🇮🇩 Indonesian" - -#: keyboards/inline/main_menu_inline.py:26 -msgid "➕ Регистрация" -msgstr "➕ Registration" - -#: keyboards/inline/main_menu_inline.py:28 keyboards/inline/settings_menu.py:10 -msgid "🌐 Язык" -msgstr "🌐 Language" - -#: keyboards/inline/main_menu_inline.py:30 -msgid "👤 Моя анекта" -msgstr "👤 My profile" - -#: keyboards/inline/main_menu_inline.py:32 -msgid "⚙️ Фильтры" -msgstr "⚙️ Filters" - -#: keyboards/inline/main_menu_inline.py:33 -msgid "💌 Найти пару" -msgstr "💌 Find date" - -#: keyboards/inline/main_menu_inline.py:34 -msgid "🗓️ Афиша" -msgstr "🗓️ Events" - -#: keyboards/inline/main_menu_inline.py:35 -msgid "🆘 Поддержка" -msgstr "🆘 Support" - -#: keyboards/inline/main_menu_inline.py:37 -msgid "ℹ️ Информация" -msgstr "ℹ️ Information" - -#: keyboards/inline/menu_profile_inline.py:10 -msgid "✅ Верификация" -msgstr "✅ Verification" - -#: keyboards/inline/menu_profile_inline.py:16 -msgid "🖊️ Изменить" -msgstr "🖊️ Modify" - -#: keyboards/inline/menu_profile_inline.py:18 -msgid "🗑️ Удалить" -msgstr "🗑️ Delete" - -#: keyboards/inline/menu_profile_inline.py:19 -msgid "⏪ Назад" -msgstr "⏪ Back" - -#: keyboards/inline/payments_inline.py:9 -msgid "💳 ЮMoney" -msgstr "💳 Yoomoney" - -#: keyboards/inline/payments_inline.py:16 -msgid "💳 Оплатить" -msgstr "💳 Pay" - -#: keyboards/inline/payments_inline.py:18 -msgid "🔄 Проверить оплату" -msgstr "🔄 Check payment" - -#: keyboards/inline/poster_inline.py:21 -msgid "✍️Создать афишу" -msgstr "✍️ Create event" - -#: keyboards/inline/poster_inline.py:24 -msgid "🎭 Смотреть афиши" -msgstr "🚀 View posters" - -#: keyboards/inline/poster_inline.py:27 -msgid "📝 Мои записи" -msgstr "📝 My records" - -#: keyboards/inline/poster_inline.py:29 -msgid "📃 Моё событие" -msgstr "📃 My event" - -#: keyboards/inline/poster_inline.py:46 -msgid "✍️ Изменить" -msgstr "✍️ Edit" - -#: keyboards/inline/poster_inline.py:58 -msgid "Название" -msgstr "Title" - -#: keyboards/inline/poster_inline.py:60 -msgid "Описание" -msgstr "Description" - -#: keyboards/inline/poster_inline.py:73 -msgid "✅ Одобрить" -msgstr "✅ Approve" - -#: keyboards/inline/poster_inline.py:76 -msgid "❌ Отклонить" -msgstr "❌ Reject" - -#: keyboards/inline/poster_inline.py:85 -msgid "Пойду!" -msgstr "I'm going!" - -#: keyboards/inline/poster_inline.py:88 -msgid "Не интересно" -msgstr "Not interested" - -#: keyboards/inline/poster_inline.py:91 keyboards/inline/poster_inline.py:103 -#: keyboards/inline/poster_inline.py:112 -msgid "⏪️ Остановить" -msgstr "⏪️ Stop" - -#: keyboards/inline/poster_inline.py:101 -msgid "❌ Отменить запись" -msgstr "❌ Cancel Reservation" - -#: keyboards/inline/questionnaires_inline.py:30 -#, fuzzy -msgid "💤 Остановить" -msgstr "⏪️ Stop" - -#: keyboards/inline/questionnaires_inline.py:34 -msgid "🚫 Забанить" -msgstr "🚫 Ban" - -#: keyboards/inline/questionnaires_inline.py:38 -msgid "Следующий" -msgstr "Next" - -#: keyboards/inline/questionnaires_inline.py:74 -msgid "🚀 Смотреть" -msgstr "🚀 View form" - -#: keyboards/inline/questionnaires_inline.py:82 -msgid "👉 Перейти в чат" -msgstr "👉 Go to Chat" - -#: keyboards/inline/questionnaires_inline.py:89 -msgid "⏪️ Вернуться к просмотру анкет" -msgstr "⏪️ Back to view forms" - -#: keyboards/inline/registration_inline.py:9 -msgid "🖌️ Пройти опрос в боте" -msgstr "🖌️ Pass a survey in a bot" - -#: keyboards/inline/registration_inline.py:21 -msgid "✅ Да все хорошо!" -msgstr "✅ All are good!" - -#: keyboards/inline/settings_menu.py:8 -msgid "📚 Брендбук" -msgstr "📚 Brand-book" - -#: keyboards/inline/settings_menu.py:9 -msgid "📞 Контакты" -msgstr "📞 Contacts" - -#: keyboards/inline/support_inline.py:37 -msgid "Ответить пользователю" -msgstr "Respond user" - -#: keyboards/inline/support_inline.py:45 -msgid "Написать оператору" -msgstr "Write operator" - -#: keyboards/inline/support_inline.py:60 keyboards/inline/support_inline.py:72 -msgid "Завершить сеанс" -msgstr "End session" - -#: middlewares/BanCheck.py:44 -msgid "😢 Вы заблокированы!" -msgstr "😢 You are banned!" - -#: middlewares/IsMaintenanceCheck.py:26 -msgid "Ведутся технические работы" -msgstr "Technical works are in progress" - -#: middlewares/LinkCheck.py:38 -msgid "" -"Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " -"подпишитесь! Ссылки ниже: " -msgstr "" -"You haven't subscribed to all channels! To continue using the bot, please" -" subscribe! Links below: " - -#: utils/notify_admins.py:24 -msgid "Оповещение администрации..." -msgstr "" - -#: utils/notify_admins.py:28 -msgid "Бот был успешно запущен" -msgstr "" - -#: utils/statistics.py:16 -msgid "" -"📊 Статистика: \n" -"\n" -"└Сейчас в нашем боте {count_users} пользователей\n" -"└Из них:\n" -" ├{users_gender_m} пользователей мужского пола\n" -" ├{users_gender_f} пользователей женского пола\n" -" ├{users_city} пользователей из города {user_city}\n" -" ├{cs_uy} пользователей из других городов\n" -" ├{users_verified} верифицированных пользователей\n" -" ├{users_status} пользователей, создавшие анкету\n" -"└Дата создания бота - 10.08.2021" -msgstr "" -"📊 Statistics: \n" -"\n" -"└There are currently {count_users} users in our bot\n" -"└Among them:\n" -" ├{users_gender_m} male users\n" -" ├{users_gender_f} female users\n" -" ├{users_city} users from {user_city}\n" -" ├{cs_uy} users from other cities\n" -" ├{users_verified} verified users\n" -" ├{users_status} users who created a profile\n" -"└Bot creation date - August 10, 2021" - -#~ msgid "" -#~ "Вы попали в раздел настроек бота," -#~ " здесь вы можете посмотреть: " -#~ "статистику,изменить язык, отключить уведомления, " -#~ "а также узнать информацию.\n" -#~ "\n" -#~ "🌐 Дней работаем: {}\n" -#~ "👤 Всего пользователей: {}\n" -#~ msgstr "" -#~ "You are in the settings section" -#~ " of the bot, where you can " -#~ "view: statistics, change the language, " -#~ "disable notifications, and also find " -#~ "information.\n" -#~ "\n" -#~ "🌐 Days of operation: {}\n" -#~ "👤 Total users: {}\n" - -#~ msgid "🇫🇷 Французский" -#~ msgstr "" - -#~ msgid "🟢 Запустить бота" -#~ msgstr "" - -#~ msgid "⚒ Админ-Меню" -#~ msgstr "" - -#~ msgid "🗒 Логи" -#~ msgstr "" - -#~ msgid "Ваше новое имя: {censored}" -#~ msgstr "Your new name: {censored}" - -#~ msgid "Ваш новый пол: {}" -#~ msgstr "Your new gender: {}" - -#~ msgid "Отправьте мне новое описание анкеты:" -#~ msgstr "Send me a new description of the questionnaire:" - -#~ msgid "Комментарий принят!" -#~ msgstr "Comment accepted!" - -#~ msgid "🫂 Пользователи" -#~ msgstr "Respond user" - -#~ msgid "📊 Реклама" -#~ msgstr "" - -#~ msgid "👀 Мониторинг" -#~ msgstr "Monitoring" - -#~ msgid "🛑 Тех.Работа" -#~ msgstr "Tech.Works" - -#~ msgid "Чат с админом не найден" -#~ msgstr "" - diff --git a/locales/in/LC_MESSAGES/dating.po b/locales/in/LC_MESSAGES/dating.po deleted file mode 100644 index 28a4e83..0000000 --- a/locales/in/LC_MESSAGES/dating.po +++ /dev/null @@ -1,1408 +0,0 @@ -# Indonesian (Indonesia) translations for PROJECT. -# Copyright (C) 2023 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2023. -# -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-08-28 14:39+0300\n" -"PO-Revision-Date: 2023-08-21 16:49+0300\n" -"Last-Translator: \n" -"Language: id_ID\n" -"Language-Team: id_ID \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.1\n" - -#: functions/dating/reaction_strategies.py:46 -#: functions/dating/reaction_strategies.py:171 -msgid "На данный момент у нас нет подходящих анкет для вас" -msgstr "Saat ini kami tidak memiliki profil yang sesuai untuk Anda" - -#: functions/dating/reaction_strategies.py:52 -msgid "У вас достигнут лимит на просмотры анкет" -msgstr "Anda telah mencapai batas melihat profil" - -#: functions/dating/reaction_strategies.py:61 -msgid "Кому-то понравилась твоя анкета" -msgstr "Ada seseorang yang menyukai profil Anda" - -#: functions/dating/reaction_strategies.py:103 handlers/users/view_event.py:51 -msgid "" -"Рад был помочь, {fullname}!\n" -"Надеюсь, ты нашел кого-то благодаря мне" -msgstr "" -"Saya senang bisa membantu, {fullname}!\n" -"Saya harap Anda menemukan seseorang berkat bantuan saya" - -#: functions/dating/reaction_strategies.py:126 -msgid "Отлично! Надеюсь вы хорошо проведете время ;) Начинай общаться 👉" -msgstr "Bagus! Saya harap Anda memiliki waktu yang baik ;) Mulailah berbicara 👉" - -#: functions/dating/reaction_strategies.py:187 -msgid "Выберите причину жалобы:" -msgstr "" - -#: functions/dating/reaction_strategies.py:204 -msgid "" -"Жалоба от пользователя: [@{username} | {tg_id}]" -"\n" -"\n" -"На пользователя: [{owner_id}]\n" -"Причина жалобы: {reason}\n" -"Количество жалоб на пользователя: {counter_of_report}" -msgstr "" - -#: functions/dating/send_form_func.py:25 -msgid "" -"{}, {} лет, {} {verification}\n" -"\n" -msgstr "" -"{}, {} tahun, {} {verification}\n" -"\n" - -#: functions/dating/send_form_func.py:28 -msgid "{commentary}" -msgstr "{commentary}" - -#: functions/dating/send_form_func.py:36 -msgid "Инстаграм - {instagram}\n" -msgstr "Instagram - {instagram}\n" - -#: functions/dating/send_form_func.py:48 -msgid "" -"{}\n" -"\n" -"{}" -msgstr "" -"{}\n" -"\n" -"{}" - -#: functions/dating/send_form_func.py:57 -msgid "" -"{}\n" -"\n" -"Инстаграм - {instagram}\n" -msgstr "" -"{}\n" -"\n" -"Instagram - {instagram}\n" - -#: functions/event/extra_features.py:86 -msgid "На данный момент у нас нет подходящих мероприятий для вас" -msgstr "Saat ini kami tidak memiliki acara yang cocok untuk Anda hadiri" - -#: functions/event/templates_messages.py:17 -msgid "" -"{} \n" -"Когда: {} \n" -"Где: {} \n" -"\n" -"{}" -msgstr "" -"{} \n" -"Ketika: {} \n" -"Di mana: {} \n" -"\n" -"{}" - -#: functions/main_app/app_scheduler.py:12 -msgid "Несколько {} из города {} хотят познакомиться с тобой прямо сейчас" -msgstr "Beberapa {} dari {} ingin bertemu dengan Anda sekarang" - -#: functions/main_app/auxiliary_tools.py:71 -msgid "" -"{name}, {age} лет, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Партнерка:\n" -"Количество приглашенных друзей: {reff}\n" -"Реферальная ссылка:\n" -" {link}" -msgstr "" -"{name}, {age} tahun, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Afiliasi:\n" -"Jumlah teman yang diundang: {reff}\n" -"Tautan referensi:\n" -" {link}" - -#: functions/main_app/auxiliary_tools.py:96 -msgid "" -"Фильтр по подбору партнеров:\n" -"\n" -"🚻 Необходимы пол партнера: {}\n" -"🔞 Возрастной диапазон: {}-{} лет\n" -"\n" -"🏙️ Город партнера: {}" -msgstr "" -"Filter pencarian pasangan:\n" -"\n" -"🚻 Jenis kelamin pasangan yang diinginkan: {}\n" -"🔞 Rentang usia: {}-{} tahun\n" -"\n" -"🏙️ Kota pasangan: {}" - -#: functions/main_app/auxiliary_tools.py:121 -msgid "" -"Приветствую вас, {fullname}!!\n" -"\n" -"{heart} QueDateBot - платформа для поиска новых знакомств.\n" -"\n" -"🪧 Новости о проекте вы можете прочитать в нашем канале - " -"https://t.me/QueDateGroup \n" -"\n" -"🤝 Сотрудничество: \n" -"Если у вас есть предложение о сотрудничестве, пишите агенту поддержки - " -"@{supports}\n" -"\n" -msgstr "" -"Selamat datang, {fullname}!!\n" -"\n" -"{heart} QueDateBot - platform untuk mencari teman baru.\n" -"\n" -"🪧 Anda dapat membaca berita tentang proyek kami di saluran kami - " -"https://t.me/QueDateGroup \n" -"\n" -"🤝 Kerjasama: \n" -"Jika Anda memiliki tawaran kerjasama, silakan hubungi agen dukungan - " -"@{supports}\n" -"\n" - -#: functions/main_app/auxiliary_tools.py:168 -msgid "" -"По вашей ссылке зарегистрировался пользователь {}!\n" -"Вы получаете дополнительных 15 ❤️" -msgstr "" -"Pengguna {} telah mendaftar melalui tautan Anda!\n" -"Anda mendapatkan 15 ❤️ tambahan" - -#: functions/main_app/auxiliary_tools.py:194 -msgid "" -"Регистрация успешно завершена! \n" -"\n" -" {}, {} лет, {}\n" -"\n" -"О себе - {}" -msgstr "" -"Registrasi berhasil! \n" -"\n" -" {}, {} tahun, {}\n" -"\n" -"Tentang Saya - {}" - -#: functions/main_app/auxiliary_tools.py:215 -#: functions/main_app/auxiliary_tools.py:281 -msgid "Фото принято!" -msgstr "Foto diterima!" - -#: functions/main_app/auxiliary_tools.py:219 -#: functions/main_app/auxiliary_tools.py:254 -#: functions/main_app/auxiliary_tools.py:290 -msgid "" -"Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" -"Если ошибка осталась, напишите агенту поддержки." -msgstr "" -"Terjadi kesalahan! Silakan coba lagi atau kirim foto lain. \n" -"Jika masalah masih berlanjut, hubungi agen dukungan." - -#: functions/main_app/auxiliary_tools.py:242 -msgid "" -"Во время проверки вашего фото мы обнаружили подозрительный контент!\n" -"Поэтому мы чуть-чуть подкорректировали вашу фотографию" -msgstr "" -"Selama pemeriksaan foto Anda, kami menemukan konten yang mencurigakan!\n" -"Kami melakukan sedikit perubahan pada foto Anda" - -#: functions/main_app/auxiliary_tools.py:262 -#, fuzzy -msgid "" -"Фото принято!\n" -"Выберите, что вы хотите изменить: " -msgstr "Pilih apa yang ingin Anda ubah: " - -#: functions/main_app/auxiliary_tools.py:285 -msgid "Выберите, что вы хотите изменить: " -msgstr "Pilih apa yang ingin Anda ubah: " - -#: functions/main_app/auxiliary_tools.py:336 -msgid "" -"Руководство по боту: \n" -"Страница №{}" -msgstr "" -"Panduan bot: \n" -"Halaman Nomor {}" - -#: functions/main_app/auxiliary_tools.py:352 -msgid "" -"Вы попали в раздел Информации бота, здесь вы можете посмотреть: " -"статистику,изменить язык, а также посмотреть наш брендбук.\n" -"\n" -"🌐 Дней работаем: {}\n" -"👤 Всего пользователей: {}\n" -msgstr "" -"Anda telah masuk ke bagian Informasi bot, di sini Anda dapat " -"melihat: statistik, mengubah bahasa, serta melihat brandbook kami.\n" -"\n" -"🌐 Hari bekerja: {}\n" -"👤 Jumlah pengguna: {}\n" - -#: functions/main_app/determin_location.py:32 -#, fuzzy -msgid "" -"Я нашел такой адрес:\n" -"{city}\n" -"Если все правильно, то подтвердите" -msgstr "" -"Saya menemukan alamat berikut:\n" -"{city}\n" -"Jika semuanya benar, silakan konfirmasi" - -#: handlers/echo_handler.py:13 -msgid "Эхо без состояния." -msgstr "" - -#: handlers/echo_handler.py:38 -msgid "Меню: " -msgstr "" - -#: handlers/admins/monitoring.py:14 -msgid "Чтобы начать мониторинг нажмите на кнопку ниже" -msgstr "" - -#: handlers/admins/monitoring.py:32 -msgid "Анкета пользователя была заблокирована" -msgstr "" - -#: handlers/admins/advert/advertisement.py:21 -msgid "" -"📧 Рассылка\n" -"Пришлите текст для рассылки либо фото с текстом для рассылки! Чтобы " -"отредактировать, используйте встроенный редактор телеграма!\n" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:34 -#: handlers/admins/advert/mailing/create.py:124 -#: handlers/admins/advert/mailing/create.py:267 -msgid "Начинаю рассылку!" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:49 -#: handlers/admins/advert/mailing/create.py:137 -#: handlers/admins/advert/mailing/create.py:192 -#: handlers/admins/advert/mailing/create.py:281 -msgid "" -"Сообщение не дошло в чат {chat} Причина: \n" -"{err}" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:55 -#: handlers/admins/advert/mailing/create.py:143 -#: handlers/admins/advert/mailing/create.py:198 -#: handlers/admins/advert/mailing/create.py:287 -msgid "Рассылка проведена успешно! Ее получили: {count} чатов!\n" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:61 -msgid "Пришлите мне название кнопки!" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:70 -msgid "Название кнопки принято! Теперь отправьте мне ссылку для этой кнопки!" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:90 -#: handlers/admins/advert/mailing/create.py:232 -msgid "" -"Вот так будет выглядеть сообщение: \n" -"\n" -"{text}\n" -"\n" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:97 -#: handlers/admins/advert/mailing/create.py:239 -msgid "Вы подтверждаете отправку?" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:104 -#: handlers/admins/advert/mailing/create.py:246 -msgid "" -"Произошла ошибка! Скорее всего, неправильно введена ссылка! Попробуйте " -"еще раз." -msgstr "" - -#: handlers/admins/advert/mailing/create.py:157 -msgid "" -"Вот сообщение: \n" -"\n" -"{text}\n" -"\n" -" Вы подтверждаете отправку? Или хотите что-то добавить?" -msgstr "" - -#: handlers/admins/settings/tech_works.py:26 -msgid "Чтобы включить/выключить технические работы, нажмите на кнопку ниже" -msgstr "" - -#: handlers/admins/settings/tech_works.py:36 -msgid "Технические работы включены" -msgstr "" - -#: handlers/admins/settings/tech_works.py:44 -msgid "Технические работы выключены" -msgstr "" - -#: handlers/groups/event_moderate.py:24 -msgid "Принято!" -msgstr "" - -#: handlers/groups/event_moderate.py:33 -msgid "Ваше мероприятие прошло модерацию" -msgstr "Acara Anda telah melewati moderasi" - -#: handlers/groups/event_moderate.py:40 -msgid "Отклонено!" -msgstr "Ditolak" - -#: handlers/groups/event_moderate.py:44 -msgid "К сожалению ваше мероприятие не прошло модерацию" -msgstr "Maaf, acara Anda tidak melewati moderasi" - -#: handlers/groups/start.py:12 -msgid "" -"Привет, я бот, проекта Que Group, для верификации анкет для " -"знакомств\n" -"\n" -msgstr "" -"Halo, saya adalah bot dari proyek Que Group, untuk verifikasi profil " -"untuk kencan\n" -"\n" - -#: handlers/users/back.py:43 -msgid "Вы забанены!" -msgstr "Anda diblokir!" - -#: handlers/users/back.py:50 -msgid "Вы вернулись в меню фильтров" -msgstr "Anda kembali ke menu filter" - -#: handlers/users/brandbook_handler.py:23 -msgid "" -"Руководство по боту: \n" -"Страница №1" -msgstr "" -"Panduan bot: \n" -"Halaman №1" - -#: handlers/users/buy_unban.py:17 -msgid "" -"💳 Сейчас вам нужно выбрать способ оплаты\n" -"\n" -"├Стоимость разблокировки - 99₽\n" -"├Оплата обычно приходить в течение 1-3 минут\n" -"├Если у вас нет Yoomoney или нет возможности\n" -"├оплатить, напишите агенту поддержки" -msgstr "" -"💳 Sekarang Anda perlu memilih metode pembayaran\n" -"\n" -"├ Biaya pembukaan blokir - 99₽\n" -"├ Pembayaran biasanya diterima dalam 1-3 menit\n" -"├ Jika Anda tidak memiliki Yoomoney atau tidak dapat\n" -"├ melakukan pembayaran, hubungi agen dukungan" - -#: handlers/users/buy_unban.py:38 -msgid "После оплаты нажмите 🔄 Проверить оплату" -msgstr "Setelah pembayaran, tekan 🔄 Periksa Pembayaran" - -#: handlers/users/buy_unban.py:57 -msgid "Поздравляем! Вы были разрабанены" -msgstr "Selamat! Anda telah dibebaskan dari larangan" - -#: handlers/users/buy_unban.py:65 -msgid "" -"Оплата не прошла! Подождите минут 10, а затем еще раз попробуйте нажать " -"кнопку ниже" -msgstr "" -"Pembayaran tidak berhasil! Tunggu selama 10 menit, lalu coba lagi dengan " -"menekan tombol di bawah ini" - -#: handlers/users/change_datas.py:35 -msgid "Ваши данные: \n" -msgstr "Data Anda: \n" - -#: handlers/users/change_datas.py:40 -msgid "Введите новое имя" -msgstr "Masukkan nama baru Anda" - -#: handlers/users/change_datas.py:53 -#, fuzzy -msgid "" -"Ваше новое имя: {censored}\n" -"Выберите, что вы хотите изменить: " -msgstr "" -"Data berhasil diubah.\n" -"Pilih apa yang ingin Anda ubah: " - -#: handlers/users/change_datas.py:63 -msgid "" -"Произошла неизвестная ошибка. Попробуйте ещё раз\n" -"Возможно, Ваше сообщение слишком большое" -msgstr "" -"Terjadi kesalahan yang tidak diketahui. Silakan coba lagi\n" -"Mungkin pesan Anda terlalu besar" - -#: handlers/users/change_datas.py:76 -msgid "Введите новый возраст" -msgstr "Masukkan usia baru Anda" - -#: handlers/users/change_datas.py:90 -#, fuzzy -msgid "" -"Ваш новый возраст: {messages}\n" -"Выберите, что вы хотите изменить: " -msgstr "Usia baru Anda: {messages}" - -#: handlers/users/change_datas.py:99 handlers/users/registration.py:163 -msgid "Вы ввели недопустимое число, попробуйте еще раз" -msgstr "Anda memasukkan angka yang tidak valid, coba lagi" - -#: handlers/users/change_datas.py:104 -msgid "Вы ввели не число, попробуйте еще раз" -msgstr "Anda tidak memasukkan angka, coba lagi" - -#: handlers/users/change_datas.py:112 -msgid "Введите новый город" -msgstr "Masukkan kota baru Anda" - -#: handlers/users/change_datas.py:124 -msgid "Мы не смогли найти город {city}. Попробуйте ещё раз" -msgstr "Kami tidak dapat menemukan kota {city}. Silakan coba lagi" - -#: handlers/users/change_datas.py:134 -msgid "" -"Данные успешно изменены.\n" -"Выберите, что вы хотите изменить: " -msgstr "" -"Data berhasil diubah.\n" -"Pilih apa yang ingin Anda ubah: " - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♂️ Мужской" -msgstr "👱🏻‍♂️ Pria" - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♀️ Женский" -msgstr "👱🏻‍♀️ Wanita" - -#: handlers/users/change_datas.py:145 -msgid "Выберите новый пол: " -msgstr "Pilih jenis kelamin baru Anda: " - -#: handlers/users/change_datas.py:155 -#, fuzzy -msgid "" -"Ваш новый пол: {}\n" -"Выберите, что вы хотите изменить: " -msgstr "Pilih apa yang ingin Anda ubah: " - -#: handlers/users/change_datas.py:167 -msgid "Отправьте мне новую фотографию" -msgstr "Kirimkan saya foto baru Anda" - -#: handlers/users/change_datas.py:191 handlers/users/registration.py:244 -msgid "Произошла ошибка, проверьте настройки конфиденциальности" -msgstr "Terjadi kesalahan, periksa pengaturan privasi Anda" - -#: handlers/users/change_datas.py:232 -#, fuzzy -msgid "Отправьте сообщение о себе" -msgstr "Kirimkan saya pesan suara" - -#: handlers/users/change_datas.py:247 -#, fuzzy -msgid "" -"Комментарий принят!\n" -"Выберите, что вы хотите изменить: " -msgstr "Komentar diterima! Pilih siapa yang ingin Anda temui:" - -#: handlers/users/change_datas.py:254 -msgid "" -"Произошла ошибка! Попробуйте еще раз изменить описание. Возможно, Ваше " -"сообщение слишком большое\n" -"Если ошибка осталась, напишите в поддержку." -msgstr "" -"Terjadi kesalahan! Coba lagi ubah deskripsi. Mungkin pesan Anda terlalu " -"besar\n" -"Jika masalah masih berlanjut, hubungi dukungan." - -#: handlers/users/change_datas.py:267 -msgid "" -"Напишите имя своего аккаунта\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" -"Tuliskan nama akun Anda\n" -"\n" -"Contoh:\n" -"@unknown\n" -"https://www.instagram.com/unknown" - -#: handlers/users/change_datas.py:289 -msgid "Ваш аккаунт успешно добавлен" -msgstr "Akun Anda berhasil ditambahkan" - -#: handlers/users/change_datas.py:293 handlers/users/verification.py:45 -msgid "Вы были возвращены в меню" -msgstr "Anda telah kembali ke menu" - -#: handlers/users/change_datas.py:297 -msgid "" -"Вы ввели неправильную ссылку или имя аккаунта.\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" -"Anda memasukkan tautan atau nama akun yang salah.\n" -"\n" -"Contoh:\n" -"@unknown\n" -"https://www.instagram.com/unknown" - -#: handlers/users/change_datas.py:304 -msgid "Возникла ошибка. Попробуйте еще раз" -msgstr "Terjadi kesalahan. Silakan coba lagi" - -#: handlers/users/change_event_datas.py:16 -msgid "Вы перешли в меню изменения данных мероприятия" -msgstr "Anda telah pindah ke menu pengubahan data acara" - -#: handlers/users/change_event_datas.py:23 -msgid "Напишите новое название вашего мероприятия" -msgstr "Tulis nama baru untuk acara Anda" - -#: handlers/users/change_event_datas.py:35 -#: handlers/users/change_event_datas.py:53 -msgid "Данные изменены" -msgstr "Data diubah" - -#: handlers/users/change_event_datas.py:41 -msgid "Напишите новое описание вашего мероприятия" -msgstr "Tulis deskripsi baru untuk acara Anda" - -#: handlers/users/event_handler.py:31 -msgid "Вы перешли в меню афиш" -msgstr "Anda telah pindah ke menu acara" - -#: handlers/users/event_handler.py:48 -msgid "Введите название мероприятие" -msgstr "Masukkan nama acara" - -#: handlers/users/event_handler.py:55 -msgid "" -"Вы уже создали мероприятие, которое проходит модерацию. Дождитесь " -"проверки, пожалуйста" -msgstr "" -"Anda telah membuat acara yang sedang dalam moderasi. Mohon tunggu hingga " -"diverifikasi" - -#: handlers/users/event_handler.py:63 -msgid "" -"Прочитайте сообщение и не нажимайте на кнопку, пока ваше мероприятие не " -"пройдет модерацию" -msgstr "Baca pesan dan jangan tekan tombol sampai acara Anda lulus moderasi" - -#: handlers/users/event_handler.py:78 -msgid "Пожалуйста, выберите дату: " -msgstr "Silakan pilih tanggal: " - -#: handlers/users/event_handler.py:83 -msgid "" -"Длинна вашего сообщение превышает допустимую.\n" -"Попробуйте ещё раз" -msgstr "" -"Panjang pesan Anda melebihi batas yang diizinkan.\n" -"Silakan coba lagi" - -#: handlers/users/event_handler.py:99 -msgid "Вы не можете проводить мероприятие в прошлом" -msgstr "Anda tidak dapat mengadakan acara di masa lalu" - -#: handlers/users/event_handler.py:105 -msgid "Теперь напишите место проведения" -msgstr "Sekarang tuliskan tempat acara" - -#: handlers/users/event_handler.py:126 -msgid "Вы ввели слишком длинное название городаПопробуйте ещё раз." -msgstr "Anda telah memasukkan nama kota yang terlalu panjang. Coba lagi." - -#: handlers/users/event_handler.py:132 -msgid "" -"Произошла неизвестная ошибка! Попробуйте еще раз.\n" -"Вероятнее всего вы ввели город неправильно" -msgstr "" -"Terjadi kesalahan yang tidak diketahui! Coba lagi.\n" -"Kemungkinan besar Anda telah memasukkan nama kota dengan salah" - -#: handlers/users/event_handler.py:142 -msgid "Хорошо, теперь напишите короткое или длинное описание вашего мероприятия" -msgstr "" -"Baiklah, sekarang tuliskan deskripsi pendek atau panjang tentang acara " -"Anda" - -#: handlers/users/event_handler.py:158 -msgid "И напоследок, пришлите постер вашего мероприятия" -msgstr "Dan terakhir, kirimkan poster acara Anda" - -#: handlers/users/event_handler.py:164 -msgid "Ваше сообщение слишком длинное.Попробуйте написать короче" -msgstr "Pesan Anda terlalu panjang. Coba tulis lebih singkat" - -#: handlers/users/event_handler.py:178 -msgid "Фото принято" -msgstr "Foto diterima" - -#: handlers/users/event_handler.py:200 -msgid "Ваше мероприятие отправлено на модерацию" -msgstr "Acara Anda dikirimkan untuk moderasi" - -#: handlers/users/event_list.py:23 -msgid "На данный момент вы никуда не записались" -msgstr "Saat ini Anda belum mendaftar ke mana-mana" - -#: handlers/users/filters.py:23 handlers/users/filters.py:29 -msgid "Вы перешли в раздел с фильтрами" -msgstr "Anda telah beralih ke bagian filter" - -#: handlers/users/filters.py:41 -msgid "Напишите минимальный возраст" -msgstr "Tulis usia minimum" - -#: handlers/users/filters.py:53 -msgid "Теперь введите максимальный возраст" -msgstr "Sekarang masukkan usia maksimum" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♂️ Парня" -msgstr "👱🏻‍♂️ Laki-laki" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♀️ Девушку" -msgstr "👱🏻‍♀️ Perempuan" - -#: handlers/users/filters.py:76 -msgid "Выберите, кого вы хотите найти:" -msgstr "Pilih siapa yang ingin Anda temui:" - -#: handlers/users/filters.py:84 handlers/users/filters.py:111 -msgid "Данные сохранены" -msgstr "Data disimpan" - -#: handlers/users/filters.py:92 -msgid "Напишите город вашего будущего партнера" -msgstr "Tuliskan kota calon pasangan Anda" - -#: handlers/users/filters.py:103 handlers/users/filters.py:144 -msgid "Произошла ошибка, попробуйте еще раз" -msgstr "Terjadi kesalahan, coba lagi" - -#: handlers/users/filters.py:124 -msgid "Вы перешли в меню настроек фильтров для мероприятий" -msgstr "Anda telah beralih ke menu pengaturan filter untuk acara" - -#: handlers/users/filters.py:132 -msgid "Напишите город, в котором бы хотели сходить куда-нибудь" -msgstr "Tuliskan kota tempat Anda ingin pergi" - -#: handlers/users/registration.py:42 -msgid "Пройдите опрос, чтобы зарегистрироваться" -msgstr "Lakukan survei untuk mendaftar" - -#: handlers/users/registration.py:52 -msgid "" -"Вы уже зарегистрированы, если вам нужно изменить анкету, то нажмите на " -"кнопку ниже" -msgstr "" - -#: handlers/users/registration.py:66 -msgid "Выберите пол" -msgstr "Pilih jenis kelamin" - -#: handlers/users/registration.py:88 -msgid "Теперь расскажите о себе:\n" -msgstr "Sekarang ceritakan tentang diri Anda:\n" - -#: handlers/users/registration.py:105 -msgid "Комментарий принят! Выберите, кого вы хотите найти: " -msgstr "Komentar diterima! Pilih siapa yang ingin Anda temui:" - -#: handlers/users/registration.py:111 -msgid "" -"Произошла неизвестная ошибка! Попробуйте изменить комментарий позже в " -"разделе \"Меню\"\n" -"\n" -"Выберите, кого вы хотите найти: " -msgstr "" -"Terjadi kesalahan yang tidak diketahui! Coba ubah komentar Anda nanti di " -"bagian \"Menu\"\n" -"\n" -"Pilih siapa yang ingin Anda temukan: " - -#: handlers/users/registration.py:125 -msgid "Отлично! Теперь напишите мне ваше имя, которое будут все видеть в анкете" -msgstr "" -"Baiklah! Sekarang tuliskan nama Anda yang akan dilihat semua orang di " -"profil" - -#: handlers/users/registration.py:145 -msgid "Введите сколько вам лет:" -msgstr "Tuliskan berapa usia Anda:" - -#: handlers/users/registration.py:170 -msgid "Вы ввели не число" -msgstr "Anda telah memasukkan bukan angka" - -#: handlers/users/registration.py:175 -msgid "Нажмите на кнопку ниже, чтобы определить ваш местоположение!" -msgstr "Klik tombol di bawah ini untuk menentukan lokasi Anda!" - -#: handlers/users/registration.py:188 -#, fuzzy -msgid "Мы не смогли найти такой город, попробуйте еще раз" -msgstr "Kami tidak dapat menemukan kota {city}. Silakan coba lagi" - -#: handlers/users/registration.py:211 handlers/users/registration.py:224 -msgid "" -"И напоследок, Пришлите мне вашу фотографию (отправлять надо сжатое " -"изображение, а не как документ)" -msgstr "Terakhir, Kirimkan foto Anda (kirim gambar yang dikompresi, bukan dokumen)" - -#: handlers/users/sponsor.py:10 -msgid "" -"Наш проект работает на Open Source и мы будем рады,если вы нам " -"поможете развивать проект.\n" -"\n" -"С помощью кнопки 💰 Донат вы можете отправить своё пожертвование" -msgstr "" - -#: handlers/users/start_handler.py:27 -msgid "Вам необходимо зарегистрировать агента(ов) тех поддержки" -msgstr "Anda perlu mendaftarkan agen pendukung" - -#: handlers/users/start_handler.py:36 -msgid "Вас нет в базе данной" -msgstr "Anda tidak ada dalam database ini" - -#: handlers/users/start_handler.py:42 handlers/users/start_handler.py:47 -msgid "Выберите язык" -msgstr "Pilih bahasa" - -#: handlers/users/start_handler.py:57 -msgid "Язык был успешно изменен. Введите команду /start" -msgstr "Bahasa berhasil diubah. Masukkan perintah /start" - -#: handlers/users/start_handler.py:61 -msgid "Произошла какая-то ошибка. Введите команду /start и попробуйте еще раз" -msgstr "Terjadi kesalahan. Masukkan perintah /start dan coba lagi" - -#: handlers/users/support_handler.py:20 -msgid "Хотите связаться с тех поддержкой? Нажмите на кнопку ниже!" -msgstr "Ingin menghubungi dukungan? Klik tombol di bawah ini!" - -#: handlers/users/support_handler.py:25 handlers/users/support_handler.py:52 -msgid "К сожалению, сейчас нет свободных операторов. Попробуйте позже." -msgstr "Sayangnya, saat ini tidak ada operator yang tersedia. Coba lagi nanti." - -#: handlers/users/support_handler.py:41 -msgid "Вы обратились в техническую поддержку. Ждем ответа от оператора!" -msgstr "" -"Anda telah menghubungi dukungan teknis. Kami menunggu jawaban dari " -"operator!" - -#: handlers/users/support_handler.py:64 -msgid "С вами хочет связаться пользователь {full_name}" -msgstr "Anda menerima pesan dari pengguna {full_name}" - -#: handlers/users/support_handler.py:79 -msgid "К сожалению, пользователь уже передумал." -msgstr "Sayangnya, pengguna sudah berubah pikiran." - -#: handlers/users/support_handler.py:91 -msgid "" -"Вы на связи с пользователем!\n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" -"Anda terhubung dengan pengguna!\n" -"Untuk mengakhiri sesi, klik tombol." - -#: handlers/users/support_handler.py:100 -msgid "" -"Техподдержка на связи! Можете писать сюда свое сообщение. \n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" -"Dukungan teknis sedang online! Anda dapat mengirim pesan di sini. \n" -"Untuk mengakhiri sesi, klik tombol." - -#: handlers/users/support_handler.py:115 -msgid "Дождитесь ответа оператора или отмените сеанс" -msgstr "Tunggu jawaban operator atau batalkan sesi" - -#: handlers/users/support_handler.py:131 -msgid "Пользователь завершил сеанс техподдержки" -msgstr "Pengguna telah mengakhiri sesi dukungan" - -#: handlers/users/support_handler.py:135 -msgid "Вы завершили сеанс и были возвращены в главное меню" -msgstr "Anda telah mengakhiri sesi dan kembali ke menu utama" - -#: handlers/users/user_profile.py:23 -msgid "" -"Ваша анкета удалена!\n" -"Я надеюсь вы кого-нибудь нашли" -msgstr "" -"Profil Anda dihapus!\n" -"Kami harap Anda menemukan seseorang" - -#: handlers/users/verification.py:17 -msgid "Чтобы пройти верификацию вам нужно отправить свой контакт" -msgstr "Untuk melakukan verifikasi, Anda perlu mengirimkan kontak Anda" - -#: handlers/users/verification.py:33 -msgid "" -"Спасибо, {contact_full_name}.\n" -"Ваш номер {contact_phone_number} был получен." -msgstr "" -"Terima kasih, {contact_full_name}.\n" -"Nomor Anda {contact_phone_number} telah diterima." - -#: handlers/users/verification.py:50 -msgid "Ваш номер недействителен, попробуйте еще раз." -msgstr "Nomor Anda tidak valid, coba lagi." - -#: handlers/users/view_event.py:27 -msgid "На данный момент вы просмотрели все существующие анкеты" -msgstr "Saat ini Anda telah melihat semua profil yang tersedia" - -#: handlers/users/view_ques.py:73 -#, fuzzy -msgid "Жалоба успешно отправлена" -msgstr "Akun Anda berhasil ditambahkan" - -#: handlers/users/view_ques.py:112 -msgid "" -"Слишком много ❤️ за сегодня.\n" -"\n" -"Пригласи друзей и получи больше ❤️\n" -"\n" -"https://t.me/{}?start={}" -msgstr "" -"Terlalu banyak ❤️ hari ini.\n" -"\n" -"Undang teman dan dapatkan lebih banyak ❤️\n" -"\n" -"https://t.me/{}?start={}" - -#: keyboards/admin/inline/customers.py:12 -#, fuzzy -msgid "🔍 Найти пользователя" -msgstr "Balas Pengguna" - -#: keyboards/admin/inline/customers.py:23 -#, fuzzy -msgid "🟢 Разблокировать" -msgstr "Buka Blokir" - -#: keyboards/admin/inline/customers.py:28 -#, fuzzy -msgid "🚫 Заблокировать" -msgstr "Buka Blokir" - -#: keyboards/admin/inline/mailing.py:8 -msgid "📧 Рассылка" -msgstr "" - -#: keyboards/admin/inline/mailing.py:16 keyboards/admin/inline/mailing.py:31 -#: keyboards/inline/admin_inline.py:9 -msgid "Подтвердить отправку" -msgstr "Konfirmasi Kirim" - -#: keyboards/admin/inline/mailing.py:19 -msgid "Добавить кнопку" -msgstr "Tambahkan Tombol" - -#: keyboards/admin/inline/mailing.py:21 keyboards/admin/inline/mailing.py:33 -#: keyboards/inline/cancel_inline.py:8 -msgid "Отмена" -msgstr "Batal" - -#: keyboards/admin/inline/payments.py:9 -msgid "⚙️ Настройки" -msgstr "" - -#: keyboards/admin/inline/payments.py:11 -msgid "📝 Статистика" -msgstr "" - -#: keyboards/admin/inline/ref.py:8 -msgid "📈 Статистика" -msgstr "" - -#: keyboards/admin/inline/ref.py:9 keyboards/admin/inline/setting.py:8 -#, fuzzy -msgid "*️⃣ Добавить" -msgstr "🗑️ Hapus" - -#: keyboards/admin/inline/ref.py:10 keyboards/admin/inline/setting.py:9 -#, fuzzy -msgid "❌ Удалить" -msgstr "🗑️ Hapus" - -#: keyboards/admin/inline/ref.py:11 keyboards/admin/inline/setting.py:10 -#, fuzzy -msgid "◀️ Назад" -msgstr "⏪️ Kembali" - -#: keyboards/admin/inline/reply_menu.py:9 -#, fuzzy -msgid "🙅🏻‍♂️ Отменить" -msgstr "🖊️ Ubah" - -#: keyboards/admin/inline/reply_menu.py:17 -msgid "👮‍♂️ Админ Состав" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:19 -#, fuzzy -msgid "📞 Сменить контакты" -msgstr "📞 Kontak" - -#: keyboards/admin/inline/reply_menu.py:29 -msgid "🗒 Выгрузить юзеров | .txt" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:32 -msgid "🗒 Выгрузить конфиги и логи" -msgstr "" - -#: keyboards/default/admin_default.py:8 -msgid "Рассылка" -msgstr "" - -#: keyboards/default/admin_default.py:9 -msgid "Сообщение по id" -msgstr "" - -#: keyboards/default/admin_default.py:10 -msgid "Посчитать людей и чаты" -msgstr "" - -#: keyboards/default/admin_default.py:11 -msgid "Мониторинг" -msgstr "" - -#: keyboards/default/admin_default.py:12 -msgid "Тех.Работа" -msgstr "" - -#: keyboards/default/get_contact_default.py:8 -msgid "📱 Отправить" -msgstr "📱 Kirim" - -#: keyboards/default/get_location_default.py:9 -msgid "🗺 Определить автоматически" -msgstr "🗺 Tentukan Otomatis" - -#: keyboards/default/get_photo.py:8 -msgid "Взять из профиля" -msgstr "Ambil dari Profil" - -#: keyboards/inline/admin_inline.py:18 -msgid "Включить" -msgstr "Hidupkan" - -#: keyboards/inline/admin_inline.py:21 -msgid "Выключить" -msgstr "Matikan" - -#: keyboards/inline/admin_inline.py:33 -msgid "Разблокировать" -msgstr "Buka Blokir" - -#: keyboards/inline/back_inline.py:8 -#: keyboards/inline/change_data_profile_inline.py:15 -#: keyboards/inline/filters_inline.py:42 keyboards/inline/language_inline.py:22 -#: keyboards/inline/poster_inline.py:31 keyboards/inline/poster_inline.py:49 -#: keyboards/inline/poster_inline.py:63 -#: keyboards/inline/registration_inline.py:12 -#: keyboards/inline/settings_menu.py:12 keyboards/inline/sponsor_inline.py:10 -#: keyboards/inline/sponsor_inline.py:21 -msgid "⏪️ Вернуться в меню" -msgstr "⏪️ Kembali ke Menu" - -#: keyboards/inline/cancel_inline.py:16 -#: keyboards/inline/change_data_profile_inline.py:28 -#, fuzzy -msgid "❌ Остановить" -msgstr "⏪ Berhenti" - -#: keyboards/inline/change_data_profile_inline.py:8 -msgid "👤 Имя" -msgstr "👤 Nama" - -#: keyboards/inline/change_data_profile_inline.py:9 -msgid "⚧ Пол" -msgstr "⚧ Jenis Kelamin" - -#: keyboards/inline/change_data_profile_inline.py:10 -msgid "📅 Возраст" -msgstr "📅 Usia" - -#: keyboards/inline/change_data_profile_inline.py:11 -msgid "🏙 Город" -msgstr "🏙 Kota" - -#: keyboards/inline/change_data_profile_inline.py:12 -msgid "📷 Фото" -msgstr "📷 Foto" - -#: keyboards/inline/change_data_profile_inline.py:13 -msgid "📝 О себе" -msgstr "📝 Tentang Saya" - -#: keyboards/inline/filters_inline.py:9 -msgid "🎉 Мероприятия" -msgstr "🎉 Acara" - -#: keyboards/inline/filters_inline.py:12 -msgid "❤️ Знакомства" -msgstr "❤️ Kenalan" - -#: keyboards/inline/filters_inline.py:14 keyboards/inline/filters_inline.py:31 -#: keyboards/inline/guide_inline.py:15 -msgid "⏪️ Назад" -msgstr "⏪️ Kembali" - -#: keyboards/inline/filters_inline.py:23 -msgid "🏙️ Город партнера" -msgstr "🏙️ Kota Pasangan" - -#: keyboards/inline/filters_inline.py:26 -msgid "🔞 Возр.диапазон" -msgstr "🔞 Rentang Usia" - -#: keyboards/inline/filters_inline.py:29 -msgid "🚻 Пол партнера" -msgstr "🚻 Jenis Kelamin Pasangan" - -#: keyboards/inline/filters_inline.py:40 -msgid "🏙️ Город" -msgstr "🏙️ Kota" - -#: keyboards/inline/guide_inline.py:21 -msgid "Вперед ➡️" -msgstr "Maju ➡️" - -#: keyboards/inline/guide_inline.py:25 -msgid "❌ Закрыть" -msgstr "❌ Tutup" - -#: keyboards/inline/language_inline.py:13 -msgid "🇷🇺 Русский" -msgstr "🇷🇺 Russia" - -#: keyboards/inline/language_inline.py:14 -msgid "🇩🇪 Немецкий" -msgstr "🇩🇪 Jerman" - -#: keyboards/inline/language_inline.py:15 -msgid "🇬🇧 Английский" -msgstr "🇬🇧 Inggris" - -#: keyboards/inline/language_inline.py:16 -msgid "🇮🇩 Индонезийский" -msgstr "🇮🇩 Indonesia" - -#: keyboards/inline/main_menu_inline.py:26 -msgid "➕ Регистрация" -msgstr "➕ Registrasi" - -#: keyboards/inline/main_menu_inline.py:28 keyboards/inline/settings_menu.py:10 -msgid "🌐 Язык" -msgstr "🌐 Bahasa" - -#: keyboards/inline/main_menu_inline.py:30 -msgid "👤 Моя анекта" -msgstr "👤 Profil Saya" - -#: keyboards/inline/main_menu_inline.py:32 -msgid "⚙️ Фильтры" -msgstr "⚙️ Filter" - -#: keyboards/inline/main_menu_inline.py:33 -msgid "💌 Найти пару" -msgstr "💌 Temukan Pasangan" - -#: keyboards/inline/main_menu_inline.py:34 -msgid "🗓️ Афиша" -msgstr "🗓️ Acara" - -#: keyboards/inline/main_menu_inline.py:35 -msgid "🆘 Поддержка" -msgstr "🆘 Bantuan" - -#: keyboards/inline/main_menu_inline.py:37 -msgid "ℹ️ Информация" -msgstr "ℹ️ Informasi" - -#: keyboards/inline/menu_profile_inline.py:10 -msgid "✅ Верификация" -msgstr "✅ Verifikasi" - -#: keyboards/inline/menu_profile_inline.py:16 -msgid "🖊️ Изменить" -msgstr "🖊️ Ubah" - -#: keyboards/inline/menu_profile_inline.py:18 -msgid "🗑️ Удалить" -msgstr "🗑️ Hapus" - -#: keyboards/inline/menu_profile_inline.py:19 -msgid "⏪ Назад" -msgstr "⏪ Kembali" - -#: keyboards/inline/payments_inline.py:9 -msgid "💳 ЮMoney" -msgstr "💳 Yandex.Money" - -#: keyboards/inline/payments_inline.py:16 -msgid "💳 Оплатить" -msgstr "💳 Bayar" - -#: keyboards/inline/payments_inline.py:18 -msgid "🔄 Проверить оплату" -msgstr "🔄 Periksa Pembayaran" - -#: keyboards/inline/poster_inline.py:21 -msgid "✍️Создать афишу" -msgstr "✍️ Buat Acara" - -#: keyboards/inline/poster_inline.py:24 -msgid "🎭 Смотреть афиши" -msgstr "🎭 Lihat Acara" - -#: keyboards/inline/poster_inline.py:27 -msgid "📝 Мои записи" -msgstr "📝 Catatan Saya" - -#: keyboards/inline/poster_inline.py:29 -msgid "📃 Моё событие" -msgstr "📃 Acara Saya" - -#: keyboards/inline/poster_inline.py:46 -msgid "✍️ Изменить" -msgstr "✍️ Ubah" - -#: keyboards/inline/poster_inline.py:58 -msgid "Название" -msgstr "Nama" - -#: keyboards/inline/poster_inline.py:60 -msgid "Описание" -msgstr "Deskripsi" - -#: keyboards/inline/poster_inline.py:73 -msgid "✅ Одобрить" -msgstr "✅ Setujui" - -#: keyboards/inline/poster_inline.py:76 -msgid "❌ Отклонить" -msgstr "❌ Tolak" - -#: keyboards/inline/poster_inline.py:85 -msgid "Пойду!" -msgstr "Akan Saya Hadiri!" - -#: keyboards/inline/poster_inline.py:88 -msgid "Не интересно" -msgstr "Tidak Tertarik" - -#: keyboards/inline/poster_inline.py:91 keyboards/inline/poster_inline.py:103 -#: keyboards/inline/poster_inline.py:112 -msgid "⏪️ Остановить" -msgstr "⏪ Berhenti" - -#: keyboards/inline/poster_inline.py:101 -msgid "❌ Отменить запись" -msgstr "❌ Batalkan Pendaftaran" - -#: keyboards/inline/questionnaires_inline.py:30 -#, fuzzy -msgid "💤 Остановить" -msgstr "⏪ Berhenti" - -#: keyboards/inline/questionnaires_inline.py:34 -msgid "🚫 Забанить" -msgstr "🚫 Blokir" - -#: keyboards/inline/questionnaires_inline.py:38 -msgid "Следующий" -msgstr "Selanjutnya" - -#: keyboards/inline/questionnaires_inline.py:74 -msgid "🚀 Смотреть" -msgstr "🚀 Lihat" - -#: keyboards/inline/questionnaires_inline.py:82 -msgid "👉 Перейти в чат" -msgstr "👉 Buka Obrolan" - -#: keyboards/inline/questionnaires_inline.py:89 -msgid "⏪️ Вернуться к просмотру анкет" -msgstr "⏪️ Kembali ke tampilan profil" - -#: keyboards/inline/registration_inline.py:9 -msgid "🖌️ Пройти опрос в боте" -msgstr "🖌️ Isi kuesioner di bot" - -#: keyboards/inline/registration_inline.py:21 -msgid "✅ Да все хорошо!" -msgstr "✅ Ya, semuanya baik!" - -#: keyboards/inline/settings_menu.py:8 -msgid "📚 Брендбук" -msgstr "📚 Brandbook" - -#: keyboards/inline/settings_menu.py:9 -msgid "📞 Контакты" -msgstr "📞 Kontak" - -#: keyboards/inline/support_inline.py:37 -msgid "Ответить пользователю" -msgstr "Balas Pengguna" - -#: keyboards/inline/support_inline.py:45 -msgid "Написать оператору" -msgstr "Tulis ke Operator" - -#: keyboards/inline/support_inline.py:60 keyboards/inline/support_inline.py:72 -msgid "Завершить сеанс" -msgstr "Akhiri Sesi" - -#: middlewares/BanCheck.py:44 -msgid "😢 Вы заблокированы!" -msgstr "😢 Anda telah diblokir!" - -#: middlewares/IsMaintenanceCheck.py:26 -msgid "Ведутся технические работы" -msgstr "Sedang dalam perawatan teknis" - -#: middlewares/LinkCheck.py:38 -msgid "" -"Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " -"подпишитесь! Ссылки ниже: " -msgstr "" -"Anda belum berlangganan di semua saluran! Untuk melanjutkan menggunakan " -"bot, berlanggananlah! Tautan di bawah ini: " - -#: utils/notify_admins.py:24 -msgid "Оповещение администрации..." -msgstr "" - -#: utils/notify_admins.py:28 -msgid "Бот был успешно запущен" -msgstr "" - -#: utils/statistics.py:16 -msgid "" -"📊 Статистика: \n" -"\n" -"└Сейчас в нашем боте {count_users} пользователей\n" -"└Из них:\n" -" ├{users_gender_m} пользователей мужского пола\n" -" ├{users_gender_f} пользователей женского пола\n" -" ├{users_city} пользователей из города {user_city}\n" -" ├{cs_uy} пользователей из других городов\n" -" ├{users_verified} верифицированных пользователей\n" -" ├{users_status} пользователей, создавшие анкету\n" -"└Дата создания бота - 10.08.2021" -msgstr "" -"📊 Statistik: \n" -"\n" -"└Saat ini bot kami memiliki {count_users} pengguna\n" -"└Diantara mereka:\n" -" ├{users_gender_m} pengguna berjenis kelamin pria\n" -" ├{users_gender_f} pengguna berjenis kelamin wanita\n" -" ├{users_city} pengguna berasal dari kota {user_city}\n" -" ├{cs_uy} pengguna berasal dari kota lain\n" -" ├{users_verified} pengguna terverifikasi\n" -" ├{users_status} pengguna telah membuat profil\n" -"└Bot dibuat pada tanggal 10 Agustus 2021" - -#~ msgid "" -#~ "Вы попали в раздел настроек бота," -#~ " здесь вы можете посмотреть: " -#~ "статистику,изменить язык, отключить уведомления, " -#~ "а также узнать информацию.\n" -#~ "\n" -#~ "🌐 Дней работаем: {}\n" -#~ "👤 Всего пользователей: {}\n" -#~ msgstr "" -#~ "Anda telah masuk ke bagian " -#~ "Pengaturan bot, di sini Anda " -#~ "dapat melihat: statistik, mengubah bahasa, " -#~ "mematikan pemberitahuan, serta mengetahui " -#~ "informasi.\n" -#~ "\n" -#~ "🌐 Hari bekerja: {}\n" -#~ "👤 Jumlah pengguna: {}\n" - -#~ msgid "🇫🇷 Французский" -#~ msgstr "" - -#~ msgid "🟢 Запустить бота" -#~ msgstr "" - -#~ msgid "⚒ Админ-Меню" -#~ msgstr "" - -#~ msgid "🗒 Логи" -#~ msgstr "" - -#~ msgid "Ваше новое имя: {censored}" -#~ msgstr "Nama baru Anda: {censored}" - -#~ msgid "Ваш новый пол: {}" -#~ msgstr "Jenis kelamin baru Anda: {}" - -#~ msgid "Отправьте мне новое описание анкеты:" -#~ msgstr "Kirimkan saya deskripsi profil baru:" - -#~ msgid "Комментарий принят!" -#~ msgstr "Komentar diterima!" - -#~ msgid "🫂 Пользователи" -#~ msgstr "Balas Pengguna" - -#~ msgid "📊 Реклама" -#~ msgstr "" - -#~ msgid "👀 Мониторинг" -#~ msgstr "" - -#~ msgid "🛑 Тех.Работа" -#~ msgstr "" - -#~ msgid "Чат с админом не найден" -#~ msgstr "" - diff --git a/locales/ru/LC_MESSAGES/dating.po b/locales/ru/LC_MESSAGES/dating.po deleted file mode 100644 index d4e9ef4..0000000 --- a/locales/ru/LC_MESSAGES/dating.po +++ /dev/null @@ -1,1511 +0,0 @@ -# Russian translations for PROJECT. -# Copyright (C) 2022 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2022. -# -msgid "" -msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-08-28 14:39+0300\n" -"PO-Revision-Date: 2022-12-15 21:01+0300\n" -"Last-Translator: FULL NAME \n" -"Language: ru\n" -"Language-Team: ru \n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.1\n" - -#: functions/dating/reaction_strategies.py:46 -#: functions/dating/reaction_strategies.py:171 -msgid "На данный момент у нас нет подходящих анкет для вас" -msgstr "" - -#: functions/dating/reaction_strategies.py:52 -msgid "У вас достигнут лимит на просмотры анкет" -msgstr "" - -#: functions/dating/reaction_strategies.py:61 -msgid "Кому-то понравилась твоя анкета" -msgstr "" - -#: functions/dating/reaction_strategies.py:103 handlers/users/view_event.py:51 -msgid "" -"Рад был помочь, {fullname}!\n" -"Надеюсь, ты нашел кого-то благодаря мне" -msgstr "" - -#: functions/dating/reaction_strategies.py:126 -msgid "Отлично! Надеюсь вы хорошо проведете время ;) Начинай общаться 👉" -msgstr "" - -#: functions/dating/reaction_strategies.py:187 -msgid "Выберите причину жалобы:" -msgstr "" - -#: functions/dating/reaction_strategies.py:204 -msgid "" -"Жалоба от пользователя: [@{username} | {tg_id}]" -"\n" -"\n" -"На пользователя: [{owner_id}]\n" -"Причина жалобы: {reason}\n" -"Количество жалоб на пользователя: {counter_of_report}" -msgstr "" - -#: functions/dating/send_form_func.py:25 -msgid "" -"{}, {} лет, {} {verification}\n" -"\n" -msgstr "" - -#: functions/dating/send_form_func.py:28 -msgid "{commentary}" -msgstr "" - -#: functions/dating/send_form_func.py:36 -msgid "Инстаграм - {instagram}\n" -msgstr "" - -#: functions/dating/send_form_func.py:48 -msgid "" -"{}\n" -"\n" -"{}" -msgstr "" - -#: functions/dating/send_form_func.py:57 -msgid "" -"{}\n" -"\n" -"Инстаграм - {instagram}\n" -msgstr "" - -#: functions/event/extra_features.py:86 -msgid "На данный момент у нас нет подходящих мероприятий для вас" -msgstr "" - -#: functions/event/templates_messages.py:17 -msgid "" -"{} \n" -"Когда: {} \n" -"Где: {} \n" -"\n" -"{}" -msgstr "" - -#: functions/main_app/app_scheduler.py:12 -msgid "Несколько {} из города {} хотят познакомиться с тобой прямо сейчас" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:71 -msgid "" -"{name}, {age} лет, {city}, {verification}\n" -"\n" -"{commentary}\n" -"\n" -"Партнерка:\n" -"Количество приглашенных друзей: {reff}\n" -"Реферальная ссылка:\n" -" {link}" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:96 -msgid "" -"Фильтр по подбору партнеров:\n" -"\n" -"🚻 Необходимы пол партнера: {}\n" -"🔞 Возрастной диапазон: {}-{} лет\n" -"\n" -"🏙️ Город партнера: {}" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:121 -msgid "" -"Приветствую вас, {fullname}!!\n" -"\n" -"{heart} QueDateBot - платформа для поиска новых знакомств.\n" -"\n" -"🪧 Новости о проекте вы можете прочитать в нашем канале - " -"https://t.me/QueDateGroup \n" -"\n" -"🤝 Сотрудничество: \n" -"Если у вас есть предложение о сотрудничестве, пишите агенту поддержки - " -"@{supports}\n" -"\n" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:168 -msgid "" -"По вашей ссылке зарегистрировался пользователь {}!\n" -"Вы получаете дополнительных 15 ❤️" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:194 -msgid "" -"Регистрация успешно завершена! \n" -"\n" -" {}, {} лет, {}\n" -"\n" -"О себе - {}" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:215 -#: functions/main_app/auxiliary_tools.py:281 -msgid "Фото принято!" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:219 -#: functions/main_app/auxiliary_tools.py:254 -#: functions/main_app/auxiliary_tools.py:290 -msgid "" -"Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" -"Если ошибка осталась, напишите агенту поддержки." -msgstr "" - -#: functions/main_app/auxiliary_tools.py:242 -msgid "" -"Во время проверки вашего фото мы обнаружили подозрительный контент!\n" -"Поэтому мы чуть-чуть подкорректировали вашу фотографию" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:262 -msgid "" -"Фото принято!\n" -"Выберите, что вы хотите изменить: " -msgstr "" - -#: functions/main_app/auxiliary_tools.py:285 -msgid "Выберите, что вы хотите изменить: " -msgstr "" - -#: functions/main_app/auxiliary_tools.py:336 -msgid "" -"Руководство по боту: \n" -"Страница №{}" -msgstr "" - -#: functions/main_app/auxiliary_tools.py:352 -msgid "" -"Вы попали в раздел Информации бота, здесь вы можете посмотреть: " -"статистику,изменить язык, а также посмотреть наш брендбук.\n" -"\n" -"🌐 Дней работаем: {}\n" -"👤 Всего пользователей: {}\n" -msgstr "" - -#: functions/main_app/determin_location.py:32 -msgid "" -"Я нашел такой адрес:\n" -"{city}\n" -"Если все правильно, то подтвердите" -msgstr "" - -#: handlers/echo_handler.py:13 -msgid "Эхо без состояния." -msgstr "" - -#: handlers/echo_handler.py:38 -msgid "Меню: " -msgstr "" - -#: handlers/admins/monitoring.py:14 -msgid "Чтобы начать мониторинг нажмите на кнопку ниже" -msgstr "" - -#: handlers/admins/monitoring.py:32 -msgid "Анкета пользователя была заблокирована" -msgstr "" - -#: handlers/admins/advert/advertisement.py:21 -msgid "" -"📧 Рассылка\n" -"Пришлите текст для рассылки либо фото с текстом для рассылки! Чтобы " -"отредактировать, используйте встроенный редактор телеграма!\n" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:34 -#: handlers/admins/advert/mailing/create.py:124 -#: handlers/admins/advert/mailing/create.py:267 -msgid "Начинаю рассылку!" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:49 -#: handlers/admins/advert/mailing/create.py:137 -#: handlers/admins/advert/mailing/create.py:192 -#: handlers/admins/advert/mailing/create.py:281 -msgid "" -"Сообщение не дошло в чат {chat} Причина: \n" -"{err}" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:55 -#: handlers/admins/advert/mailing/create.py:143 -#: handlers/admins/advert/mailing/create.py:198 -#: handlers/admins/advert/mailing/create.py:287 -msgid "Рассылка проведена успешно! Ее получили: {count} чатов!\n" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:61 -msgid "Пришлите мне название кнопки!" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:70 -msgid "Название кнопки принято! Теперь отправьте мне ссылку для этой кнопки!" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:90 -#: handlers/admins/advert/mailing/create.py:232 -msgid "" -"Вот так будет выглядеть сообщение: \n" -"\n" -"{text}\n" -"\n" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:97 -#: handlers/admins/advert/mailing/create.py:239 -msgid "Вы подтверждаете отправку?" -msgstr "" - -#: handlers/admins/advert/mailing/create.py:104 -#: handlers/admins/advert/mailing/create.py:246 -msgid "" -"Произошла ошибка! Скорее всего, неправильно введена ссылка! Попробуйте " -"еще раз." -msgstr "" - -#: handlers/admins/advert/mailing/create.py:157 -msgid "" -"Вот сообщение: \n" -"\n" -"{text}\n" -"\n" -" Вы подтверждаете отправку? Или хотите что-то добавить?" -msgstr "" - -#: handlers/admins/settings/tech_works.py:26 -msgid "Чтобы включить/выключить технические работы, нажмите на кнопку ниже" -msgstr "" - -#: handlers/admins/settings/tech_works.py:36 -msgid "Технические работы включены" -msgstr "" - -#: handlers/admins/settings/tech_works.py:44 -msgid "Технические работы выключены" -msgstr "" - -#: handlers/groups/event_moderate.py:24 -msgid "Принято!" -msgstr "" - -#: handlers/groups/event_moderate.py:33 -msgid "Ваше мероприятие прошло модерацию" -msgstr "" - -#: handlers/groups/event_moderate.py:40 -msgid "Отклонено!" -msgstr "" - -#: handlers/groups/event_moderate.py:44 -msgid "К сожалению ваше мероприятие не прошло модерацию" -msgstr "" - -#: handlers/groups/start.py:12 -msgid "" -"Привет, я бот, проекта Que Group, для верификации анкет для " -"знакомств\n" -"\n" -msgstr "" - -#: handlers/users/back.py:43 -msgid "Вы забанены!" -msgstr "" - -#: handlers/users/back.py:50 -msgid "Вы вернулись в меню фильтров" -msgstr "" - -#: handlers/users/brandbook_handler.py:23 -msgid "" -"Руководство по боту: \n" -"Страница №1" -msgstr "" - -#: handlers/users/buy_unban.py:17 -msgid "" -"💳 Сейчас вам нужно выбрать способ оплаты\n" -"\n" -"├Стоимость разблокировки - 99₽\n" -"├Оплата обычно приходить в течение 1-3 минут\n" -"├Если у вас нет Yoomoney или нет возможности\n" -"├оплатить, напишите агенту поддержки" -msgstr "" - -#: handlers/users/buy_unban.py:38 -msgid "После оплаты нажмите 🔄 Проверить оплату" -msgstr "" - -#: handlers/users/buy_unban.py:57 -msgid "Поздравляем! Вы были разрабанены" -msgstr "" - -#: handlers/users/buy_unban.py:65 -msgid "" -"Оплата не прошла! Подождите минут 10, а затем еще раз попробуйте нажать " -"кнопку ниже" -msgstr "" - -#: handlers/users/change_datas.py:35 -msgid "Ваши данные: \n" -msgstr "" - -#: handlers/users/change_datas.py:40 -msgid "Введите новое имя" -msgstr "" - -#: handlers/users/change_datas.py:53 -msgid "" -"Ваше новое имя: {censored}\n" -"Выберите, что вы хотите изменить: " -msgstr "" - -#: handlers/users/change_datas.py:63 -msgid "" -"Произошла неизвестная ошибка. Попробуйте ещё раз\n" -"Возможно, Ваше сообщение слишком большое" -msgstr "" - -#: handlers/users/change_datas.py:76 -msgid "Введите новый возраст" -msgstr "" - -#: handlers/users/change_datas.py:90 -msgid "" -"Ваш новый возраст: {messages}\n" -"Выберите, что вы хотите изменить: " -msgstr "" - -#: handlers/users/change_datas.py:99 handlers/users/registration.py:163 -msgid "Вы ввели недопустимое число, попробуйте еще раз" -msgstr "" - -#: handlers/users/change_datas.py:104 -msgid "Вы ввели не число, попробуйте еще раз" -msgstr "" - -#: handlers/users/change_datas.py:112 -msgid "Введите новый город" -msgstr "" - -#: handlers/users/change_datas.py:124 -msgid "Мы не смогли найти город {city}. Попробуйте ещё раз" -msgstr "" - -#: handlers/users/change_datas.py:134 -msgid "" -"Данные успешно изменены.\n" -"Выберите, что вы хотите изменить: " -msgstr "" - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♂️ Мужской" -msgstr "" - -#: handlers/users/change_datas.py:143 handlers/users/registration.py:63 -msgid "👱🏻‍♀️ Женский" -msgstr "" - -#: handlers/users/change_datas.py:145 -msgid "Выберите новый пол: " -msgstr "" - -#: handlers/users/change_datas.py:155 -msgid "" -"Ваш новый пол: {}\n" -"Выберите, что вы хотите изменить: " -msgstr "" - -#: handlers/users/change_datas.py:167 -msgid "Отправьте мне новую фотографию" -msgstr "" - -#: handlers/users/change_datas.py:191 handlers/users/registration.py:244 -msgid "Произошла ошибка, проверьте настройки конфиденциальности" -msgstr "" - -#: handlers/users/change_datas.py:232 -msgid "Отправьте сообщение о себе" -msgstr "" - -#: handlers/users/change_datas.py:247 -msgid "" -"Комментарий принят!\n" -"Выберите, что вы хотите изменить: " -msgstr "" - -#: handlers/users/change_datas.py:254 -msgid "" -"Произошла ошибка! Попробуйте еще раз изменить описание. Возможно, Ваше " -"сообщение слишком большое\n" -"Если ошибка осталась, напишите в поддержку." -msgstr "" - -#: handlers/users/change_datas.py:267 -msgid "" -"Напишите имя своего аккаунта\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" - -#: handlers/users/change_datas.py:289 -msgid "Ваш аккаунт успешно добавлен" -msgstr "" - -#: handlers/users/change_datas.py:293 handlers/users/verification.py:45 -msgid "Вы были возвращены в меню" -msgstr "" - -#: handlers/users/change_datas.py:297 -msgid "" -"Вы ввели неправильную ссылку или имя аккаунта.\n" -"\n" -"Примеры:\n" -"@unknown\n" -"https://www.instagram.com/unknown" -msgstr "" - -#: handlers/users/change_datas.py:304 -msgid "Возникла ошибка. Попробуйте еще раз" -msgstr "" - -#: handlers/users/change_event_datas.py:16 -msgid "Вы перешли в меню изменения данных мероприятия" -msgstr "" - -#: handlers/users/change_event_datas.py:23 -msgid "Напишите новое название вашего мероприятия" -msgstr "" - -#: handlers/users/change_event_datas.py:35 -#: handlers/users/change_event_datas.py:53 -msgid "Данные изменены" -msgstr "" - -#: handlers/users/change_event_datas.py:41 -msgid "Напишите новое описание вашего мероприятия" -msgstr "" - -#: handlers/users/event_handler.py:31 -msgid "Вы перешли в меню афиш" -msgstr "" - -#: handlers/users/event_handler.py:48 -msgid "Введите название мероприятие" -msgstr "" - -#: handlers/users/event_handler.py:55 -msgid "" -"Вы уже создали мероприятие, которое проходит модерацию. Дождитесь " -"проверки, пожалуйста" -msgstr "" - -#: handlers/users/event_handler.py:63 -msgid "" -"Прочитайте сообщение и не нажимайте на кнопку, пока ваше мероприятие не " -"пройдет модерацию" -msgstr "" - -#: handlers/users/event_handler.py:78 -msgid "Пожалуйста, выберите дату: " -msgstr "" - -#: handlers/users/event_handler.py:83 -msgid "" -"Длинна вашего сообщение превышает допустимую.\n" -"Попробуйте ещё раз" -msgstr "" - -#: handlers/users/event_handler.py:99 -msgid "Вы не можете проводить мероприятие в прошлом" -msgstr "" - -#: handlers/users/event_handler.py:105 -msgid "Теперь напишите место проведения" -msgstr "" - -#: handlers/users/event_handler.py:126 -msgid "Вы ввели слишком длинное название городаПопробуйте ещё раз." -msgstr "" - -#: handlers/users/event_handler.py:132 -msgid "" -"Произошла неизвестная ошибка! Попробуйте еще раз.\n" -"Вероятнее всего вы ввели город неправильно" -msgstr "" - -#: handlers/users/event_handler.py:142 -msgid "Хорошо, теперь напишите короткое или длинное описание вашего мероприятия" -msgstr "" - -#: handlers/users/event_handler.py:158 -msgid "И напоследок, пришлите постер вашего мероприятия" -msgstr "" - -#: handlers/users/event_handler.py:164 -msgid "Ваше сообщение слишком длинное.Попробуйте написать короче" -msgstr "" - -#: handlers/users/event_handler.py:178 -msgid "Фото принято" -msgstr "" - -#: handlers/users/event_handler.py:200 -msgid "Ваше мероприятие отправлено на модерацию" -msgstr "" - -#: handlers/users/event_list.py:23 -msgid "На данный момент вы никуда не записались" -msgstr "" - -#: handlers/users/filters.py:23 handlers/users/filters.py:29 -msgid "Вы перешли в раздел с фильтрами" -msgstr "" - -#: handlers/users/filters.py:41 -msgid "Напишите минимальный возраст" -msgstr "" - -#: handlers/users/filters.py:53 -msgid "Теперь введите максимальный возраст" -msgstr "" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♂️ Парня" -msgstr "" - -#: handlers/users/filters.py:73 handlers/users/registration.py:97 -msgid "👱🏻‍♀️ Девушку" -msgstr "" - -#: handlers/users/filters.py:76 -msgid "Выберите, кого вы хотите найти:" -msgstr "" - -#: handlers/users/filters.py:84 handlers/users/filters.py:111 -msgid "Данные сохранены" -msgstr "" - -#: handlers/users/filters.py:92 -msgid "Напишите город вашего будущего партнера" -msgstr "" - -#: handlers/users/filters.py:103 handlers/users/filters.py:144 -msgid "Произошла ошибка, попробуйте еще раз" -msgstr "" - -#: handlers/users/filters.py:124 -msgid "Вы перешли в меню настроек фильтров для мероприятий" -msgstr "" - -#: handlers/users/filters.py:132 -msgid "Напишите город, в котором бы хотели сходить куда-нибудь" -msgstr "" - -#: handlers/users/registration.py:42 -msgid "Пройдите опрос, чтобы зарегистрироваться" -msgstr "" - -#: handlers/users/registration.py:52 -msgid "" -"Вы уже зарегистрированы, если вам нужно изменить анкету, то нажмите на " -"кнопку ниже" -msgstr "" - -#: handlers/users/registration.py:66 -msgid "Выберите пол" -msgstr "" - -#: handlers/users/registration.py:88 -msgid "Теперь расскажите о себе:\n" -msgstr "" - -#: handlers/users/registration.py:105 -msgid "Комментарий принят! Выберите, кого вы хотите найти: " -msgstr "" - -#: handlers/users/registration.py:111 -msgid "" -"Произошла неизвестная ошибка! Попробуйте изменить комментарий позже в " -"разделе \"Меню\"\n" -"\n" -"Выберите, кого вы хотите найти: " -msgstr "" - -#: handlers/users/registration.py:125 -msgid "Отлично! Теперь напишите мне ваше имя, которое будут все видеть в анкете" -msgstr "" - -#: handlers/users/registration.py:145 -msgid "Введите сколько вам лет:" -msgstr "" - -#: handlers/users/registration.py:170 -msgid "Вы ввели не число" -msgstr "" - -#: handlers/users/registration.py:175 -msgid "Нажмите на кнопку ниже, чтобы определить ваш местоположение!" -msgstr "" - -#: handlers/users/registration.py:188 -msgid "Мы не смогли найти такой город, попробуйте еще раз" -msgstr "" - -#: handlers/users/registration.py:211 handlers/users/registration.py:224 -msgid "" -"И напоследок, Пришлите мне вашу фотографию (отправлять надо сжатое " -"изображение, а не как документ)" -msgstr "" - -#: handlers/users/sponsor.py:10 -msgid "" -"Наш проект работает на Open Source и мы будем рады,если вы нам " -"поможете развивать проект.\n" -"\n" -"С помощью кнопки 💰 Донат вы можете отправить своё пожертвование" -msgstr "" - -#: handlers/users/start_handler.py:27 -msgid "Вам необходимо зарегистрировать агента(ов) тех поддержки" -msgstr "" - -#: handlers/users/start_handler.py:36 -msgid "Вас нет в базе данной" -msgstr "" - -#: handlers/users/start_handler.py:42 handlers/users/start_handler.py:47 -msgid "Выберите язык" -msgstr "" - -#: handlers/users/start_handler.py:57 -msgid "Язык был успешно изменен. Введите команду /start" -msgstr "" - -#: handlers/users/start_handler.py:61 -msgid "Произошла какая-то ошибка. Введите команду /start и попробуйте еще раз" -msgstr "" - -#: handlers/users/support_handler.py:20 -msgid "Хотите связаться с тех поддержкой? Нажмите на кнопку ниже!" -msgstr "" - -#: handlers/users/support_handler.py:25 handlers/users/support_handler.py:52 -msgid "К сожалению, сейчас нет свободных операторов. Попробуйте позже." -msgstr "" - -#: handlers/users/support_handler.py:41 -msgid "Вы обратились в техническую поддержку. Ждем ответа от оператора!" -msgstr "" - -#: handlers/users/support_handler.py:64 -msgid "С вами хочет связаться пользователь {full_name}" -msgstr "" - -#: handlers/users/support_handler.py:79 -msgid "К сожалению, пользователь уже передумал." -msgstr "" - -#: handlers/users/support_handler.py:91 -msgid "" -"Вы на связи с пользователем!\n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" - -#: handlers/users/support_handler.py:100 -msgid "" -"Техподдержка на связи! Можете писать сюда свое сообщение. \n" -"Чтобы завершить общение нажмите на кнопку." -msgstr "" - -#: handlers/users/support_handler.py:115 -msgid "Дождитесь ответа оператора или отмените сеанс" -msgstr "" - -#: handlers/users/support_handler.py:131 -msgid "Пользователь завершил сеанс техподдержки" -msgstr "" - -#: handlers/users/support_handler.py:135 -msgid "Вы завершили сеанс и были возвращены в главное меню" -msgstr "" - -#: handlers/users/user_profile.py:23 -msgid "" -"Ваша анкета удалена!\n" -"Я надеюсь вы кого-нибудь нашли" -msgstr "" - -#: handlers/users/verification.py:17 -msgid "Чтобы пройти верификацию вам нужно отправить свой контакт" -msgstr "" - -#: handlers/users/verification.py:33 -msgid "" -"Спасибо, {contact_full_name}.\n" -"Ваш номер {contact_phone_number} был получен." -msgstr "" - -#: handlers/users/verification.py:50 -msgid "Ваш номер недействителен, попробуйте еще раз." -msgstr "" - -#: handlers/users/view_event.py:27 -msgid "На данный момент вы просмотрели все существующие анкеты" -msgstr "" - -#: handlers/users/view_ques.py:73 -msgid "Жалоба успешно отправлена" -msgstr "" - -#: handlers/users/view_ques.py:112 -msgid "" -"Слишком много ❤️ за сегодня.\n" -"\n" -"Пригласи друзей и получи больше ❤️\n" -"\n" -"https://t.me/{}?start={}" -msgstr "" - -#: keyboards/admin/inline/customers.py:12 -msgid "🔍 Найти пользователя" -msgstr "" - -#: keyboards/admin/inline/customers.py:23 -msgid "🟢 Разблокировать" -msgstr "" - -#: keyboards/admin/inline/customers.py:28 -msgid "🚫 Заблокировать" -msgstr "" - -#: keyboards/admin/inline/mailing.py:8 -msgid "📧 Рассылка" -msgstr "" - -#: keyboards/admin/inline/mailing.py:16 keyboards/admin/inline/mailing.py:31 -#: keyboards/inline/admin_inline.py:9 -msgid "Подтвердить отправку" -msgstr "" - -#: keyboards/admin/inline/mailing.py:19 -msgid "Добавить кнопку" -msgstr "" - -#: keyboards/admin/inline/mailing.py:21 keyboards/admin/inline/mailing.py:33 -#: keyboards/inline/cancel_inline.py:8 -msgid "Отмена" -msgstr "" - -#: keyboards/admin/inline/payments.py:9 -msgid "⚙️ Настройки" -msgstr "" - -#: keyboards/admin/inline/payments.py:11 -msgid "📝 Статистика" -msgstr "" - -#: keyboards/admin/inline/ref.py:8 -msgid "📈 Статистика" -msgstr "" - -#: keyboards/admin/inline/ref.py:9 keyboards/admin/inline/setting.py:8 -msgid "*️⃣ Добавить" -msgstr "" - -#: keyboards/admin/inline/ref.py:10 keyboards/admin/inline/setting.py:9 -msgid "❌ Удалить" -msgstr "" - -#: keyboards/admin/inline/ref.py:11 keyboards/admin/inline/setting.py:10 -msgid "◀️ Назад" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:9 -msgid "🙅🏻‍♂️ Отменить" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:17 -msgid "👮‍♂️ Админ Состав" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:19 -msgid "📞 Сменить контакты" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:29 -msgid "🗒 Выгрузить юзеров | .txt" -msgstr "" - -#: keyboards/admin/inline/reply_menu.py:32 -msgid "🗒 Выгрузить конфиги и логи" -msgstr "" - -#: keyboards/default/admin_default.py:8 -msgid "Рассылка" -msgstr "" - -#: keyboards/default/admin_default.py:9 -msgid "Сообщение по id" -msgstr "" - -#: keyboards/default/admin_default.py:10 -msgid "Посчитать людей и чаты" -msgstr "" - -#: keyboards/default/admin_default.py:11 -msgid "Мониторинг" -msgstr "" - -#: keyboards/default/admin_default.py:12 -msgid "Тех.Работа" -msgstr "" - -#: keyboards/default/get_contact_default.py:8 -msgid "📱 Отправить" -msgstr "" - -#: keyboards/default/get_location_default.py:9 -msgid "🗺 Определить автоматически" -msgstr "" - -#: keyboards/default/get_photo.py:8 -msgid "Взять из профиля" -msgstr "" - -#: keyboards/inline/admin_inline.py:18 -msgid "Включить" -msgstr "" - -#: keyboards/inline/admin_inline.py:21 -msgid "Выключить" -msgstr "" - -#: keyboards/inline/admin_inline.py:33 -msgid "Разблокировать" -msgstr "" - -#: keyboards/inline/back_inline.py:8 -#: keyboards/inline/change_data_profile_inline.py:15 -#: keyboards/inline/filters_inline.py:42 keyboards/inline/language_inline.py:22 -#: keyboards/inline/poster_inline.py:31 keyboards/inline/poster_inline.py:49 -#: keyboards/inline/poster_inline.py:63 -#: keyboards/inline/registration_inline.py:12 -#: keyboards/inline/settings_menu.py:12 keyboards/inline/sponsor_inline.py:10 -#: keyboards/inline/sponsor_inline.py:21 -msgid "⏪️ Вернуться в меню" -msgstr "" - -#: keyboards/inline/cancel_inline.py:16 -#: keyboards/inline/change_data_profile_inline.py:28 -msgid "❌ Остановить" -msgstr "" - -#: keyboards/inline/change_data_profile_inline.py:8 -msgid "👤 Имя" -msgstr "" - -#: keyboards/inline/change_data_profile_inline.py:9 -msgid "⚧ Пол" -msgstr "" - -#: keyboards/inline/change_data_profile_inline.py:10 -msgid "📅 Возраст" -msgstr "" - -#: keyboards/inline/change_data_profile_inline.py:11 -msgid "🏙 Город" -msgstr "" - -#: keyboards/inline/change_data_profile_inline.py:12 -msgid "📷 Фото" -msgstr "" - -#: keyboards/inline/change_data_profile_inline.py:13 -msgid "📝 О себе" -msgstr "" - -#: keyboards/inline/filters_inline.py:9 -msgid "🎉 Мероприятия" -msgstr "" - -#: keyboards/inline/filters_inline.py:12 -msgid "❤️ Знакомства" -msgstr "" - -#: keyboards/inline/filters_inline.py:14 keyboards/inline/filters_inline.py:31 -#: keyboards/inline/guide_inline.py:15 -msgid "⏪️ Назад" -msgstr "" - -#: keyboards/inline/filters_inline.py:23 -msgid "🏙️ Город партнера" -msgstr "" - -#: keyboards/inline/filters_inline.py:26 -msgid "🔞 Возр.диапазон" -msgstr "" - -#: keyboards/inline/filters_inline.py:29 -msgid "🚻 Пол партнера" -msgstr "" - -#: keyboards/inline/filters_inline.py:40 -msgid "🏙️ Город" -msgstr "" - -#: keyboards/inline/guide_inline.py:21 -msgid "Вперед ➡️" -msgstr "" - -#: keyboards/inline/guide_inline.py:25 -msgid "❌ Закрыть" -msgstr "" - -#: keyboards/inline/language_inline.py:13 -msgid "🇷🇺 Русский" -msgstr "" - -#: keyboards/inline/language_inline.py:14 -msgid "🇩🇪 Немецкий" -msgstr "" - -#: keyboards/inline/language_inline.py:15 -msgid "🇬🇧 Английский" -msgstr "" - -#: keyboards/inline/language_inline.py:16 -msgid "🇮🇩 Индонезийский" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:26 -msgid "➕ Регистрация" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:28 keyboards/inline/settings_menu.py:10 -msgid "🌐 Язык" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:30 -msgid "👤 Моя анекта" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:32 -msgid "⚙️ Фильтры" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:33 -msgid "💌 Найти пару" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:34 -msgid "🗓️ Афиша" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:35 -msgid "🆘 Поддержка" -msgstr "" - -#: keyboards/inline/main_menu_inline.py:37 -msgid "ℹ️ Информация" -msgstr "" - -#: keyboards/inline/menu_profile_inline.py:10 -msgid "✅ Верификация" -msgstr "" - -#: keyboards/inline/menu_profile_inline.py:16 -msgid "🖊️ Изменить" -msgstr "" - -#: keyboards/inline/menu_profile_inline.py:18 -msgid "🗑️ Удалить" -msgstr "" - -#: keyboards/inline/menu_profile_inline.py:19 -msgid "⏪ Назад" -msgstr "" - -#: keyboards/inline/payments_inline.py:9 -msgid "💳 ЮMoney" -msgstr "" - -#: keyboards/inline/payments_inline.py:16 -msgid "💳 Оплатить" -msgstr "" - -#: keyboards/inline/payments_inline.py:18 -msgid "🔄 Проверить оплату" -msgstr "" - -#: keyboards/inline/poster_inline.py:21 -msgid "✍️Создать афишу" -msgstr "" - -#: keyboards/inline/poster_inline.py:24 -msgid "🎭 Смотреть афиши" -msgstr "" - -#: keyboards/inline/poster_inline.py:27 -msgid "📝 Мои записи" -msgstr "" - -#: keyboards/inline/poster_inline.py:29 -msgid "📃 Моё событие" -msgstr "" - -#: keyboards/inline/poster_inline.py:46 -msgid "✍️ Изменить" -msgstr "" - -#: keyboards/inline/poster_inline.py:58 -msgid "Название" -msgstr "" - -#: keyboards/inline/poster_inline.py:60 -msgid "Описание" -msgstr "" - -#: keyboards/inline/poster_inline.py:73 -msgid "✅ Одобрить" -msgstr "" - -#: keyboards/inline/poster_inline.py:76 -msgid "❌ Отклонить" -msgstr "" - -#: keyboards/inline/poster_inline.py:85 -msgid "Пойду!" -msgstr "" - -#: keyboards/inline/poster_inline.py:88 -msgid "Не интересно" -msgstr "" - -#: keyboards/inline/poster_inline.py:91 keyboards/inline/poster_inline.py:103 -#: keyboards/inline/poster_inline.py:112 -msgid "⏪️ Остановить" -msgstr "" - -#: keyboards/inline/poster_inline.py:101 -msgid "❌ Отменить запись" -msgstr "" - -#: keyboards/inline/questionnaires_inline.py:30 -msgid "💤 Остановить" -msgstr "" - -#: keyboards/inline/questionnaires_inline.py:34 -msgid "🚫 Забанить" -msgstr "" - -#: keyboards/inline/questionnaires_inline.py:38 -msgid "Следующий" -msgstr "" - -#: keyboards/inline/questionnaires_inline.py:74 -msgid "🚀 Смотреть" -msgstr "" - -#: keyboards/inline/questionnaires_inline.py:82 -msgid "👉 Перейти в чат" -msgstr "" - -#: keyboards/inline/questionnaires_inline.py:89 -msgid "⏪️ Вернуться к просмотру анкет" -msgstr "" - -#: keyboards/inline/registration_inline.py:9 -msgid "🖌️ Пройти опрос в боте" -msgstr "" - -#: keyboards/inline/registration_inline.py:21 -msgid "✅ Да все хорошо!" -msgstr "" - -#: keyboards/inline/settings_menu.py:8 -msgid "📚 Брендбук" -msgstr "" - -#: keyboards/inline/settings_menu.py:9 -msgid "📞 Контакты" -msgstr "" - -#: keyboards/inline/support_inline.py:37 -msgid "Ответить пользователю" -msgstr "" - -#: keyboards/inline/support_inline.py:45 -msgid "Написать оператору" -msgstr "" - -#: keyboards/inline/support_inline.py:60 keyboards/inline/support_inline.py:72 -msgid "Завершить сеанс" -msgstr "" - -#: middlewares/BanCheck.py:44 -msgid "😢 Вы заблокированы!" -msgstr "" - -#: middlewares/IsMaintenanceCheck.py:26 -msgid "Ведутся технические работы" -msgstr "" - -#: middlewares/LinkCheck.py:38 -msgid "" -"Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " -"подпишитесь! Ссылки ниже: " -msgstr "" - -#: utils/notify_admins.py:24 -msgid "Оповещение администрации..." -msgstr "" - -#: utils/notify_admins.py:28 -msgid "Бот был успешно запущен" -msgstr "" - -#: utils/statistics.py:16 -msgid "" -"📊 Статистика: \n" -"\n" -"└Сейчас в нашем боте {count_users} пользователей\n" -"└Из них:\n" -" ├{users_gender_m} пользователей мужского пола\n" -" ├{users_gender_f} пользователей женского пола\n" -" ├{users_city} пользователей из города {user_city}\n" -" ├{cs_uy} пользователей из других городов\n" -" ├{users_verified} верифицированных пользователей\n" -" ├{users_status} пользователей, создавшие анкету\n" -"└Дата создания бота - 10.08.2021" -msgstr "" - -#~ msgid "Вам необходимо зарегистрироваться, нажмите на кнопку ниже" -#~ msgstr "" - -#~ msgid "" -#~ "Описание анкеты пользователя.\n" -#~ "\n" -#~ "Мы не проверяем голосовые сообщения, " -#~ "поэтому советуем уменьшить громкость звука" -#~ msgstr "" - -#~ msgid "Описание вашей анкеты" -#~ msgstr "" - -#~ msgid "Вы вошли в админ панель!" -#~ msgstr "" - -#~ msgid "Количество людей внутри бота: {users}\n" -#~ msgstr "" - -#~ msgid "Отправьте мне id получателя!" -#~ msgstr "" - -#~ msgid "ID принят! Теперь введите текст!" -#~ msgstr "" - -#~ msgid "Сообщение успешно отправлено!" -#~ msgstr "" - -#~ msgid "" -#~ "Пришлите текст для рассылки либо фото" -#~ " с текстом для рассылки! Чтобы " -#~ "отредактировать, используйте встроенный редактор " -#~ "телеграма!\n" -#~ msgstr "" - -#~ msgid "" -#~ "Руководство по боту: \n" -#~ "Страница №2" -#~ msgstr "" - -#~ msgid "" -#~ "Руководство по боту: \n" -#~ "Страница №3" -#~ msgstr "" - -#~ msgid "" -#~ "Руководство по боту: \n" -#~ "Страница №4" -#~ msgstr "" - -#~ msgid "Теперь выберите, как вы хотите рассказать о себе:\n" -#~ msgstr "" - -#~ msgid "Запишите голосовое сообщение" -#~ msgstr "" - -#~ msgid "Отправьте сообщение о себе" -#~ msgstr "" - -#~ msgid "Парень" -#~ msgstr "" - -#~ msgid "Девушка" -#~ msgstr "" - -#~ msgid "💬 Руководство" -#~ msgstr "" - -#~ msgid "📈 Статистика" -#~ msgstr "" - -#~ msgid "💬 Текстом" -#~ msgstr "" - -#~ msgid "🎤 Голосовым" -#~ msgstr "" - -#~ msgid "🕴️ Спонсорство" -#~ msgstr "" - -#~ msgid "💰 Донат" -#~ msgstr "" - -#~ msgid "" -#~ "{}, {} лет, {} {}\n" -#~ "\n" -#~ "{}\n" -#~ "\n" -#~ "Партнерка:\n" -#~ "Количество приглашенных друзей: {}\n" -#~ "Реферальная ссылка: {}" -#~ msgstr "" - -#~ msgid "" -#~ "\n" -#~ "\n" -#~ "Инстаграм - {}\n" -#~ msgstr "" - -#~ msgid "" -#~ "У вас не установлен username, " -#~ "пожалуйста, зайдите в настройки аккаунта " -#~ "и создайте username.\n" -#~ "Без него вы не сможете пользоваться ботом\n" -#~ "\n" -#~ "Инструкция №1\n" -#~ "Инструкция №2" -#~ msgstr "" - -#~ msgid "" -#~ "Вами заинтересовался пользователь {varname_0}" -#~ msgstr "" - -#~ msgid "Ваша анкета отправлена другому пользователю" -#~ msgstr "" - -#~ msgid "👱🏻‍♂️ Парень" -#~ msgstr "" - -#~ msgid "👱🏻‍♀️ Девушка" -#~ msgstr "" - -#~ msgid "Изменить анкету" -#~ msgstr "" - -#~ msgid "📸 Instagram" -#~ msgstr "" - -#~ msgid "Удалить анкету" -#~ msgstr "" - -#~ msgid "Смотреть афиши" -#~ msgstr "" - -#~ msgid "🎭 Моё событие" -#~ msgstr "" - -#~ msgid "Вернуться к просмотру анкет" -#~ msgstr "" - -#~ msgid "" -#~ "Произошла ошибка! Попробуйте еще раз\n" -#~ "Если ошибка осталась, напишите агенту поддержки." -#~ msgstr "" - -#~ msgid "Под ваши фильтры нет пользователей" -#~ msgstr "" - -#~ msgid "" -#~ "{}, {} лет, {}, {} {}\n" -#~ "\n" -#~ "{}\n" -#~ "\n" -#~ "Партнерка:\n" -#~ "Количество приглашенных друзей: {}\n" -#~ "Реферальная ссылка: {}" -#~ msgstr "" - -#~ msgid "" -#~ "📧 Рассылка:\n" -#~ "Пришлите текст для рассылки либо фото" -#~ " с текстом для рассылки! Чтобы " -#~ "отредактировать, используйте встроенный редактор " -#~ "телеграма!\n" -#~ msgstr "" - -#~ msgid "" -#~ "💳 Стоимость разбана - 600\n" -#~ "├Чтобы проверить актуальность цен, нажмите на кнопку \n" -#~ "├🔄 Проверить цены\n" -#~ "├Если у вас нет Qiwi или нет возможности\n" -#~ "├оплатить с помощью киви, напишите агенту поддержки" -#~ msgstr "" - -#~ msgid "✔️ Цена актуальна" -#~ msgstr "" - -#~ msgid "" -#~ "После оплаты нажмите Проверить оплату\n" -#~ "Если не получается оплатить по странице ниже" -#~ msgstr "" - -#~ msgid "Оплата прошла успешно!" -#~ msgstr "" - -#~ msgid "Произошла неизвестная ошибка. Попробуйте ещё раз" -#~ msgstr "" - -#~ msgid "Ваш новый пол: Мужской" -#~ msgstr "" - -#~ msgid "Ваш новый пол: Женский" -#~ msgstr "" - -#~ msgid "" -#~ "Произошла ошибка! Попробуйте еще раз " -#~ "изменить описание. Возможно, Ваше сообщение" -#~ " слишком большое\n" -#~ "Если ошибка осталась, напишите системному администратору." -#~ msgstr "" - -#~ msgid "Произошла неизвестная ошибка! Попробуйте еще раз" -#~ msgstr "" - -#~ msgid "" -#~ "Введите город в котором проживаете.\n" -#~ "Для точного определения местоположения, можете нажать на кнопку ниже!" -#~ msgstr "" - -#~ msgid "И напоследок, Пришлите мне вашу фотографию" -#~ msgstr "" - -#~ msgid "Имя" -#~ msgstr "" - -#~ msgid "Пол" -#~ msgstr "" - -#~ msgid "Возраст" -#~ msgstr "" - -#~ msgid "Город" -#~ msgstr "" - -#~ msgid "Фото" -#~ msgstr "" - -#~ msgid "О себе" -#~ msgstr "" - -#~ msgid "✏️ Информация" -#~ msgstr "" - -#~ msgid "🖌️ Изменить" -#~ msgstr "" - -#~ msgid "💳 Qiwi" -#~ msgstr "" - -#~ msgid "🔄 Проверить цены" -#~ msgstr "" - -#~ msgid "Оплатить" -#~ msgstr "" - -#~ msgid "Проверить оплату" -#~ msgstr "" - -#~ msgid "💬 Брендбук" -#~ msgstr "" - -#~ msgid "" -#~ "Я нашел такой адрес:\n" -#~ "{city}\n" -#~ "Если все правильно то подтвердите" -#~ msgstr "" - -#~ msgid "" -#~ "Вы попали в раздел настроек бота," -#~ " здесь вы можете посмотреть: " -#~ "статистику,изменить язык, отключить уведомления, " -#~ "а также узнать информацию.\n" -#~ "\n" -#~ "🌐 Дней работаем: {}\n" -#~ "👤 Всего пользователей: {}\n" -#~ msgstr "" - -#~ msgid "🇫🇷 Французский" -#~ msgstr "" - -#~ msgid "🟢 Запустить бота" -#~ msgstr "" - -#~ msgid "⚒ Админ-Меню" -#~ msgstr "" - -#~ msgid "🗒 Логи" -#~ msgstr "" - -#~ msgid "Ваше новое имя: {censored}" -#~ msgstr "" - -#~ msgid "Ваш новый возраст: {messages}" -#~ msgstr "" - -#~ msgid "Ваш новый пол: {}" -#~ msgstr "" - -#~ msgid "Отправьте мне новое описание анкеты:" -#~ msgstr "" - -#~ msgid "Отправьте голосовое сообщение" -#~ msgstr "" - -#~ msgid "Комментарий принят!" -#~ msgstr "" - -#~ msgid "🫂 Пользователи" -#~ msgstr "" - -#~ msgid "📊 Реклама" -#~ msgstr "" - -#~ msgid "👀 Мониторинг" -#~ msgstr "" - -#~ msgid "🛑 Тех.Работа" -#~ msgstr "" - -#~ msgid "Чат с админом не найден" -#~ msgstr "" - From 252696cf148d8f897be1e92e789eac54e0da3641 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:40:42 +0300 Subject: [PATCH 018/148] =?UTF-8?q?=F0=9F=94=A5=20drop=20django?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- django_project/telegrambot/common/admin.py | 0 django_project/telegrambot/common/mixins.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 django_project/telegrambot/common/admin.py delete mode 100644 django_project/telegrambot/common/mixins.py diff --git a/django_project/telegrambot/common/admin.py b/django_project/telegrambot/common/admin.py deleted file mode 100644 index e69de29..0000000 diff --git a/django_project/telegrambot/common/mixins.py b/django_project/telegrambot/common/mixins.py deleted file mode 100644 index e69de29..0000000 From 57c93b5d34b352c0ee612ca72f8844c979ad3c65 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:40:57 +0300 Subject: [PATCH 019/148] =?UTF-8?q?=F0=9F=94=A5=20drop=20brandbook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- brandbook/1_page.png | Bin 64812 -> 0 bytes brandbook/2_page.png | Bin 118354 -> 0 bytes brandbook/3_page.png | Bin 85122 -> 0 bytes brandbook/4_page.png | Bin 40833 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 brandbook/1_page.png delete mode 100644 brandbook/2_page.png delete mode 100644 brandbook/3_page.png delete mode 100644 brandbook/4_page.png diff --git a/brandbook/1_page.png b/brandbook/1_page.png deleted file mode 100644 index a6811ef255c71155d96e5ead5d0cc4c542348037..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64812 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 diff --git a/brandbook/2_page.png b/brandbook/2_page.png deleted file mode 100644 index ad2ff2d094ef2b6f1423e6d11f185a0b829ccad3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118354 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

Ret;2 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�KK;=!ZiTGebpnC;%e|coKDr%`@B|sI3BC)GsbR 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 diff --git a/brandbook/3_page.png b/brandbook/3_page.png deleted file mode 100644 index 0d9f882f0ed45575cf35d456c4a421b638c6a8ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85122 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%0AA>`Ee_zUQpax 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 NpEv)t=-kcU{|6s~a3=r& diff --git a/brandbook/4_page.png b/brandbook/4_page.png deleted file mode 100644 index 2aa80a4b6cd3a8e49f7d56aaf9dd79e0337d6f23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40833 zcmeFY_g7Qj^F16y;RPay(yL;@08&EP0fmYXWF=^S}D}Uh7vy2frH{ zWa)3lLBigddC8CmG;@AP=zL5;ajHl0$v&RmJdN0RYRMp@6ZJy#!nI8-^7Xx^0xB0( z#R4us?uWgb!MadTOg^Qcn7K?tW7K~Y=}%@WBK-9k4KHvkml25%>D~DJuPX+@0QD`LxfpP*vDM26&I#xR11};%95a{89*RsG(Z`(b9dw0KV z0)PL#`M(GLXQlsz#{cl}Ki2pkRsJtO{QoT}T9hqQf)1*Zw{6cP2r*i|6Y3sVAwQJ- zg&Zx#nq~E%r{Iwy|*5|{>1yozf zt(@<)WG16xw6JrluXAET!2J^W#y#)4Kl+do1}O$r|qbiBY#_7 zJlXoX%QtCBm>Tn7)xA4e9j`As*1cGT}ro*qtF6PpE>&_%~+fi07=g3M>h zoQws_%j07PN}(O&5GX-{pri5}dwYe<@mVYR%dJ`o~#^h9`%99DPpKRjg z&13lSp@{NC+DrW1@CXicX8n1`TiV>s%{6hJ#Qs9an`J~Wy@;; z9shtQVc5nO?yo9cISH*zYWN7NwoS^45e-d$M&97dH$OW*nQM<4{JRxe8ZY7Wh?A}| zYAp-SCvgVih|)yUnXbnhrFLRfKVGFn^{r5|4a9Q?k&dFJD1f{4`>1$aj z6DT1%I$En!Dje<01S)9EwJ))hk$T&MZxk7uOoY+|ggpQavSk1G{01daxCMb_GKfEr zdMwJtky37ta8OAMPDg?UN5|U-%2#d5lCnC6U{a6wY<|;sKr2@HGK-+sr&&tvaS+U6 z?rSO0zaCBkV^)Z#3Wv1Q2G6lNDy8;l`w9?8fYmS^sGFb1rH7YS(j~ghS-6#)BQ{7h z9KR5CA0;vKGzb4^ek{vA$tvl|67BRG7?vS&%-vFwl7pO;E;km@Qw4Y7;>}(Mg&Tvl z!B`u&D6MPTUmf;r9_PE@3S3k!aDiUpr7~jlS3B!qISHYZjGf>@y*#a~AB*oUH}I<@ zswna=JgQw8{4fTAMI*#u4xJR!yDEu1D!892QmK@TQJY`+VAzFZ-eWN`kMtmxY%_$c z5Rbs+$1va-mAT|gvY^QhDOw<=P*)%8daEkj8Owr!sXRb6(U^w*a$dCW)#DT$BYBz&?Xj;7PHZ zD5px`W?A}T8};9;vLGuBa~1l$y(dvy)50AdPEUEh#|%i=f4;}(0qRa~X819erHKhp zxQ%bTIcnGcyy|n`T{3^xSW)pRc#>XsK)@}0qgiFnbXndwB=e0cZNo~I=)(Bi&m>C9 zC#?ZZUl5ZtrNEX#z?OC{CZ7LfPYFukQNoqen_=&+$g|Szy-&vgr}~HB-&j03t8G@iaK7{6VqOb^iyN-+zup zYtcL}8U|L-&WyQR5y=zvjRE)%EfrL_C+$MxWYp%X_2!RrEL@=DtmY93-V0nSV!)zm zJy0GsJ;`8y%7#<0NI51#Gl_*rW}?R9#XiBfP(XbNcMTf1k;XtUI%(c`_`T#eE8>cp7g~dg8^OfYRmP2U^`B&OVM0C_$>LLr^@z%T3 zWth)AT%g_d;zW~R&mslj;*%cFq^x9pU=f{+QcMQ~23!Lyixz=tN28K~diN_&B*MmJ zS?Bz+jE2hRpz)vMIaCkbcnDcBQHaUi=!QtZ&y=Ky9|5lWxIi#AVgsv3n$u4^Jc>#~ zmLZ5}yFVAdJ}&+-WiOGxa$q=p5BLvqW>)nE@~`ECV+|Iofl7 zs6C4KMY#m>W3*H{o^jIotIUVk>T`4iqyWa!L@8mi;L%;Jpd9@kFkuM8UvwR~*G6n? z{?B3(=wXXXcrq%e?-5xX5U(z?TG-N2z&JKc7)#FHMp*-Zqq;_|YLZ{N$%1U+w6UcH zLm$pxOJ5AlC|af@^EXuL@hmY$469#IR&EWLH?sTFZEr&ZSV&0gb*8LAD(M9@E>LUZ z>maEM{iWr$ziEMJ$WgCXJi<3_k*>K@ zi4lfOV4(1ZEF}}I=75iYF_MGcB((yY41WTFSrpzC0OI7MQK(ZtV9@IZauE4h#m&8q%9ttQZT*-(_ z6R?>G=~lFFq7RnL3F4w+?{MpTOjHRBRdf{#GiX=p?t*ExkxxIJgB^c(3Wmo}hD2_@ zqKaqN`Du2+$(7Bz-^{RX{uLGK3ZFfW4+v8umk&)IML!3D=E&a)))Ah^fQu|Ld@cy6 za?)8|v(zga#9g=4J5LiVVNSXk+-kd{xow75WR^POe<4WfV@ep_w+D%QR3e=Az@ za&oXo(GAi~W#QRm(jeu#<=Oo$vId_*DNAt4Z6QG`TW>(&FtxkMMZ!}q!e}iOMWZUu zOQ7&i-{_bCA77Io6Cw;5P&PPsbfoYX3A55IxRt$+7b=LL@V6*_5G1(JX#QF@VR%n? z^6tv&ku&HO9Oh82FjJ6rDg1pXrD{8rklaMFzWQyPxS6IXSNEEWj&=+AG|N20QfF(N zu{`Y`b-?sr_Z@82ulsKynE?CxkRKqx=rTV&j14g3KM zhXe(+KkFo)_OML9A)rLs1+&1dpT-xQYC_+WT*vflw1^7S_6z~URw$)da1g90Bq-*# zEU-&86f`@5vI~f>C%8`I9>Z4h>4c98mx~xkTi_gUQK|k)4ic6GJX<=N=UHqlb|({f z3~P0Q5ax^n_%YN3>hUUo8q{VZb5R0j(RP<)1y=jN9u#7raHG2M4lR|@)#wHh(gG-d zWR-Yk87X375Vz3~vqyk=?g`qTH43ItfZT7Ha)MGi1FAYagwwq{-1!Q;N#uPQi@{}AvzOX6<&)VO^7pJw}jQxRuW5*xww@9BOvQQ!U$cU;#NhcDt><7^;o%@u z*?}iJ1~(3g)S631AU2EBc01lF;D0k3gah%&m&r0PqE6qRl1(QC9#hO1d0nr6?NtT? zID6sOVtmpYmrj)N*ZBBszrJ+xKtg9L(<`fU$UPOyEjDeHi~W%@BiRF!YQzL)=VBV`fX$R|BQly9BQk&ybGDQU4l6xzeAQ{SJNW577X#` zP1e_8J-CY=n<4v4^%oBky#ZhyI($3?V(QjXI1xgt*-HNQjKBaLXu+fvj>Vkv5K0dp zVdnt*WyC}uP`3$dA0kNdBlzDxx95<^H_=->XG0>HG5$isG#6S4{NIvW9iewA6~LHpJ%JVKy$+ z)rVLJLjg`{{lnbx{R@~nOUQy$07E3 z%Bil6)LCo+PGm&6=BSG z=ctT&vcT^6l(>>sU$AZ(jZi@m)0pcG{>z|^PyZSL0ir#CwRTK~%pGsjww?F9)P2?d z=yX1Yrvjzm{|rw_JKEAVXm(C!+oDm$h^DKSrK;OSHDmjZZ+)if$D0=}B#hUr91~KS z>Rh z1fN>P-yIAJf~(+JP!p;2_DB=C(?9yjwbz9l6Dh0!N7Ve(`=x-_$ZGVeQ_E}?ZqDO8 z(Ne2=DY@F=>SB%*Jie1{RI2Wu$#QIFLLufpk3qtZUUB6FLBd4THYJBk?fjmY)VJqN z85*0{+eO5`IQnp@Qia`$APcOXuf6DD5~=llz4NGsrb&phCM0b%wbldQ>pySIN_8bf zSkg^4A^wu8yE#iR8nUdxlO+Okyf@yll%w2C~=RTwbErqa#5}}VRii%T2KreSK9t!|34#T7C zB}|CFp1^xepQjP^Mn4eEAASJ9RrJA0FgWwMMWyQYRbz&=tb155rnOUv4wwa6E={gq zg>1S#fwCdAyo&&}z8o)bgQwcGPBoh7+K+cg$gJs4ljP+smZskxx!jY}1c-x#e)mo$ zdc#%=D*z6+KH5sqDkugAIS0XycGFJCwnpKHClx>7&wq|y2I^seI1`4uWb|KIVH2`u zhRIXu@I6Wy&Y?>W)W&P4xFyS$OO~R0Il3+OU3%mKNoKG@iliD=zE-10g%j?od)f1e z&}uG#$7{_NWL+_TUk|9fYX_Njws?zl;Eq80;c&E0~V8o6%2s5jAZ5UEEk@CKdP0#) z)QQZhgdhfbECaSOq)W%@i#Ebn+UKYWDyd2pZ}Z!Qzkanj4X+v_t| z)yHcyiyhJ1n-=*D2AzAtsj-(5P@$Agubu#_)6?TUgn8E{9ec(Laj~R5BA!op%93s< z5sPvT2x$P(RKhcfT8p4?&PUHf#eZEX7W;+U`a z#DPJ#g$a)QFA$>R=OHbO+YQhN2wEHtLqiHR6+xsl^iHUacaU@FH|7nw zrx^_K@jw3TuO7@SKf@QjNP}lK7)cNYI8FRXu~8Ur8)xVzJBe*EPU030liz|4c5xwF zkTWP(!s_2AKlR#(&SnxlLJj9axl3F%fNec*U5jBbc3)E^)Phf&Skh@5ym`2ybckWz zOB4&NSxqTg=6y#BRP>Q`d^bGF93WjzrHmiVI-{S_O3i;#)56kxtzhqLks=Ii*Ud<3 zcFhGdkkcv?60{~;rMkTudkq_twfSFmzZ%?vpBfHdGzbPV&)eN{ zM#83tb%1e_qjCh=Hg-7saPIamJ!+#g*sm@q_sj(vpr;zZTBRRNCjztdmWZN{%ED%SVt)mDTb zq=cKZ?gERZ&Z;|naqP6!75^!rEzlXL7=bQ{XDvrJt$QveDhvE6echuDJRTPCc{;Uy z0*{IlUUIwKe+V^7AT+ ze|N%wYA%CPdVTIKkSe}tnJ0~BY@IS0s3d(FJn}v{xGp2%>G-1M9HIS6)-@rA@$bSU z!ShE*_?jo<;cRmX7n1V3yrUj}K7)t4KBc0J(JKcM_+!nUH{;_0=*NY>j<-?_?!&Kb zC_)I?Sb}LKb0ppOE7IpVAJFK+Ve`X)z|lIp44wUc`Tg$+@s~E`Mp=6&b|*Vx!1C6I z1Ol@^>mWq-3NWAl_bq^YrvJp4$qlRL`1qJ>7W#)7RWU4WI(JLZhI5zJS2?S#q`1ej5v?EPpbJRwG-1;8t*F>9X=nwPJ}}n7eM$ zlWshxkQ{t+iXL9nsvHgbDMb58yG41Ik zLBWI`D8a~X_){Vv@SW4~NEM@Grop0>?PB_K%DtnonBSjeOl}pbBaU%z*o2^yw@I8&DbeAt z@OnNfPv!H>S9iDrH8PqDdGcOV+#2wDedMMGKhPH^)j*GX4BKyIGDKEyyWRs;l!;!~(oa@}m{KPHl!OiXg&qg#tt zd`2tKkXu?G@a$b>gXOHrSYI%mkY?p1Zk!fVD=8zJs+!t-U$wP$sAwPF{S*|tjs?(*8Hh<+vawE%iCFZY>L}rGky&dceUo&Q)E; zs!QBEE7MXz_%5VBRe?IHX9FEOAWItrKkMla3i|ePag~i;-Is-Xa6ZIw_2Z)wL`3Enm`c` z6UjAqr9-I@Ydtvl7VX@ue{~rzNr;3%L#tT0-*9w_q9NJWrpA}kPB-}X^IQq@r}=A} z6#-(JD;hE0r@whOMu3jU1ya!uAdONieN5!uoul1r>w{)NFa3<=y^i#br+5ai>uosG zz3krA_&f~ReNI4m>uJf^RK-Mbv<4^vpiYlxE^y-bq$Hi1CYQG6UB-2Ud@0TnxV7PM zh9Ua0-Ivsv`)VRQQO#PaC8bB9Xb=VDUz;Q=uP~ND_9Ic+l6svqdDPm(-%0Uld&LZb zdbW%yvUjQg)PC9($hgImR$rvMp2K?Gj6|pHPcV3TdWGSh7rLK=+NNme9FW`xaJImM z1z*22y(y$zoWOm+_J0GAH8ES~1pG!Ofpsu<7zsA-@z z4PpSjx)~`Wf&11M&$VzDPv-r_w+9Uw&YoW|?Hwj;n<%l|dEhBKdH4%!G*iT}Gu96& zVE7WzebdimrjNv*{P#_-I5H-+j=IAac6Qn}_w<)rhS0}yT$8KywS`~X9v``h(Z(@` z2dcKef&%thbCDIx9lXm^4LoqV;c>9wP`_bxkLQ7x^yKL;uT4AAD;DmNy`~VW6J_&b zH+PToLcZp?Dc=wh+__t?l-^9lP2dBY%FcQm;fwg0iPM=0-@f}k^}52kf|*cxEFIXfj%#;tkM@2+9=98LoE_5;uoZ1(2~al`Bh(X+|u^aTKqD%U!T!C&nwHig|WZs zM{1rXWmU=6gSr8Q!OGfd!$?o8uG%wBny4N>&6Savh(BC|WUkk-a*eYM*~T(ix%y_s z{x0s*`j6D$tAVKH3!jY+1GgJX zy0=tlhO1S@cpmtU$XDV_haIKT)@tko12i4YXeT^nXw6M9HPWO8c=wwUO{}=N_|maX zfn&T?%)y??SrZcPM*LI~va{3SaV(F<5*B8SD%!1un*HClw76soiJS`&`&Kcf8k^82 zx5-Q>jWc0+Z7Cw~`i9^Y1=$*$52BEJ-wgq#Ac5 zOq8t-tPIVs=`S~}(lAA)ol36ZyIptq0Pkl6l2nQ?(-$wF)={Kr$X-FjA^g!s1)47n=7qfm!T`D-e3ZB6;1SU zUlEmA-~vlPRGn;icspgI_A)6a#ihJNCeT&BN3S75gtv-_z45;WZd+Ym?cUC2Bt2$Z zVyxXG^JaKxQ+D=P8IN~7I?9sF%xI$vJgzuyn(*l~No>uM**DBp4RSBy0QMJ;5Txb^ zTzLT~6Y$#P-+DiYQP-{2>|H}oC-m`Y|MYfUXmxye_4X6~k4bhW)J(GHtN@rFkiWRy zl8w|(2JXpCZw?Z6cRZEYA%2g*OC3mjicrL?d|aY9A=_}Ca{2)PB^k1HCnd#-9&Vts za8fgVwB=ve=S|mim2Q1+YLff$qX;^Utc8HECp4jn!W`>o9}+n*GIt71U-QcH zU)5hT61jU`=}olcSSiZfqZj#!2;M*W6$^0tD))n*ho53AkVoFt+w(_D(H*0FlL~+* zc)UGmjeD5;9dM7x5+Z==6m3uh`OX>R9Hce2e_Uo6?6|h8o)$WM{FE|3X6L}n@65g4 z{pP7SH1}~kP_m5i5~l4@ctBZ_O3dLdJSE!q+T zFBOLOYU#Sos>kyZKRh7y9#w@?IX?s~PFloUHKzwQ;Twf&vZ?BnhHY2B9}g#xy_e zEJLoTFl1n)*z-#PZBPR+Oi9oL-?1+o8sBZ&u);DZoH-OW*YN|=p=X@ag)<8b0KFbr zvOH9$f53#)#ydWl9z12%nCh+~j)yTRt_ex}m zy!?G-E{n;P#j`1hb@q(OP*W5_uQ`BJ&Zp-Eb`5Us4MF1S{BVV8ir!~$4i-!tU7&fp z)v}dXpMA`$!8B8?R(GGQFSHwM=PQYu|Ml6n~^QfmNODJ_XhgfaO0VWcsl$9YGN^NkLcts4Ui`hzn?DS zSeYy1_+;knYNh^lQeL{oGnu)~wvDc|nIHD;K~B+5O|IXUUXK+8G8yOE)Zbx3+wn&c z_0y_1L)O%afseZX)kVqM^QBEWX z0*6;z{NlRiP@cVtD!{lv3jj^Lc-I4Z`U&aU^1v3URzSeeO@(#IGa?eQb{2j8*7Yh( z^i#JF?$BeFF0)_Q@1DmE7yWnneALMLMDfj=vX^)kOK%vxBS$Ck<-|4~+U5osvYtQ1 z>_)#pXL_ZaB#D+UYiHK@Tmu`^hRjO-8!g83p|YNRDxCmp?=07QPRzUdN=%&oLVUE! zS9vz&Co_&7O{>jWRTY@64Xd*SBm9D~HNTTd#-z68 zmXf7C{fUwz6#vG+(a*$-$0_J7#-cSUQWfb=QDV#*2niFKa`Dl3AX@LABr*~$BxyHS ziKmUL4vTbKUf8I9nUIAQ&rQBbO1*#Ce9io+7}`J&EaHmUTNya28Lu66Gq&I7-q;pc0vILfh&wSN z!F$y#YmCR5fjQ|V(rlmAUg)z##{D0rgdnTr*~$FV&-@TiTVl1hSE1j|!$*Zy+EZ^b zK{}+b0NOo5SOZ1FFjCDEh1Dg@RxUND{|7K@_5Ga!;(wMkbTQ}>ceYxNIHv#mvH_TX zFC8?Re^;EE#}cslXGTa{{Wn{w5*Wt1D(t!$;$w0&CW-qVPg)8!d&uRkl=BEEeTIaU zRGnB1t0OKR$vyC5+jm6b7yTr;|F--ewie?|z5!v5Fdfb`+&n*hx;>}kFr5oS>o?gN znTh&A=1Z~#&%jIlO{b^%4nB3tX?)Rhw!pvzKRsWQoIC-Hxgt0HRmO>L4`w3S^(uW) z3)Fz;szWjb4_7|wQFm2UkAWA?)kXh;3C38yrFc>L!L@|0td~a(uFLBBLG5W@(UH^BYxAbcI zO;T?0G-e$LwcoDZ)I2UcBYtA1Owy-qPop3{0RHE}&Gf`#B(KC%GakgEbGJdEfd5P)T9iOw+jgzIFw)$l6t))<}*EPzMCWA(7lNt5Z65c%CsffGR z1hQVnA6Hb@9~1wspA{w5er?NhQOi^ehNy|`{aEfAX_GpA8^0aRj!!#XdYu?3>i|SD zN&A-?=el-VFwdHv9(Nr5Oh&7`yL1jNp;TD?SzC0kpBHiXEP<;&Mmy%7Hd`x8AZD+N zr48vlvv5F6Lk)JizM+C)S+1C|n%D@Z!^n5KGOTUNOUuK~_Ufk&xX+N*dX2x21na7X z{Z5sRBmCsxFQ$%%?`l1_7`t6v7x7I9yYr}i#=4%>cORK%jKyy9OQe5e7A0}?s5nep zrC1-qD|ZSZ4*S(RFU!je(lG(`mV8Q2YP|f(;hdyt>F_?4Y*K`a8a-Gk)PgN=5iLps zjQh3l(Dh*&@3+?cP7@5le+;8N&kWR(?M2Nx!Pn(4FWx;uw<23lbvNm&PbzGo|N zV1rO-)y$oZWeE&T>*;JE-Zkah8Y2`4y7BU)r}vc(Y0uk)5=(-DpFL$XRSt*GDxHsN zTx}`0N5V>&v8zJa@~88yj(+>!F4Z2X+`yJXm6?5*eYlHtR1bxGC96~s&zM6}c75KP z2?618i;`|n(V^4tB#oOGab}%w!G*pS$@O5-@9n_Jj1WMfE*WL%Pr<-QH`L-US~OgQ z241Xz9Q^zG1TR1FMKbRHCWWNewDG3R1Cbu#aOzgOOgWR2-`fS`QTdaZIpo=f$;1HN zx*IU`>Y8*Fp%n=)S*`WwG~l|y1$5_7K_ zm!H_mk)f20CaG?>RtJvfx!XZlcPA{=)hoILU(*~AelleY5lc{M?opV?^F2l{h14Sk zI|q2Gbgq*S9x97Q+n4}b6x4&TJoo??4(^2GRP_g?C^&}=Op6}GmJPk8*7)=kGw*W=wk zj$4P!?(nteYMwGT_!j@KQfsC9`{ejr%+|4dnNaGK$IjQO-I9kYV5JOzVp4>PioUKd@ zBiL1w_78cx8v3HY#}98>XA#2o&Ckw`Q-UTEQ$8=HE+VUYm+-Hj_LJT4@9ee~3ceTj z-?i_z^~j!j%QI64Q#|+o*m;s^8Mjb6pOSjp{;SPv*{_dVxXfDMYxN=uNAahG`n?KwECxrbf3SrL`nzjJ;hvMIi@LAKt>bkE=UpnhswGgad8ge7 z_LYM4u3@tEzZSWrhTn3-;$*!BPjG(w%-`OHK)>^~IsWQN#65WWQnjB@@gXdSncl5H z>RXSD7w&y^pHi`m_gIoUm}%m-v3r9Kkm_vaIphA=i?u^55MNw-*d2YX&A6<3zOMTb z;YD?^vSYAYFx1TbKJK@&d3^G&_zewt`G zG{!sy5fZdp=jmpAnqZsj^489ad9B~1x|%B_i1o53-*=PzLA!er+Y(rT>){mOmoG5D zuN<spOIm^!UJF7?c3<$Ieu+i>Dn0{g=Y)+c7@jg*Z zMoZg!OZCk>+{Rx?pz_VTw?(PWHvZ1~B7usm4EU+(V0CKDACXY+a>JE#E1i$tE zzYX1gldD3sD@xLGbCDJm*99kJ?6}{gC#sN-Y9o(2zUTk8M;y*vQ{1i&_8LG}MOyaj)>GU6+tu@*6uE6Kl z9<7(ELL|SBtjWW#-LcYe&b{jY_sgb#r*TS(@T&Q%3%a^)Q^X%BLmqn&;di-vLOvDv z5TUjcPhgaG?9@^&&S|uS0Mnz4DeW~t`iWYU;q&8A6 z%X3WH(eNMFPpW$jIyi`D8&i8X6nT*40N+#Q=q)+P{w+7O&Yu_y=PnVZ)BeskWK)4Z zCgK96lko#5l~T?tnb3eO+xQlbhd^)X;m48NZ|ib$%hr~<7LM*}*Jw!EmJK6n3-*z8 zL$HHuPj>oK;S!?~-g`OMHvGqc(@;Az^^I>%h8z^uIcCC6U*~+@f9tvAWxT$kT!*cC zK@I7Bp*ot*X$9}OZIMI$j`WChQL}frfi149e|V~7UYajEH{jj6(Se;u;meE1Hfl^Q zYnzuZO#ZZh(B+T;XJE4a+I!1(b;14Z!B3IZ_cx6A0_uR&Vu`(>aZ8j-qbhIPWT`BfzXwWxhKX4HaD98@VL>^j#;JBPBTV;hQTR**F0q|c z);=K!xlE%SnWyA=lIY(Fz=xVKS}SK@A+oHXA2M~S?Uj1^_O`#_e6q=&QQ`7Ld{~QT zxOycf-FT*XyGlb^{lAAFNrn!7qns}hp)+V$ad;2vJv3by*qfCLoXo9;I8vUt-?&MI zf6+C%$5jHcPH{=Ss&OmsE_p0EDd};hL}2oMYn8QWSv9BM+moyJT?MY`c6>S&8=9CP z2F)gCjl)#Fx36+$HGlGaXv(X>-3lOl>Az(RA*QT{g}BhB7q!LQ>@&YqfS5TVQaTLr zDbM*V_NN-YP$R{T(InQh>>=mF7GRU}z_C(*V}<&jn5xn+PRsoqG#NHF(c7x4h(mRh zS#>$AI<Ds;Og5lkQ@BI=@ zZ)Am1RAp;#AJ6`9{F;>%h@N^F*+x9@$cRNB{5h%;<%8&bRN4FT3PKR0)){whzmfCf#34uFT49dbjTG9XBW< z(Kt#~^@JY>eFH-?9NgBA7X+9={A=a~o#pDg>1Go9f1ty5N~S(j=5JYUPZ`|Vp16EA z&7SwXK`J$fC7rVO+TTNIPCZ`hk`>X`g$Xyy`*f7q^LG9W zANQR8zV#>ID@w)z{&|18=H@V&I1gZqPC7N=Rt@yWn_WM1xnynS#w^Vy-@B*@dDZ;} zB5KQ_|2QOR)y_KYdM`oUgi2KNdJZKJ@8s5B1Djv4D{cKf*2}}(cP%!u|2eRUm2G&) z{8p-2(f8~bn^Mj0!Wa4(;hSFt$M3xB)Xcop>g6^h&epFlUusBSdPB#puYSzH^6#BZ zadoZc&sek1vRRv9pK?y0_&H|=qec={S-ZyH7yBz9@J4-`c z*h{{=?32Vf*arGb5_FWBSrqDdHOK)Nf2$Eq$BDigdZBAP6`bbRB>Jt2P`kGsrkho1 zzo5*zdD)$&TEv%dBiQ1F0LaO7T;v_~QVvcum`BSbXX~TqabVc$1EvKGzb+@OcOgEo zq`0{AwL_!mwMX>L0AW%e}lhB&}=wBCP(f7^9dmN;pq4%LM1= z-IzHmVO@i%t*$(p*-PK0xYeJC-};wm=3`mKUyN+AqIiCJeOcqmzzWi?W$?v6n5x<^ zlqg((Hu$kJE#nHhNdPn5w@&ph7IdQTKTNkPo>vA3MaEB+=?%@onJFcI{QbHv!|dRT zHSY1ca^`OCy8bNYc1^@%Z9B;atM5VYNYLj?d8bxkd9anvFz~15s311uq{aGU?(0N= zs~ctQP=}hif5|=AuIJlJ?+wtUYa&&{juc6@fzi&@Y@|duZR`1HjmaDr=r0iAK zm58O6H8XFQ9Y;`kn3>1j)u02soA0efT{bqmVUq90^Xu&P1oX|tX#Ryr1V9wEhk*S= zCA+dq{chs-x!RIFxH8O}82>7MwHa4<2l&fS>ehdilmw$XG_lJOl_$`RqR?I4_o)zT z10cW9#My=$aj|8$E4#+SST*@Upj+~;undUyc1MYYKGiP%w)aS#Jc#N}fa7P@o7&?siP)EoQ4aNhTLL#GA0 z>TXnszPBXEJSiyp%mzsZ?6N$s;$FJv!z*tPph0t=LmutgF$=M93=%lo^|yuGTE1DH zh1aLc6S)};zKcKEPffV}4R)=3`SX36c`{wRFSX9S!q%K_ro{b8s1Q&7{J}E!iw{J~ zZ+yhM_2lCjr$?rm>j7h}a5rp0zL=_*w^BAQG4ZOgc*dKH@P0{RU_#;9wMhoh#us26&c$*gr`*3k86%}6rp|t(6bY|i zhh|S)9$vmbJAlR=KGZubC=gRc6n}7@xKIMjmvs2mk*Uss9|<+R`yFq}zw1wxJtgkX z{gZK=>))<1Ub(T>`Wfdnkl+s1h+a0l-z@v}Eg&;!TUowoz4S1Qn-4>!1ng|E!f+89 zv5%&ZLf&zk3so&A713_%#m}_E!;J)#22k#g{2Ktd&}qDDsFiM3Dy*)UT4z&WKTutp zl1ocbhnS%VsV(lj_?K{c@F7gOy8MxP+k5kSbUAE5QsCf>yXUc9TQ4mFe1eFA2iW4t zbIrx3mJc4KJ(*TM@J!81%y6(4nxCxH2z5%hs=-YH%kn3_D|$iggBq#9?E@|+gLL+6 zvX=;@!_Kv-zjoH}p;D6d`wSCfu^Eppzb=8F6XU(sRo@Zj0=di{rlX>i!iGUEcz3gqC zn!9&ghut)M@`ngPR2Nz?920r3tObKKz`)k->+0&VK`I-e@klH7TT^GMGS8UTs5K*J z8jw9ir-zlt91eo}M*S(|Q*VlJeUHDhQ=ZG&IFSXL>bryNaykOtwvcqkGdSG&h)m}W zfUNP|;~IXsG~~AAC2#nkgM9^{WkCON1Ew3=P2~^4erO{CvQ~e4RiXml*2`r6F$zJV znl8_&L%*XMR7ntX+nWFNUgo5Z>{8+4OXtedNKQXGWm=@i%e1+MFa;S7kQ3=8L8<~z zaJ@PDGZF2>?@pW9Pjg7!_CS;Ug7^&*hV^EDN+F!$texbfl828$NweR37>d&Wc=S3EBNVMCb9DFT zM^6AE2M)mCd1pdiST`{~$lBcNp!8V@S4)S-IIVv!NrCzpg!R`ouIdbiJ*ku^pXC)I zhl&WXAFX{{--q|;n)^z)4AchPvlmz+yUAjPxo{pHRe8D#SV0OaL+6sSl|6ckYS$eX`0po0p;cLqcO+TXC*)}#*zR9Kt?K=U+4#TY?ruFXwTIOF)idf zwnmsj7G`rb2mj#=qu@*~iVvhI__=h;3jSSq%<_c_st}aZ?k#i(T4;Y>(IPDN)2&re z^R?pKVcvq}Fn2vB%Q#2ohl<2b09ncwR;25?d3yW$12uxJ02`w=E5;s7%XC45{zwx@ z)`Ki%tZN-(dFc$z?fO#{FIEj}H1kyA;1&202}H1!M?sd{*zl9+T8QTFx3XWKl#IB7 z-Ycej^5Wyp{36jkeD)_en)NS_20dZt7eUagMel0@tz@1m_e)iGuL|*1tyksVPBB*l zJkG^-nU45+Nyg(QBJj&Qbf@|AT%`goptBH5Z}KRBgxw>saGXsZ4t{lJ0XzTvGqnj>An?w zmdP`l7g&%~PnH!>Y~~DXECsWo?nV{**@?fvRt0Z`=}MDyU#)EMh?SE-#%NEuyVO;> zXyG8rKfo{Jbk+a>#%J8(Rh0p z^@V=hKbQoC;&&`{$+=8kc5JtV>g`M8bF1C*Y|aLdLNzIw{WqTr-@<#`b@7V8N@Z!j zi{Ovq^8f23L&2vQc!%o)q9>xfLw#To#f@`5X<&{ev9i}ppaPreb~C2=kgB@ue!jaX zP`c9!>;Kd0ObVVJSLf3i^dx8Fr{qR5D!p;t0922puX~cE{qAj$GC_2#aR{qk-+w&J zd#+%<@l3N?WKCUdlGPqGkATsSKCoMPa5?~>+&q6zdTmR~+S-s=uH%nI)#sB_{D-?T za=B6*5o+rH%AMr>U`ic1l|~lNLm0wy9{A>ea(O;;9S{}CC0MyAUt65eeZ&neIl;8d zL`qq6E)8*#B$k~hJ^|6Si~)vpHm^JSmXeX?4WDPva}_-KgD0y?R{Q_h`|G!;zV8hf zKZuHfqM(G7fCAFpVbI-ON_Te*yet|8=@KdX4D3>-PHn3 zVN?5`nnL{#chLR@UG*KdihDoa3cR?j!E#|A80SRGV*Va>T`gy=4VC1+;;joTGn~YUVk}E( zJ!t8O-0)K>DI&+Qu;~Y*T+prh9KPI<7&Zgc&%I4Gy6!b|T={B2d1ck%Huk-wq|eLN z0$&C|ZL{QY}a@xYV$EV`zz?aIM~(uEl%){SjM2;-p>!qc&baeo7$bQefFO# zr=_%{?0z=>xkmHDn!f3D_~l?U`aMDigaRiv&8<{X@1G3Qw^gCzeoqA=n=3tB4|FAC z>snX!5mU-!BroUmLeE3RYj^pzbW$_^6Pp~!i;)4{0m0tP@%Ja9i8kV5K@%cD6JOMt zY`51-B|~galcg5EUjj^Q(RO*lC!fQ2K)K!(-VD2U9&(zZ8@}0HDMs#}u)hBEd}*xy zH-EaeqSHiO{C?@mxUa{Qw=K)|9QBN-uVMbHmed?A(=H5=dT!7Ko8-Li5N*L=U-Angy&q-%=e zPqxnZ$|E?hU%90;J>YXQB`<;qv$%9gZHH>sPcv5u?EKP^d+9gI zb@i6iIY|C_;$hTOs0%{1s=GIc%HG)M-br1p@$=^lIVcvT+1#j*l&odx_7VA{sQ-{@ zUV4Rn)~<1`ql-}LzJ|r{-E2AlQ9S(N4<(Ze&6B3&m||Xx8lCjKUnl9Ov=ClY`L7m$accV%`^U~#=zcgxJOssl}kLVXaopDi$ZueZO z8(jO(n5-}VM;(Z`X- zf;bVDoy_B0-%-HP@$6ybBhGW{ckb&;3cCsic{mMiMg>%6T|3d+G-<3$-#vAf9G;7f+$;o-TNi5hFMd_2{#@}+ zXWR%>nv~Fbe@?G)tQhBet45rbLvnpHbN-8U@8oySY^SPVQ zK?ax?*~B-7?*OR!qzOcG$}c9&7dzNxQ(2K3u28d%I0FlwIxZh OO^{RbuZP9)z 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�*3}XJ8(nV9kl(3s;}?SK_u7w&es3O7#fI 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 Date: Sun, 24 Mar 2024 22:41:45 +0300 Subject: [PATCH 020/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20interacti?= =?UTF-8?q?on=20with=20db?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/db_api/db_commands.py | 257 ----------------------- 1 file changed, 257 deletions(-) delete mode 100644 src/infrastructure/db_api/db_commands.py diff --git a/src/infrastructure/db_api/db_commands.py b/src/infrastructure/db_api/db_commands.py deleted file mode 100644 index 47c1818..0000000 --- a/src/infrastructure/db_api/db_commands.py +++ /dev/null @@ -1,257 +0,0 @@ -import os - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.telegrambot.telegrambot.settings") -import django - -django.setup() - -from asgiref.sync import ( - sync_to_async, -) -from django.db.models import ( - F, - Q, -) -from django.db.models.expressions import ( - CombinedExpression, - Value, -) - -from django_project.telegrambot.usersmanage.models import ( - NecessaryLink, - SettingModel, - User, - UserMeetings, - ViewedProfile, -) - - -@sync_to_async -def select_all_links(): - links = NecessaryLink.objects.all().values() - return links - - -@sync_to_async -def select_user(telegram_id: int): - try: - user = User.objects.get(telegram_id=telegram_id) - except Exception as ex: - user = User.objects.filter(telegram_id=telegram_id).values().first() - print(f"Error in select_user {ex}") - return user - - -@sync_to_async -def select_user_object(telegram_id: int): - try: - user = User.objects.get(telegram_id=telegram_id) - except Exception as ex: - user = User.objects.filter(telegram_id=telegram_id).values().first() - print(f"Error in select_user_object {ex}") - return user - - -@sync_to_async -def add_profile_to_viewed(user, viewed_profile): - return ViewedProfile.objects.create(viewer=user, profile=viewed_profile) - - -@sync_to_async -def check_user_exists(telegram_id: int): - user_exists = User.objects.filter(telegram_id=telegram_id).exists() - return user_exists - - -@sync_to_async -def check_user_meetings_exists(telegram_id: int): - user_exists = UserMeetings.objects.filter(telegram_id=telegram_id).exists() - return user_exists - - -@sync_to_async -def add_user(telegram_id, name, username, referrer_id=None): - if referrer_id: - return User( - telegram_id=int(telegram_id), - name=name, - username=username, - referrer_id=referrer_id, - ).save() - else: - return User(telegram_id=int(telegram_id), name=name, username=username).save() - - -@sync_to_async -def delete_user(telegram_id): - return User.objects.filter(telegram_id=telegram_id).delete() - - -@sync_to_async -def delete_user_meetings(telegram_id): - return UserMeetings.objects.filter(telegram_id=telegram_id).delete() - - -@sync_to_async -def add_meetings_user(telegram_id, username): - return UserMeetings(telegram_id=int(telegram_id), username=username).save() - - -@sync_to_async -def select_all_user_meetings(): - users = UserMeetings.objects.all().values() - return users - - -@sync_to_async -def select_user_meetings(telegram_id: int): - user = UserMeetings.objects.get(telegram_id=telegram_id) - return user - - -@sync_to_async -def select_all_users(): - users = User.objects.all().values() - return users - - -@sync_to_async -def select_all_users_id(telegram_id: int): - users = User.objects.filter(telegram_id=telegram_id).all().values() - return users - - -@sync_to_async -def count_users(): - return User.objects.all().count() - - -@sync_to_async -def update_user_data(telegram_id, **kwargs): - return User.objects.filter(telegram_id=telegram_id).update(**kwargs) - - -@sync_to_async -def update_user_meetings_data(telegram_id, **kwargs): - return UserMeetings.objects.filter(telegram_id=telegram_id).update(**kwargs) - - -@sync_to_async -def select_meetings_user(telegram_id: int): - user = UserMeetings.objects.filter(telegram_id=telegram_id).values().first() - return user - - -# https://stackoverflow.com/questions/29014966/django-1-8-arrayfield-append-extend -@sync_to_async -def update_user_events(telegram_id: int, events_id: int): - return User.objects.filter(telegram_id=telegram_id).update( - events=CombinedExpression(F("events"), "||", Value([f"{events_id}"])) - ) - - -@sync_to_async -def remove_events_from_user(telegram_id: int, events_id: int): - user = User.objects.get(telegram_id=telegram_id) - user.remove_events(f"{events_id}") - - -@sync_to_async -def select_user_username(username: str): - try: - user = User.objects.get(username=username) - except Exception as ex: - user = User.objects.filter(username=username).values().first() - print(f"Error in select_user_username {ex}") - return user - - -# https://stackoverflow.com/questions/10040143/and-dont-work-with-filter-in-django -@sync_to_async -def search_users( - need_partner_sex, - need_age_min, - need_age_max, - user_need_city, - offset: int, - limit: int, -): - query = ( - Q(is_banned=False) - & Q(sex=need_partner_sex) - & ( - (Q(age__gte=need_age_min) & Q(age__lte=need_age_max)) - | (Q(age__gte=need_age_min + 1) & Q(age__lte=need_age_max + 1)) - ) - & Q(city=user_need_city) - & Q(status=True) - ) - users = User.objects.filter(query).values()[offset: offset + limit] - return users - - -@sync_to_async -def search_event_forms(): - return UserMeetings.objects.filter(Q(is_active=True)).all().values() - - -@sync_to_async -def search_users_all(offset: int, limit: int): - return ( - User.objects.filter(Q(is_banned=False) & Q(status=True)) - .all() - .values()[offset: offset + limit] - ) - - -@sync_to_async -def count_all_users_kwarg(**kwarg): - return User.objects.filter(**kwarg).all().values().count() - - -@sync_to_async -def update_setting(telegram_id: int, **kwargs): - return SettingModel.objects.filter(telegram_id=telegram_id).update(**kwargs) - - -@sync_to_async -def select_setting(telegram_id): - try: - return SettingModel.objects.get(telegram_id=telegram_id) - except Exception as ex: - print(f"Error in select_setting {ex}") - return SettingModel.objects.filter(telegram_id=telegram_id).values().first() - - -@sync_to_async -def add_user_to_settings(telegram_id: int): - return SettingModel(telegram_id=int(telegram_id)).save() - - -@sync_to_async -def select_setting_tech_work(): - return SettingModel.objects.filter(technical_works=True).values().first() - - -@sync_to_async -def check_returned_event_id(telegram_id: int, id_of_events_seen: int) -> bool: - """ - Function that checks if the given event_id was previously returned for the given telegram_id. - """ - returned_event = User.objects.filter(telegram_id=telegram_id).first() - event_list = returned_event.id_of_events_seen - - return str(id_of_events_seen) in event_list - - -@sync_to_async -def add_returned_event_id(telegram_id: int, id_of_events_seen: int): - """Function that adds the returned event_id for the given telegram_id to the database.""" - returned_event, created = User.objects.get_or_create(telegram_id=telegram_id) - returned_event.id_of_events_seen.append(id_of_events_seen) - returned_event.save() - - -@sync_to_async -def reset_view_limit(): - return User.objects.filter(limit_of_views__lt=10).update(limit_of_views=10) From bc1faf45aed30f0e72b683adf8fb75928ac7e59e Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:43:50 +0300 Subject: [PATCH 021/148] =?UTF-8?q?=F0=9F=A4=A1=20Quiet=20and=20sound=20of?= =?UTF-8?q?=20tumbleweed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/jobs/__init__.py | 0 src/infrastructure/jobs/autobackup.py | 0 src/infrastructure/jobs/autoupdate.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/infrastructure/jobs/__init__.py delete mode 100644 src/infrastructure/jobs/autobackup.py delete mode 100644 src/infrastructure/jobs/autoupdate.py diff --git a/src/infrastructure/jobs/__init__.py b/src/infrastructure/jobs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/infrastructure/jobs/autobackup.py b/src/infrastructure/jobs/autobackup.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/infrastructure/jobs/autoupdate.py b/src/infrastructure/jobs/autoupdate.py deleted file mode 100644 index e69de29..0000000 From f0107c2a2095f6d6fafdcfa8734c5822502472a4 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:45:43 +0300 Subject: [PATCH 022/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/__init__.py | 3 - src/tgbot/__init__.py | 1 - src/tgbot/config.py | 172 ++++++++++++++++----- src/tgbot/filters/FiltersChat.py | 22 ++- src/tgbot/filters/IsAdminFilter.py | 8 +- src/tgbot/handlers/__init__.py | 18 ++- src/tgbot/services/app/set_bot_commands.py | 38 +++-- src/tgbot/services/app/states.py | 5 +- src/tgbot/services/dating/__init__.py | 14 +- 9 files changed, 181 insertions(+), 100 deletions(-) diff --git a/src/infrastructure/__init__.py b/src/infrastructure/__init__.py index cc09a16..e69de29 100644 --- a/src/infrastructure/__init__.py +++ b/src/infrastructure/__init__.py @@ -1,3 +0,0 @@ -from . import ( - db_api, -) diff --git a/src/tgbot/__init__.py b/src/tgbot/__init__.py index 8b13789..e69de29 100644 --- a/src/tgbot/__init__.py +++ b/src/tgbot/__init__.py @@ -1 +0,0 @@ - diff --git a/src/tgbot/config.py b/src/tgbot/config.py index 8bee98b..b003260 100644 --- a/src/tgbot/config.py +++ b/src/tgbot/config.py @@ -1,16 +1,13 @@ +import inspect from dataclasses import ( dataclass, ) from functools import ( lru_cache, ) -import inspect from pathlib import ( Path, ) -from typing import ( - List, -) from environs import ( Env, @@ -19,18 +16,86 @@ URL, ) -env = Env() -env.read_env() - @dataclass(frozen=True, slots=True) class DataBaseConfig: + """ + Database configuration class. + This class holds the settings for the database, such as host, password, port, etc. + + Attributes + ---------- + host : str + The host where the database server is located. + password : str + The password used to authenticate with the database. + user : str + The username used to authenticate with the database. + database : str + The name of the database. + port : int + The port where the database server is listening. + """ user: str password: str host: str database: str port: str + @staticmethod + def from_env(env: Env): + """ + Creates the DbConfig object from environment variables. + """ + host = env.str("DB_HOST") + password = env.str("POSTGRES_PASSWORD") + user = env.str("POSTGRES_USER") + database = env.str("POSTGRES_DB") + port = env.int("DB_PORT", 5432) + return DataBaseConfig( + host=host, password=password, user=user, database=database, port=port + ) + + +@dataclass +class RedisConfig: + """ + Redis configuration class. + + Attributes + ---------- + redis_pass : Optional(str) + The password used to authenticate with Redis. + redis_port : Optional(int) + The port where Redis server is listening. + redis_host : Optional(str) + The host where Redis server is located. + """ + + redis_pass: str | None + redis_port: int | None + redis_host: str | None + + def dsn(self) -> str: + """ + Constructs and returns a Redis DSN (Data Source Name) for this database configuration. + """ + if self.redis_pass: + return f"redis://:{self.redis_pass}@{self.redis_host}:{self.redis_port}/0" + else: + return f"redis://{self.redis_host}:{self.redis_port}/0" + + @staticmethod + def from_env(env: Env): + """ + Creates the RedisConfig object from environment variables. + """ + redis_pass = env.str("REDIS_PASSWORD") + redis_port = env.int("REDIS_PORT") + redis_host = env.str("REDIS_HOST") + + return RedisConfig(redis_pass=redis_pass, redis_port=redis_port, redis_host=redis_host) + @dataclass(frozen=True, slots=True) class TgBot: @@ -43,6 +108,29 @@ class TgBot: moderate_chat: int use_redis: bool + @staticmethod + def from_env(env: Env) -> "TgBot": + """ + Creates the TgBot object from environment variables. + """ + token = env.str("BOT_TOKEN") + admin_ids = list(map(int, env.list("ADMINS"))) + support_ids = list(map(int, env.list("SUPPORTS"))) + ip = env.str("IP") + timezone = env.str("TIMEZONE") + I18N_DOMAIN = "dating" + moderate_chat = env.int("MODERATE_CHAT") + use_redis = env.bool("USE_REDIS") + return TgBot( + token=token, + admin_ids=admin_ids, + support_ids=support_ids, + ip=ip, timezone=timezone, + moderate_chat=moderate_chat, + I18N_DOMAIN=I18N_DOMAIN, + use_redis=use_redis, + ) + @dataclass(frozen=True, slots=True) class Miscellaneous: @@ -53,23 +141,47 @@ class Miscellaneous: yoomoney_key: str production: bool + @staticmethod + def from_env(env: Env) -> "Miscellaneous": + """ + Creates the Miscellaneous object from environment variables. + """ + secret_key = env.str("SECRET_KEY") + yandex_api_key = env.str("API_KEY") + client_id = env.str("CLIENT_ID") + redirect_url = env.str("REDIRECT_URI") + yoomoney_key = env.str("YOOMONEY_KEY") + production = env.bool("PRODUCTION") + return Miscellaneous( + secret_key=secret_key, + yandex_api_key=yandex_api_key, + client_id=client_id, + redirect_url=redirect_url, + production=production, + yoomoney_key=yoomoney_key + ) + @dataclass(frozen=True, slots=True) class Config: tg_bot: TgBot db: DataBaseConfig misc: Miscellaneous + redis: RedisConfig | None = None -def search_env() -> Path: +def search_env() -> str: current_frame = inspect.currentframe() frame = current_frame.f_back caller_dir = Path(frame.f_code.co_filename).parent.resolve() start = caller_dir / ".env" - return start + return str(start) def change_env(section: str, value: str) -> None: + env = Env() + env.read_env() + dumped_env = env.dump() text = "" start = search_env() @@ -86,35 +198,17 @@ def change_env(section: str, value: str) -> None: @lru_cache def load_config() -> Config: - return Config( - tg_bot=TgBot( - token=env.str("BOT_TOKEN"), - admin_ids=list(map(int, env.list("ADMINS"))), - support_ids=list(map(int, env.list("SUPPORTS"))), - ip=env.str("IP"), - timezone=env.str("TIMEZONE"), - I18N_DOMAIN="dating", - moderate_chat=env.int("MODERATE_CHAT"), - use_redis=env.bool("USE_REDIS"), - ), - db=DataBaseConfig( - user=env.str("POSTGRES_USER"), - password=env.str("POSTGRES_PASSWORD"), - host=env.str("DB_HOST"), - database=env.str("POSTGRES_DB"), - port=env.str("DB_PORT"), - ), - misc=Miscellaneous( - secret_key=env.str("SECRET_KEY"), - yandex_api_key=env.str("API_KEY"), - client_id=env.str("CLIENT_ID"), - redirect_url=env.str("REDIRECT_URI"), - yoomoney_key=env.str("YOOMONEY_KEY"), - production=env.bool("PRODUCTION"), - ), - ) + """ + This function takes an optional file path as input and returns a Config object. + It reads environment variables from a .env file if provided, else from the process environment. + :return: Config object with attributes set as per environment variables. + """ + env = Env() + env.read_env(search_env()) -# TODO: Move to dataclass -BASE_DIR = Path(__file__).parent.parent.parent -LOCALES_DIR = BASE_DIR / "locales" + return Config( + tg_bot=TgBot.from_env(env), + db=DataBaseConfig.from_env(env), + misc=Miscellaneous.from_env(env), + ) diff --git a/src/tgbot/filters/FiltersChat.py b/src/tgbot/filters/FiltersChat.py index 56fa4db..c940bcc 100644 --- a/src/tgbot/filters/FiltersChat.py +++ b/src/tgbot/filters/FiltersChat.py @@ -1,19 +1,15 @@ -from aiogram import ( - types, -) -from aiogram.dispatcher.filters import ( - BoundFilter, -) +from aiogram.filters import BaseFilter from aiogram.types import ( Message, ) -class IsPrivate(BoundFilter): - async def check(self, message: Message) -> bool: - return types.ChatType.PRIVATE == message.chat.type - +class ChatTypeFilter(BaseFilter): + def __init__(self, chat_type: str | list): + self.chat_type = chat_type -class IsGroup(BoundFilter): - async def check(self, message: types.Message) -> bool: - return message.chat.type in (types.ChatType.GROUP, types.ChatType.SUPERGROUP) + async def __call__(self, message: Message) -> bool: + if isinstance(self.chat_type, str): + return message.chat.type == self.chat_type + else: + return message.chat.type in self.chat_type diff --git a/src/tgbot/filters/IsAdminFilter.py b/src/tgbot/filters/IsAdminFilter.py index 480629d..4e96735 100644 --- a/src/tgbot/filters/IsAdminFilter.py +++ b/src/tgbot/filters/IsAdminFilter.py @@ -1,15 +1,13 @@ from aiogram import ( types, ) -from aiogram.dispatcher.filters import ( - BoundFilter, -) +from aiogram.filters import BaseFilter from src.tgbot.config import ( load_config, ) -class IsAdmin(BoundFilter): - async def check(self, message: types.Message) -> bool: +class IsAdmin(BaseFilter): + async def __call__(self, message: types.Message) -> bool: return message.from_user.id in load_config().tg_bot.admin_ids diff --git a/src/tgbot/handlers/__init__.py b/src/tgbot/handlers/__init__.py index 351e76a..f12779d 100644 --- a/src/tgbot/handlers/__init__.py +++ b/src/tgbot/handlers/__init__.py @@ -1,7 +1,13 @@ -from . import ( - admins, - errors, - groups, - users, - echo_handler, +from .start import ( + start_router, +) + +routers_list = [ + start_router, + + # echo_router, # echo_router must be last +] + +__all__ = ( + "routers_list", ) diff --git a/src/tgbot/services/app/set_bot_commands.py b/src/tgbot/services/app/set_bot_commands.py index 00c12e8..bcc41c4 100644 --- a/src/tgbot/services/app/set_bot_commands.py +++ b/src/tgbot/services/app/set_bot_commands.py @@ -1,40 +1,46 @@ import logging from aiogram import ( - Dispatcher, - types, + types, Bot, ) from src.tgbot.config import ( - load_config, + Config, ) async def set_user_commands( - dp: Dispatcher, user_id: int, commands: list[types.BotCommand] + bot: Bot, user_id: int, commands: list[types.BotCommand] ): try: - await dp.bot.set_my_commands( - commands=commands, scope=types.BotCommandScopeChat(user_id) + await bot.set_my_commands( + commands=commands, scope=types.BotCommandScopeChat(chat_id=user_id) ) except Exception as ex: logging.error(f"{user_id}: Commands are not installed. {ex}") -async def set_default_commands(dp: Dispatcher) -> None: +async def set_default_commands(bot: Bot, config: Config) -> None: default_commands = [ - types.BotCommand("start", "🟢 Запустить бота"), + types.BotCommand(command="start", description="🟢 Запустить бота"), + types.BotCommand(command="catalog", description="🏪 Открыть каталог"), + types.BotCommand(command="profile", description="👨 Личный кабинет"), + types.BotCommand(command="order", description="🚚 Статус заказа"), + types.BotCommand(command="cart", description="📂 Корзина"), ] admin_commands = [ - types.BotCommand("admin", "⚒ Админ-Меню"), - types.BotCommand("users", "🫂 Пользователи"), - types.BotCommand("settings", "⚙️ Настройки"), - types.BotCommand("ad", "📊 Реклама"), - types.BotCommand("logs", "🗒 Логи"), + types.BotCommand(command="admin", description="⚒ Админ-Меню"), + types.BotCommand(command="users", description="🫂 Пользователи"), + types.BotCommand(command="settings", description="⚙️ Настройки"), + types.BotCommand(command="logs", description="🗒 Логи"), ] - await dp.bot.set_my_commands(default_commands, scope=types.BotCommandScopeDefault()) + await bot.set_my_commands(default_commands, scope=types.BotCommandScopeDefault()) - for admin_id in load_config().tg_bot.admin_ids: - await set_user_commands(dp, admin_id, admin_commands + default_commands) + for admin_id in config.tg_bot.admin_ids: + await set_user_commands( + bot=bot, + user_id=admin_id, + commands=admin_commands + default_commands + ) diff --git a/src/tgbot/services/app/states.py b/src/tgbot/services/app/states.py index d8e0637..0a2cb7d 100644 --- a/src/tgbot/services/app/states.py +++ b/src/tgbot/services/app/states.py @@ -1,7 +1,4 @@ -from aiogram.dispatcher.filters.state import ( - State, - StatesGroup, -) +from aiogram.fsm.state import StatesGroup, State class AdminsActions(StatesGroup): diff --git a/src/tgbot/services/dating/__init__.py b/src/tgbot/services/dating/__init__.py index 1f89ce8..8b13789 100644 --- a/src/tgbot/services/dating/__init__.py +++ b/src/tgbot/services/dating/__init__.py @@ -1,13 +1 @@ -from .reaction_strategies import ( - ChooseReportReason, - DislikeAction, - DislikeReciprocity, - GoBackToViewing, - LikeAction, - LikeReciprocity, - SendReport, - StartFindingFailure, - StartFindingReachLimit, - StartFindingSuccess, - StoppedAction, -) + From 7e0b1cf18130834ebab98a0f23a07815fa62eca7 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:46:05 +0300 Subject: [PATCH 023/148] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20add=20types=20f?= =?UTF-8?q?ile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/types.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/tgbot/types.py diff --git a/src/tgbot/types.py b/src/tgbot/types.py new file mode 100644 index 0000000..75f4756 --- /dev/null +++ b/src/tgbot/types.py @@ -0,0 +1,11 @@ +from typing import ( + Any, + Awaitable, + Callable, +) + +from aiogram.types import ( + Message, +) + +Handler = Callable[[Message, dict[str, Any]], Awaitable[Any]] From ac7dc3c2ab347c52dc11ef9fa5b4b7248022cf64 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:46:44 +0300 Subject: [PATCH 024/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20dead=20co?= =?UTF-8?q?de?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handlers/admins/advert/ref/__init__.py | 0 .../admins/advert/requiredsub/__init__.py | 0 src/tgbot/keyboards/admin/__init__.py | 0 src/tgbot/keyboards/admin/inline/__init__.py | 0 src/tgbot/keyboards/default/__init__.py | 0 src/tgbot/services/app/ds_name.py | 13 - src/tgbot/services/app/logger.py | 14 - src/tgbot/services/app/message_operations.py | 282 ------------------ src/tgbot/services/app/notify_admins.py | 60 ---- src/tgbot/services/app/photo_operations.py | 117 -------- src/tgbot/services/app/throttling.py | 18 -- .../services/dating/get_next_user_func.py | 53 ---- .../services/dating/reaction_strategies.py | 252 ---------------- src/tgbot/services/dating/send_form_func.py | 134 --------- src/tgbot/services/event/extra_features.py | 124 -------- 15 files changed, 1067 deletions(-) delete mode 100644 src/tgbot/handlers/admins/advert/ref/__init__.py delete mode 100644 src/tgbot/handlers/admins/advert/requiredsub/__init__.py delete mode 100644 src/tgbot/keyboards/admin/__init__.py delete mode 100644 src/tgbot/keyboards/admin/inline/__init__.py delete mode 100644 src/tgbot/keyboards/default/__init__.py delete mode 100644 src/tgbot/services/app/ds_name.py delete mode 100644 src/tgbot/services/app/logger.py delete mode 100644 src/tgbot/services/app/message_operations.py delete mode 100644 src/tgbot/services/app/notify_admins.py delete mode 100644 src/tgbot/services/app/photo_operations.py delete mode 100644 src/tgbot/services/app/throttling.py delete mode 100644 src/tgbot/services/dating/get_next_user_func.py delete mode 100644 src/tgbot/services/dating/reaction_strategies.py delete mode 100644 src/tgbot/services/dating/send_form_func.py delete mode 100644 src/tgbot/services/event/extra_features.py diff --git a/src/tgbot/handlers/admins/advert/ref/__init__.py b/src/tgbot/handlers/admins/advert/ref/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tgbot/handlers/admins/advert/requiredsub/__init__.py b/src/tgbot/handlers/admins/advert/requiredsub/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tgbot/keyboards/admin/__init__.py b/src/tgbot/keyboards/admin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tgbot/keyboards/admin/inline/__init__.py b/src/tgbot/keyboards/admin/inline/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tgbot/keyboards/default/__init__.py b/src/tgbot/keyboards/default/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tgbot/services/app/ds_name.py b/src/tgbot/services/app/ds_name.py deleted file mode 100644 index 29f23d3..0000000 --- a/src/tgbot/services/app/ds_name.py +++ /dev/null @@ -1,13 +0,0 @@ -from aiogram import ( - types, -) -from aiogram.utils.markdown import ( - hbold, -) - - -def get_display_name(user: types.User) -> str: - if user.username: - return f"@{user.username}" - - return hbold(user.first_name) diff --git a/src/tgbot/services/app/logger.py b/src/tgbot/services/app/logger.py deleted file mode 100644 index e231c9d..0000000 --- a/src/tgbot/services/app/logger.py +++ /dev/null @@ -1,14 +0,0 @@ -import logging - -import betterlogging as bl - - -# TODO: Удалить эту функцию. Перенести ее функционал в функцию main в bot.py -def setup_logger(level=logging.INFO, ignored=""): - bl.basic_colorized_config(level=level) - logging.basicConfig( - level=logging.INFO, - format="%(filename)s:%(lineno)d #%(levelname)-8s [%(asctime)s] - %(name)s - %(message)s", - ) - for ignore in ignored: - logging.getLogger(ignore).disabled = True diff --git a/src/tgbot/services/app/message_operations.py b/src/tgbot/services/app/message_operations.py deleted file mode 100644 index 72dae27..0000000 --- a/src/tgbot/services/app/message_operations.py +++ /dev/null @@ -1,282 +0,0 @@ -from contextlib import ( - suppress, -) -import datetime -import os -import random -import re - -from aiogram import ( - types, -) -from aiogram.dispatcher import ( - FSMContext, -) -from aiogram.types import ( - CallbackQuery, - InlineKeyboardMarkup, - Message, -) -from aiogram.utils.exceptions import ( - BadRequest, - MessageCantBeDeleted, - MessageToDeleteNotFound, -) -from asyncpg import ( - UniqueViolationError, -) - -from loader import ( - _, - bot, - scheduler, -) -from src.tgbot.config import ( - load_config, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.infrastructure.db_api.db_commands import ( - check_user_meetings_exists, -) -from src.tgbot.keyboards.inline.filters_inline import ( - dating_filters_keyboard, -) -from src.tgbot.keyboards.inline.guide_inline import ( - create_pagination_keyboard, -) -from src.tgbot.keyboards.inline.main_menu_inline import ( - start_keyboard, -) -from src.tgbot.keyboards.inline.settings_menu import ( - information_keyboard, -) -from src.tgbot.services.app.app_scheduler import ( - send_message_week, -) - - -async def delete_message(message: Message) -> None: - with suppress(MessageCantBeDeleted, MessageToDeleteNotFound): - await message.delete() - - -async def choice_gender(call: CallbackQuery) -> None: - """Function that saves to the database the gender that the user has selected.""" - sex_mapping = {"male": "Мужской", "female": "Женский"} - - selected_sex = sex_mapping.get(call.data) - - if selected_sex: - try: - await db_commands.update_user_data( - telegram_id=call.from_user.id, need_partner_sex=selected_sex - ) - except UniqueViolationError: - pass - - -async def display_profile(call: CallbackQuery, markup: InlineKeyboardMarkup) -> None: - """Function for displaying the user profile.""" - user = await db_commands.select_user(telegram_id=call.from_user.id) - count_referrals = await db_commands.count_all_users_kwarg( - referrer_id=call.from_user.id - ) - user_verification = "✅" if user.verification else "" - - user_info_template = _( - "{name}, {age} лет, {city}, {verification}\n\n{commentary}\n\n" - "Партнерка:\nКоличество приглашенных друзей: {reff}\nРеферальная ссылка:\n {link}" - ) - info = await bot.get_me() - user_info = user_info_template.format( - name=user.varname, - age=user.age, - city=user.city, - verification=user_verification, - commentary=user.commentary, - reff=count_referrals, - link=f"https://t.me/{info.username}?start={call.from_user.id}", - ) - - await call.message.answer_photo( - caption=user_info, photo=user.photo_id, reply_markup=markup - ) - - -async def show_dating_filters(obj: CallbackQuery | Message) -> None: - user_id = obj.from_user.id - user = await db_commands.select_user(telegram_id=user_id) - markup = await dating_filters_keyboard() - - text = _( - "Фильтр по подбору партнеров:\n\n" - "🚻 Необходимы пол партнера: {}\n" - "🔞 Возрастной диапазон: {}-{} лет\n\n" - "🏙️ Город партнера: {}" - ).format( - user.need_partner_sex, - user.need_partner_age_min, - user.need_partner_age_max, - user.need_city, - ) - try: - await obj.message.edit_text(text, reply_markup=markup) - except AttributeError: - await obj.answer(text, reply_markup=markup) - - -async def registration_menu( - obj: CallbackQuery | Message, -) -> None: - support = await db_commands.select_user( - telegram_id=load_config().tg_bot.support_ids[0] - ) - markup = await start_keyboard(obj) - heart = random.choice(["💙", "💚", "💛", "🧡", "💜", "🖤", "❤", "🤍", "💖", "💝"]) - text = _( - "Приветствую вас, {fullname}!!\n\n" - "{heart} Querendo - платформа для поиска новых знакомств.\n\n" - "🪧 Новости о проекте вы можете прочитать в нашем канале - " - "https://t.me/que_group \n\n" - "🤝 Сотрудничество: \n" - "Если у вас есть предложение о сотрудничестве, пишите агенту поддержки - " - "@{supports}\n\n" - ).format(fullname=obj.from_user.full_name, heart=heart, supports=support.username) - try: - await obj.message.edit_text(text=text, reply_markup=markup) - scheduler.add_job( - send_message_week, - trigger="interval", - weeks=1, - jitter=120, - args={obj.message}, - ) - except AttributeError: - await obj.answer(text=text, reply_markup=markup) - scheduler.add_job( - send_message_week, trigger="interval", weeks=1, jitter=120, args={obj} - ) - except BadRequest: - await delete_message(obj.message) - - await obj.message.answer(text=text, reply_markup=markup) - - -async def check_user_in_db(telegram_id: int, message: Message, username: str) -> None: - if not await db_commands.check_user_exists( - telegram_id - ) and not await check_user_meetings_exists(telegram_id): - user = await db_commands.select_user_object(telegram_id=telegram_id) - referrer_id = message.text[7:] - if referrer_id != "" and referrer_id != telegram_id: - await db_commands.add_user( - name=message.from_user.full_name, - telegram_id=telegram_id, - username=username, - referrer_id=referrer_id, - ) - await db_commands.update_user_data( - telegram_id=telegram_id, limit_of_views=user.limit_of_views + 15 - ) - await bot.send_message( - chat_id=referrer_id, - text=_( - "По вашей ссылке зарегистрировался пользователь {}!\n" - "Вы получаете дополнительных 15 ❤️" - ).format(message.from_user.username), - ) - else: - await db_commands.add_user( - name=message.from_user.full_name, - telegram_id=telegram_id, - username=username, - ) - await db_commands.add_meetings_user(telegram_id=telegram_id, username=username) - if telegram_id in load_config().tg_bot.admin_ids: - await db_commands.add_user_to_settings(telegram_id=telegram_id) - - -async def finished_registration( - state: FSMContext, telegram_id: int, message: Message -) -> None: - await state.finish() - await db_commands.update_user_data(telegram_id=telegram_id, status=True) - - user = await db_commands.select_user(telegram_id=telegram_id) - - markup = await start_keyboard(obj=message) - - text = _( - "Регистрация успешно завершена! \n\n " - "{}, " - "{} лет, " - "{}\n\n" - "О себе - {}" - ).format(user.varname, user.age, user.city, user.commentary) - - await message.answer_photo(caption=text, photo=user.photo_id, reply_markup=markup) - - -async def send_photo_with_caption( - call: CallbackQuery, - photo: str, - caption: str, - step: int, - total_steps: int, -) -> None: - markup = await create_pagination_keyboard(step, total_steps) - - await call.message.delete() - await call.message.answer_photo( - types.InputFile(photo), reply_markup=markup, caption=caption - ) - - -async def handle_guide_callback( - call: CallbackQuery, - callback_data: dict, -) -> None: - step = int(callback_data.get("value")) - - photo_path = f"brandbook/{step}_page.png" - caption = _("Руководство по боту: \nСтраница №{}").format(step) - await send_photo_with_caption( - call=call, - photo=photo_path, - caption=caption, - step=step, - total_steps=len(os.listdir("brandbook/")), - ) - - -async def information_menu(call: CallbackQuery) -> None: - start_date = datetime.datetime(2021, 8, 10, 14, 0) - now_date = datetime.datetime.now() - delta = now_date - start_date - count_users = await db_commands.count_users() - markup = await information_keyboard() - txt = _( - "Вы попали в раздел Информации бота, здесь вы можете посмотреть: статистику," - "изменить язык, а также посмотреть наш брендбук.\n\n" - "🌐 Дней работаем: {}\n" - "👤 Всего пользователей: {}\n" - ).format(delta.days, count_users) - try: - await call.message.edit_text(text=txt, reply_markup=markup) - except BadRequest: - await delete_message(call.message) - await call.message.answer(text=txt, reply_markup=markup) - - -async def get_report_reason(call: CallbackQuery) -> str: - match = re.search(r"report:(.*?):", call.data) - reason_key = match.group(1) - reason_mapping = { - "adults_only": "🔞 Развратный контент", - "drugs": "💊 Продажа наркотиков", - "scam": "💰 Мошенничество", - "another": "🦨 Другая причина", - } - return reason_mapping.get(reason_key, "Неизвестная причина") diff --git a/src/tgbot/services/app/notify_admins.py b/src/tgbot/services/app/notify_admins.py deleted file mode 100644 index 04fffe3..0000000 --- a/src/tgbot/services/app/notify_admins.py +++ /dev/null @@ -1,60 +0,0 @@ -from abc import ( - ABC, - abstractmethod, -) - -import aiogram -from aiogram import ( - Dispatcher, -) -import aiogram.utils.exceptions -from aiogram.utils.exceptions import ( - ChatNotFound, -) - -from loader import ( - _, - bot, - logger, -) -from src.tgbot.config import ( - load_config, -) - - -class BaseNotification(ABC): - @abstractmethod - def send(self, *args): - pass - - -class AdminNotification(BaseNotification): - def __init__(self, dp: Dispatcher): - self.dp = dp - - async def send(self) -> None: - logger.info(_("Оповещение администрации...")) - for admin in load_config().tg_bot.admin_ids: - try: - await bot.send_message( - admin, _("Бот был успешно запущен"), disable_notification=True - ) - except ChatNotFound: - logger.debug("Чат с админом не найден") - - -class ErrorNotification(BaseNotification): - def __init__(self, error_message: Exception): - self.__error_message = error_message - - async def send(self) -> None: - text = ( - f"❗ Error During Operation ❗\n" - f"{self.__error_message}\n\n❗" - f" The bot will restart automatically." - ) - for user_id in load_config().tg_bot.admin_ids: - try: - await bot.send_message(user_id, text) - except (aiogram.exceptions.BotBlocked, aiogram.exceptions.ChatNotFound): - continue diff --git a/src/tgbot/services/app/photo_operations.py b/src/tgbot/services/app/photo_operations.py deleted file mode 100644 index 4868052..0000000 --- a/src/tgbot/services/app/photo_operations.py +++ /dev/null @@ -1,117 +0,0 @@ -import asyncio -import pathlib - -from aiogram.dispatcher import ( - FSMContext, -) -from aiogram.types import ( - InlineKeyboardMarkup, - InputFile, - Message, - ReplyKeyboardRemove, -) - -from loader import ( - _, - bot, - logger, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.services.app.message_operations import ( - finished_registration, -) - - -async def saving_normal_photo( - message: Message, telegram_id: int, file_id: str, state: FSMContext -) -> None: - """Функция, сохраняющая фотографию пользователя без цензуры.""" - try: - await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) - - await message.answer( - text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() - ) - except Exception as err: - logger.info(f"Ошибка в saving_normal_photo | err: {err}") - await message.answer( - text=_( - "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" - "Если ошибка осталась, напишите агенту поддержки." - ) - ) - await finished_registration(state=state, telegram_id=telegram_id, message=message) - - -async def saving_censored_photo( - message: Message, - telegram_id: int, - state: FSMContext, - out_path: str | pathlib.Path, - flag: str | None = "registration", - markup: InlineKeyboardMarkup | None = None, -) -> None: - """.Функция, сохраняющая фотографию пользователя с цензурой.""" - photo = InputFile(out_path) - id_photo = await bot.send_photo( - chat_id=telegram_id, - photo=photo, - caption=_( - "Во время проверки вашего фото мы обнаружили подозрительный контент!\n" - "Поэтому мы чуть-чуть подкорректировали вашу фотографию" - ), - ) - file_id = id_photo["photo"][0]["file_id"] - await asyncio.sleep(1) - try: - await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) - - except Exception as err: - logger.info(f"Ошибка в saving_censored_photo | err: {err}") - await message.answer( - text=_( - "Произошла ошибка!" - " Попробуйте еще раз либо отправьте другую фотографию. \n" - "Если ошибка осталась, напишите агенту поддержки." - ) - ) - if flag == "change_datas": - await message.answer( - text=_("Фото принято!\n" "Выберите, что вы хотите изменить: "), - reply_markup=markup, - ) - await state.reset_state() - elif flag == "registration": - await finished_registration( - state=state, telegram_id=telegram_id, message=message - ) - - -async def update_normal_photo( - message: Message, - telegram_id: int, - file_id: str, - state: FSMContext, - markup -) -> None: - """Функция, которая обновляет фотографию пользователя.""" - try: - await db_commands.update_user_data(telegram_id=telegram_id, photo_id=file_id) - await message.answer( - text=_("Фото принято!"), reply_markup=ReplyKeyboardRemove() - ) - await asyncio.sleep(3) - await message.answer( - text=_("Выберите, что вы хотите изменить: "), reply_markup=markup - ) - await state.reset_state() - except Exception as err: - logger.info(f"Ошибка в update_normal_photo | err: {err}") - await message.answer( - text=_( - "Произошла ошибка! Попробуйте еще раз либо отправьте другую фотографию. \n" - "Если ошибка осталась, напишите агенту поддержки." - ) - ) diff --git a/src/tgbot/services/app/throttling.py b/src/tgbot/services/app/throttling.py deleted file mode 100644 index 52dc542..0000000 --- a/src/tgbot/services/app/throttling.py +++ /dev/null @@ -1,18 +0,0 @@ -def rate_limit(limit: int, key=None): - """ - Rate limit decorator. - - Decorator for configuring rate limit and key in different functions. - - :param limit: - :param key: - :return: - """ - - def decorator(func): - setattr(func, "throttling_rate_limit", limit) - if key: - setattr(func, "throttling_key", key) - return func - - return decorator diff --git a/src/tgbot/services/dating/get_next_user_func.py b/src/tgbot/services/dating/get_next_user_func.py deleted file mode 100644 index 9caa017..0000000 --- a/src/tgbot/services/dating/get_next_user_func.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import ( - List, -) - -from async_lru import ( - alru_cache, -) - -from src.infrastructure.db_api import ( - db_commands, -) - - -@alru_cache -async def get_next_user( - telegram_id: int, monitoring: bool = False, offset: int = 0, limit: int = 100 -) -> List[int]: - user = await db_commands.select_user_object(telegram_id=telegram_id) - viewed_profiles = user.viewed_profiles.all() - - if monitoring: - user_filter = await db_commands.search_users_all(offset, limit) - else: - user_filter = await db_commands.search_users( - user.need_partner_sex, - user.need_partner_age_min, - user.need_partner_age_max, - user.need_city, - offset, - limit, - ) - - viewed_profiles_ids = [profile.telegram_id for profile in viewed_profiles] - - user_list = [] - for i in user_filter: - if ( - int(i["telegram_id"]) != int(telegram_id) - and i["telegram_id"] not in viewed_profiles_ids - ): - user_list.append(i["telegram_id"]) - - if not user_list: - user_filter_2 = await db_commands.search_users_all(offset, limit) - for k in user_filter_2: - if ( - k not in user_filter - and int(k["telegram_id"]) != int(telegram_id) - and k["telegram_id"] not in viewed_profiles_ids - ): - user_list.append(k["telegram_id"]) - - return user_list diff --git a/src/tgbot/services/dating/reaction_strategies.py b/src/tgbot/services/dating/reaction_strategies.py deleted file mode 100644 index c4ec7f3..0000000 --- a/src/tgbot/services/dating/reaction_strategies.py +++ /dev/null @@ -1,252 +0,0 @@ -from abc import ( - ABC, - abstractmethod, -) -import asyncio -import random -import secrets - -from aiogram.dispatcher import ( - FSMContext, -) -from aiogram.types import ( - CallbackQuery, -) - -from loader import ( - _, - bot, -) -from src.tgbot.config import ( - load_config, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.keyboards.inline.main_menu_inline import ( - start_keyboard, -) -from src.tgbot.keyboards.inline.questionnaires_inline import ( - report_menu_keyboard, - user_link_keyboard, -) -from src.tgbot.services.app.message_operations import ( - get_report_reason, -) -from src.tgbot.services.dating.create_forms_funcs import ( - create_questionnaire, - create_questionnaire_reciprocity, - rand_user_list, -) -from src.tgbot.services.dating.get_next_user_func import ( - get_next_user, -) - - -class ActionStrategy(ABC): - @abstractmethod - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - pass - - -class StartFindingSuccess(ActionStrategy): - async def execute(self, call: CallbackQuery, state: FSMContext, **kwargs): - await call.message.delete() - telegram_id = call.from_user.id - user_list = await get_next_user(telegram_id) - random_user = random.choice(user_list) - await create_questionnaire(form_owner=random_user, chat_id=telegram_id) - await state.set_state("finding") - - -class StartFindingFailure(ActionStrategy): - async def execute(self, call: CallbackQuery, state: FSMContext, **kwargs): - await call.answer(_("На данный момент у нас нет подходящих анкет для вас")) - - -class StartFindingReachLimit(ActionStrategy): - async def execute(self, call: CallbackQuery, state: FSMContext, **kwargs): - await call.answer( - text=_("У вас достигнут лимит на просмотры анкет"), show_alert=True - ) - - -class LikeAction(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - user = await db_commands.select_user_object(telegram_id=call.from_user.id) - text = _("Кому-то понравилась твоя анкета") - target_id = int(callback_data["target_id"]) - - await create_questionnaire( - form_owner=call.from_user.id, chat_id=target_id, add_text=text - ) - - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=None, - ) - - await db_commands.update_user_data( - telegram_id=call.from_user.id, limit_of_views=user.limit_of_views - 1 - ) - await create_questionnaire( - form_owner=(await rand_user_list(call)), chat_id=call.from_user.id - ) - - await state.reset_data() - - -class DislikeAction(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=None, - ) - await create_questionnaire( - form_owner=(await rand_user_list(call)), chat_id=call.from_user.id - ) - await state.reset_data() - - -class StoppedAction(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - text = _( - "Рад был помочь, {fullname}!\nНадеюсь, ты нашел кого-то благодаря мне" - ).format(fullname=call.from_user.full_name) - await call.answer(text, show_alert=True) - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=await start_keyboard(call), - ) - await state.reset_state() - - -class LikeReciprocity(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - user_for_like = int(callback_data["user_for_like"]) - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=None, - ) - await call.message.answer( - text=_("Отлично! Надеюсь вы хорошо проведете время ;) Начинай общаться 👉"), - reply_markup=await user_link_keyboard(telegram_id=user_for_like), - ) - await create_questionnaire_reciprocity( - liker=call.from_user.id, chat_id=user_for_like, add_text="" - ) - await bot.send_message( - chat_id=user_for_like, - text="Есть взаимная симпатия! Начиная общаться 👉", - reply_markup=await user_link_keyboard(telegram_id=call.from_user.id), - ) - await state.reset_state() - - -class DislikeReciprocity(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=await start_keyboard(call), - ) - await state.reset_state() - - -class GoBackToViewing(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=None, - ) - - user_list = await get_next_user(call.from_user.id) - random_user = secrets.choice(user_list) - await state.set_state("finding") - try: - await create_questionnaire( - form_owner=random_user, chat_id=call.from_user.id - ) - await state.reset_data() - except IndexError: - await call.answer(_("На данный момент у нас нет подходящих анкет для вас")) - await state.reset_data() - - -class ChooseReportReason(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - await state.reset_state() - await bot.edit_message_reply_markup( - chat_id=call.from_user.id, - message_id=call.message.message_id, - reply_markup=None, - ) - target_id = int(callback_data["target_id"]) - await call.message.answer( - text=_("Выберите причину жалобы:"), - reply_markup=await report_menu_keyboard(telegram_id=target_id), - ) - - -class SendReport(ActionStrategy): - async def execute( - self, call: CallbackQuery, state: FSMContext, callback_data: dict[str, str] - ): - target_id = int(callback_data["target_id"]) - target_user = await db_commands.select_user(telegram_id=target_id) - - counter_of_report = target_user.counter_of_report - username = call.from_user.username - user_id = call.from_user.id - report_reason = await get_report_reason(call) - - text = _( - "Жалоба от пользователя: [@{username} | {tg_id}]\n\n" - "На пользователя: [{owner_id}]\n" - "Причина жалобы: {reason}\n" - "Количество жалоб на пользователя: {counter_of_report}" - ).format( - username=username, - tg_id=user_id, - owner_id=target_id, - reason=report_reason, - counter_of_report=counter_of_report, - ) - - await db_commands.update_user_data( - telegram_id=target_id, counter_of_report=counter_of_report + 1 - ) - - moderate_chat = load_config().tg_bot.moderate_chat - if counter_of_report >= 5 and not target_user.on_check_by_admin: - await db_commands.update_user_data( - telegram_id=target_id, on_check_by_admin=True - ) - await create_questionnaire( - form_owner=target_id, - chat_id=moderate_chat, - report_system=True, - add_text=text, - ) - await asyncio.sleep(0.5) diff --git a/src/tgbot/services/dating/send_form_func.py b/src/tgbot/services/dating/send_form_func.py deleted file mode 100644 index 22fdfd0..0000000 --- a/src/tgbot/services/dating/send_form_func.py +++ /dev/null @@ -1,134 +0,0 @@ -from typing import ( - Optional, -) - -from aiogram.types import ( - InlineKeyboardMarkup, -) -from aiogram.utils.exceptions import ( - BadRequest, -) - -from loader import ( - _, - bot, - logger, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.keyboards.admin.inline.customers import ( - user_blocking_keyboard, -) -from src.tgbot.keyboards.inline.questionnaires_inline import ( - questionnaires_keyboard, - reciprocity_keyboard, -) - - -async def send_questionnaire( - chat_id: int, - owner_id: int | None = None, - markup: InlineKeyboardMarkup | None = None, - add_text: str | None = None, - monitoring: bool = False, - report_system: bool = False, -) -> None: - user = await db_commands.select_user(owner_id) - text_template = _("{}, {} лет, {} {verification}\n\n") - user_verification = "✅" if user.verification else "" - - text_without_inst = _(text_template + "{commentary}").format( - user.varname, - user.age, - user.city, - commentary=user.commentary, - verification=user_verification, - ) - - text_with_inst_template = text_template + _( - "Инстаграм - {instagram}\n" - ) - text_with_inst = _(text_with_inst_template).format( - user.varname, - user.age, - user.city, - user.commentary, - verification=user_verification, - instagram=user.instagram, - ) - - caption_with_add_text = _("{}\n\n" + text_template + "{}").format( - add_text, - user.varname, - user.age, - user.city, - user.commentary, - verification=user_verification, - ) - - add_text_with_inst = _( - "{}\n\n" + text_template + "Инстаграм - {instagram}\n" - ).format( - add_text, - user.varname, - user.age, - user.city, - user.commentary, - verification=user_verification, - instagram=user.instagram, - ) - try: - if add_text is None and user.instagram is None: - await bot.send_photo( - chat_id=chat_id, - caption=text_without_inst, - photo=user.photo_id, - reply_markup=await questionnaires_keyboard( - target_id=owner_id, monitoring=monitoring - ), - ) - elif add_text is None: - await bot.send_photo( - chat_id=chat_id, - caption=text_with_inst, - photo=user.photo_id, - reply_markup=await questionnaires_keyboard( - target_id=owner_id, monitoring=monitoring - ), - ) - elif markup is None and user.instagram is None: - await bot.send_photo( - chat_id=chat_id, - caption=caption_with_add_text, - photo=user.photo_id, - ) - elif markup is None: - await bot.send_photo( - chat_id=chat_id, caption=add_text_with_inst, photo=user.photo_id - ) - elif user.instagram is None and not report_system: - await bot.send_photo( - chat_id=chat_id, - caption=caption_with_add_text, - photo=user.photo_id, - reply_markup=await reciprocity_keyboard(user_for_like=owner_id), - ) - elif report_system: - await bot.send_photo( - chat_id=chat_id, - caption=add_text, - photo=user.photo_id, - reply_markup=await user_blocking_keyboard( - user_id=owner_id, is_banned=user.is_banned - ), - ) - else: - await bot.send_photo( - chat_id=chat_id, - caption=add_text_with_inst, - photo=user.photo_id, - reply_markup=await reciprocity_keyboard(user_for_like=owner_id), - ) - except BadRequest as err: - logger.info(f"{err}. Error in the send_questionnaire function") diff --git a/src/tgbot/services/event/extra_features.py b/src/tgbot/services/event/extra_features.py deleted file mode 100644 index 14f39e4..0000000 --- a/src/tgbot/services/event/extra_features.py +++ /dev/null @@ -1,124 +0,0 @@ -from datetime import ( - datetime, -) -from typing import ( - List, - Optional, - Union, -) - -from aiogram.types import ( - CallbackQuery, -) -from aiogram.utils.exceptions import ( - BadRequest, -) - -from loader import ( - _, - bot, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.services.event.templates_messages import ( - ME, -) - - -async def add_events_to_user(call: CallbackQuery, event_id: int) -> None: - """Function that stores id's of events liked by a user.""" - user = await db_commands.select_user(telegram_id=call.from_user.id) - event_list = user.events - - if str(event_id) not in event_list: - await db_commands.update_user_events( - telegram_id=call.from_user.id, events_id=event_id - ) - - -async def check_availability_on_event() -> bool: - """Function that checks the availability of seats for an event.""" - ... - - -async def check_event_date(telegram_id: int) -> None: - """Function that checks whether an event has passed or not.""" - event = await db_commands.select_user_meetings(telegram_id) - event_time = event.time_event - if event_time is None: - return - event_datetime, now_datetime = ( - datetime.strptime(event_time, "%d-%m-%Y"), - datetime.now().date(), - ) - is_admin = True - verification_status = True - is_active = True - - if event_datetime.date() <= now_datetime: - is_admin = False - verification_status = False - is_active = False - await db_commands.update_user_meetings_data( - telegram_id=telegram_id, - is_admin=is_admin, - verification_status=verification_status, - is_active=is_active, - ) - - -async def create_form( - form_owner: int, chat_id: int, call: CallbackQuery, view: bool | None = True -) -> None: - """Function that fills the form with text.""" - try: - owner = await db_commands.select_user_meetings(telegram_id=form_owner) - document = { - "title": owner.event_name, - "date": owner.time_event, - "place": owner.venue, - "description": owner.commentary, - "photo_id": owner.photo_id, - "telegram_id": form_owner, - } - if view: - await ME.send_event_message( - text=document, bot=bot, chat_id=chat_id, view_event=True, call=call - ) - else: - await ME.send_event_list( - text=document, call=call, bot=bot, telegram_id=call.from_user.id - ) - except BadRequest: - await call.answer( - text=_("На данный момент у нас нет подходящих мероприятий для вас"), - show_alert=True, - ) - - -async def get_next_random_event_id(telegram_id: int) -> int | None: - """Function that returns a random id of an event created by another user.""" - event_ids = await db_commands.search_event_forms() - - other_events_ids = [] - for e in event_ids: - if e["telegram_id"] != telegram_id: - other_events_ids.append(e["telegram_id"]) - - for event_id in other_events_ids: - if not await db_commands.check_returned_event_id( - telegram_id=telegram_id, id_of_events_seen=event_id - ): - await db_commands.add_returned_event_id( - telegram_id=telegram_id, id_of_events_seen=event_id - ) - return event_id - - raise ValueError("No more event ids") - - -async def get_next_registration(telegram_id: int) -> list[int]: - user = await db_commands.select_user(telegram_id=telegram_id) - events: list = user.events - return events From 6cd9c811459e00ebe5d56d874fe5e9ceecbd0afc Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:55:10 +0300 Subject: [PATCH 025/148] =?UTF-8?q?=F0=9F=9A=A7=20Update=20code=20to=20wor?= =?UTF-8?q?k=20with=20new=20library=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/AgentSupport.py | 45 ++++++++-------- src/tgbot/middlewares/Config.py | 29 +++++++++++ src/tgbot/middlewares/__init__.py | 75 +++++++++++---------------- 3 files changed, 83 insertions(+), 66 deletions(-) create mode 100644 src/tgbot/middlewares/Config.py diff --git a/src/tgbot/middlewares/AgentSupport.py b/src/tgbot/middlewares/AgentSupport.py index d1c2d94..7e53473 100644 --- a/src/tgbot/middlewares/AgentSupport.py +++ b/src/tgbot/middlewares/AgentSupport.py @@ -1,31 +1,32 @@ from typing import ( - NoReturn, -) + Dict, Any, ) from aiogram import ( - Dispatcher, - types, -) -from aiogram.dispatcher.handler import ( - CancelHandler, -) -from aiogram.dispatcher.middlewares import ( BaseMiddleware, ) +from aiogram.types import TelegramObject +from src.tgbot.types import Handler -class SupportMiddleware(BaseMiddleware): - @staticmethod - async def on_pre_process_message(message: types.Message, data: dict) -> NoReturn: - dispatcher = Dispatcher.get_current() - state = dispatcher.current_state( - chat=message.from_user.id, user=message.from_user.id - ) - state_str = str(await state.get_state()) - if state_str == "in_support": - data = await state.get_data() - second_id = data.get("second_id") - await message.copy_to(second_id) +class SupportMiddleware(BaseMiddleware): - raise CancelHandler() + async def __call__( + self, + handler: Handler, + event: TelegramObject, + data: Dict[str, Any] + ) -> Any: + pass + # dispatcher = Dispatcher.get_current() + # state = dispatcher.current_state( + # chat=message.from_user.id, user=message.from_user.id + # ) + # + # state_str = str(await state.get_state()) + # if state_str == "in_support": + # data = await state.get_data() + # second_id = data.get("second_id") + # await message.copy_to(second_id) + # + # raise CancelHandler() diff --git a/src/tgbot/middlewares/Config.py b/src/tgbot/middlewares/Config.py new file mode 100644 index 0000000..e2ea2ce --- /dev/null +++ b/src/tgbot/middlewares/Config.py @@ -0,0 +1,29 @@ +from typing import ( + Any, + Dict, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + Message, +) + +from src.tgbot.types import ( + Handler, +) + + +class ConfigMiddleware(BaseMiddleware): + def __init__(self, config) -> None: + self.config = config + + async def __call__( + self, + handler: Handler, + event: Message, + data: Dict[str, Any], + ) -> Any: + data["config"] = self.config + return await handler(event, data) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index a979855..5ed3183 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -1,48 +1,35 @@ -# from loader import ( -# dp, -# scheduler, +# from .AgentSupport import ( +# SupportMiddleware, # ) -# -from .AgentSupport import ( - SupportMiddleware, -) -from .BanCheck import ( - BanMiddleware, -) -from .IsMaintenanceCheck import ( - IsMaintenance, -) -from .LinkCheck import ( - LinkCheckMiddleware, -) -from .Log import ( - LogMiddleware, -) -from .SchedulerWare import ( - SchedulerMiddleware, -) -from .Throttling import ( - ThrottlingMiddleware, -) +# from .BanCheck import ( +# BanMiddleware, +# ) +# from .IsMaintenanceCheck import ( +# IsMaintenance, +# ) +# from .LinkCheck import ( +# LinkCheckMiddleware, +# ) +# from .Log import ( +# LogMiddleware, +# ) +# from .SchedulerWare import ( +# SchedulerMiddleware, +# ) +# from .Throttling import ( +# ThrottlingMiddleware, +# ) +from .Config import ConfigMiddleware __all__ = ( - "AgentSupport", - "BanMiddleware", - "IsMaintenance", - "LinkCheck", - "LogMiddleware", - "SchedulerMiddleware", - "ThrottlingMiddleware", - "SupportMiddleware", - "LinkCheckMiddleware", + # "AgentSupport", + # "BanMiddleware", + # "IsMaintenance", + # "LinkCheck", + # "LogMiddleware", + # "SchedulerMiddleware", + # "ThrottlingMiddleware", + # "SupportMiddleware", + # "LinkCheckMiddleware", + "ConfigMiddleware", ) - -# -# if __name__ == "middlewares": -# dp.middleware.setup(ThrottlingMiddleware()) -# dp.middleware.setup(LinkCheckMiddleware()) -# dp.middleware.setup(SupportMiddleware()) -# dp.middleware.setup(IsMaintenance()) -# dp.middleware.setup(SchedulerMiddleware(scheduler)) -# dp.middleware.setup(BanMiddleware()) -# dp.middleware.setup(LogMiddleware()) From 9b3d1f27c8a7cb67285cfe99af0eff03576fcf7a Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:55:39 +0300 Subject: [PATCH 026/148] =?UTF-8?q?=F0=9F=9A=9A=20rename=20default=20folde?= =?UTF-8?q?r=20to=20reply?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/__init__.py | 2 +- .../db_api => tgbot/keyboards/reply}/__init__.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{infrastructure/db_api => tgbot/keyboards/reply}/__init__.py (100%) diff --git a/src/tgbot/keyboards/__init__.py b/src/tgbot/keyboards/__init__.py index fd9825f..a5a25a5 100644 --- a/src/tgbot/keyboards/__init__.py +++ b/src/tgbot/keyboards/__init__.py @@ -1,4 +1,4 @@ from . import ( - default, + reply, inline, ) diff --git a/src/infrastructure/db_api/__init__.py b/src/tgbot/keyboards/reply/__init__.py similarity index 100% rename from src/infrastructure/db_api/__init__.py rename to src/tgbot/keyboards/reply/__init__.py From 1adc7f127ed0ff72618a4154cefddeb6b73de7a8 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:56:43 +0300 Subject: [PATCH 027/148] =?UTF-8?q?=E2=9C=A8=20Add=20funcs=20for=20sending?= =?UTF-8?q?=20and=20broadcasting=20messages=20with=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/app/__init__.py | 4 ++ src/tgbot/services/app/broadcaster.py | 83 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/tgbot/services/app/broadcaster.py diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py index e69de29..b54b049 100644 --- a/src/tgbot/services/app/__init__.py +++ b/src/tgbot/services/app/__init__.py @@ -0,0 +1,4 @@ +from .broadcaster import ( + send_message, + broadcast, +) \ No newline at end of file diff --git a/src/tgbot/services/app/broadcaster.py b/src/tgbot/services/app/broadcaster.py new file mode 100644 index 0000000..ce9dd64 --- /dev/null +++ b/src/tgbot/services/app/broadcaster.py @@ -0,0 +1,83 @@ +import asyncio +import logging +from typing import Union + +from aiogram import Bot +from aiogram import exceptions +from aiogram.types import InlineKeyboardMarkup + + +async def send_message( + bot: Bot, + user_id: Union[int, str], + text: str, + disable_notification: bool = False, + reply_markup: InlineKeyboardMarkup = None, +) -> bool: + """ + Safe messages sender + + :param bot: Bot instance. + :param user_id: user id. If str - must contain only digits. + :param text: text of the message. + :param disable_notification: disable notification or not. + :param reply_markup: reply markup. + :return: success. + """ + try: + await bot.send_message( + user_id, + text, + disable_notification=disable_notification, + reply_markup=reply_markup, + ) + except exceptions.TelegramBadRequest as e: + logging.error("Telegram server says - Bad Request: chat not found") + except exceptions.TelegramForbiddenError: + logging.error(f"Target [ID:{user_id}]: got TelegramForbiddenError") + except exceptions.TelegramRetryAfter as e: + logging.error( + f"Target [ID:{user_id}]: Flood limit is exceeded. Sleep {e.retry_after} seconds." + ) + await asyncio.sleep(e.retry_after) + return await send_message( + bot, user_id, text, disable_notification, reply_markup + ) # Recursive call + except exceptions.TelegramAPIError: + logging.exception(f"Target [ID:{user_id}]: failed") + else: + logging.info(f"Target [ID:{user_id}]: success") + return True + return False + + +async def broadcast( + bot: Bot, + users: list[Union[str, int]], + text: str, + disable_notification: bool = False, + reply_markup: InlineKeyboardMarkup = None, +) -> int: + """ + Simple broadcaster. + :param bot: Bot instance. + :param users: List of users. + :param text: Text of the message. + :param disable_notification: Disable notification or not. + :param reply_markup: Reply markup. + :return: Count of messages. + """ + count = 0 + try: + for user_id in users: + if await send_message( + bot, user_id, text, disable_notification, reply_markup + ): + count += 1 + await asyncio.sleep( + 0.05 + ) # 20 messages per second (Limit: 30 messages per second) + finally: + logging.info(f"{count} messages successful sent.") + + return count From ac5abbd05ff0cfdc0f71a61dfc298165e1dd5f9f Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:57:39 +0300 Subject: [PATCH 028/148] =?UTF-8?q?=E2=9C=A8=20Add=20start=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/start.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/tgbot/handlers/start.py diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py new file mode 100644 index 0000000..4009c27 --- /dev/null +++ b/src/tgbot/handlers/start.py @@ -0,0 +1,9 @@ +from aiogram import Router, types +from aiogram.filters import CommandStart + +start_router = Router() + + +@start_router.message(CommandStart()) +async def start_handler(message: types.Message): + await message.answer("Привет {user}".format(user=message.from_user.full_name)) From 72f81677191a3bbea0f0020455ca668a4edb4807 Mon Sep 17 00:00:00 2001 From: David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com> Date: Sun, 24 Mar 2024 22:57:55 +0300 Subject: [PATCH 029/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 164 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 93 insertions(+), 71 deletions(-) diff --git a/bot.py b/bot.py index d3c4845..31cdd40 100644 --- a/bot.py +++ b/bot.py @@ -1,89 +1,111 @@ -# noinspection PyUnresolvedReferences +import asyncio import logging -import os -import django +import betterlogging as bl from aiogram import ( - executor, Dispatcher, + Dispatcher, Bot, ) +from aiogram.client.default import DefaultBotProperties +from aiogram.enums import ParseMode +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.fsm.storage.redis import RedisStorage, DefaultKeyBuilder -# noinspection PyUnresolvedReferences -from django_project.telegrambot.telegrambot import ( - settings, -) -from loader import ( - dp, - scheduler, -) -from src.tgbot.filters import IsGroup, IsAdmin, IsPrivate -from src.infrastructure.db_api.db_commands import ( - reset_view_limit, -) -from src.tgbot.services.app.logger import ( - setup_logger, -) -from src.tgbot.services.app.notify_admins import ( - AdminNotification, -) -from src.tgbot.services.app.set_bot_commands import ( - set_default_commands, -) -from src.tgbot.middlewares import ( - ThrottlingMiddleware, - LinkCheckMiddleware, - SupportMiddleware, - IsMaintenance, - SchedulerMiddleware, - BanMiddleware, - LogMiddleware +from src.tgbot.config import Config, load_config +from src.tgbot.handlers import routers_list +from src.tgbot.middlewares import ConfigMiddleware +from src.tgbot.services.app import ( + broadcaster ) -def register_all_middlewares(dp: Dispatcher) -> None: - dp.setup_middleware(ThrottlingMiddleware()) - dp.setup_middleware(LinkCheckMiddleware()) - dp.setup_middleware(SupportMiddleware()) - dp.setup_middleware(IsMaintenance()) - dp.setup_middleware(SchedulerMiddleware(scheduler)) - dp.setup_middleware(BanMiddleware()) - dp.setup_middleware(LogMiddleware()) +async def on_startup(bot: Bot, admin_ids: list[int]) -> None: + await broadcaster.broadcast(bot, admin_ids, "Бот запущен") -def register_all_filters(dp: Dispatcher) -> None: - dp.filters_factory.bind(IsGroup) - dp.filters_factory.bind(IsPrivate) - dp.filters_factory.bind(IsAdmin) +def register_global_middlewares( + dp: Dispatcher, + config: Config +) -> None: + middleware_types = [ + ConfigMiddleware(config) + # ThrottlingMiddleware(), + # LinkCheckMiddleware(), + # SupportMiddleware(), + # IsMaintenance(), + # SchedulerMiddleware(scheduler), + # BanMiddleware(), + # LogMiddleware(), + ] + for middleware_type in middleware_types: + dp.message.outer_middleware(middleware_type) + dp.callback_query.outer_middleware(middleware_type) -async def on_startup(dispatcher) -> None: - await set_default_commands(dispatcher) - scheduler.add_job( - func=reset_view_limit, - trigger="cron", - hour=0, - id="reset_view_limit", - replace_existing=True, - ) - await AdminNotification.send(dispatcher) +def setup_logging(): + """ + Set up logging configuration for the application. + This method initializes the logging configuration for the application. + It sets the log level to INFO and configures a basic colorized log for + output. The log format includes the filename, line number, log level, + timestamp, logger name, and log message. -def setup_django(): - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "django_project.telegrambot.telegrambot.settings" - ) - os.environ.update({"DJANGO_ALLOW_ASYNC_UNSAFE": "true"}) - django.setup() + Returns: + None + Example usage: + setup_logging() + """ + log_level = logging.INFO + bl.basic_colorized_config(level=log_level) -if __name__ == "__main__": - setup_django() - setup_logger("INFO", ["aiogram.bot.api"]) - # noinspection PyUnresolvedReferences - from src.tgbot import ( - filters, - middlewares, - handlers + logging.basicConfig( + level=logging.INFO, + format="%(filename)s:%(lineno)d #%(levelname)-8s [%(asctime)s] - %(name)s - %(message)s", ) + logger = logging.getLogger(__name__) + logger.info("Starting bot") + + +def get_storage(config): + """ + Return storage based on the provided configuration. + + Args: + config (Config): The configuration object. - scheduler.start() - executor.start_polling(dp, on_startup=on_startup, skip_updates=True) + Returns: + Storage: The storage object based on the configuration. + + """ + if config.tg_bot.use_redis: + return RedisStorage.from_url( + config.redis.dsn(), + key_builder=DefaultKeyBuilder(with_bot_id=True, with_destiny=True), + ) + else: + return MemoryStorage() + + +async def main(): + setup_logging() + + config = load_config() + storage = get_storage(config) + + bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) + dp = Dispatcher(storage=storage) + + dp.include_routers(*routers_list) + + register_global_middlewares(dp, config) + + await on_startup(bot, config.tg_bot.admin_ids) + await dp.start_polling(bot) + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except (KeyboardInterrupt, SystemExit): + logging.error("Бот был выключен!") From 45b8ee24e406c5116ed9ded53c7693eeb1d1e901 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:16:38 +0300 Subject: [PATCH 030/148] =?UTF-8?q?=F0=9F=8E=89=20frontend=20for=20tg=20mi?= =?UTF-8?q?ni=20apps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.eslintrc.cjs | 18 + frontend/.gitignore | 24 + frontend/index.html | 14 + frontend/package-lock.json | 4010 +++++++++++++++++++++++++++++++++ frontend/package.json | 36 + frontend/public/vite.svg | 1 + frontend/src/App.tsx | 37 + frontend/src/assets/react.svg | 1 + frontend/src/main.tsx | 10 + frontend/src/style.ts | 58 + frontend/src/vite-env.d.ts | 1 + frontend/tsconfig.json | 25 + frontend/tsconfig.node.json | 11 + frontend/vite.config.ts | 7 + photos/.gitkeep | 0 15 files changed, 4253 insertions(+) create mode 100644 frontend/.eslintrc.cjs create mode 100644 frontend/.gitignore create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/style.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts delete mode 100644 photos/.gitkeep diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..3d504cb --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: {browser: true, es2020: true}, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + {allowConstantExport: true}, + ], + }, +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..f4fae88 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + Vite + React + TS + + +

+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..061d6a3 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4010 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@mui/icons-material": "^5.15.15", + "@mui/material": "^5.15.15", + "@vkruglikov/react-telegram-web-app": "^2.1.9", + "axios": "^1.6.8", + "bootstrap": "^5.2.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", + "styled-components": "^6.1.8", + "yup": "^1.4.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.1.tgz", + "integrity": "sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", + "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", + "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", + "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", + "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.15", + "@mui/system": "^5.15.15", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", + "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.14", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.15.15", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", + "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.14", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", + "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.15.0.tgz", + "integrity": "sha512-O63bJ7p909pRRQfOJ0k/Jp8gNFMud+ZzLLG5EBWquylHxmRT2k18M2ifg8WyjCgFVdpA7+rI0YZ8EkAtg6dSUw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.15.0.tgz", + "integrity": "sha512-5UywPdmC9jiVOShjQx4uuIcnTQOf85iA4jgg8bkFoH5NYWFfAfrJpv5eeokmTdSmYwUTT5IrcrBCJNkowhrZDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.15.0.tgz", + "integrity": "sha512-hNkt75uFfWpRxHItCBmbS0ba70WnibJh6yz60WShSWITLlVRbkvAu1E/c7RlliPY4ajhqJd0UPZz//gNalTd4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.15.0.tgz", + "integrity": "sha512-HnC5bTP7qdfO9nUw/mBhNcjOEZfbS8NwV+nFegiMhYOn1ATAGZF4kfAxR9BuZevBrebWCxMmxm8NCU1CUoz+wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.15.0.tgz", + "integrity": "sha512-QGOIQIJZeIIqMsc4BUGe8TnV4dkXhSW2EhaQ1G4LqMUNpkyeLztvlDlOoNHn7SR7a4dBANdcEbPkkEzz3rzjzA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.15.0.tgz", + "integrity": "sha512-PS/Cp8CinYgoysQ8i4UXYH/TZl06fXszvY/RDkyBYgUB1+tKyOMS925/4FZhfrhkl3XQEKjMc3BKtsxpB9Tz9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.15.0.tgz", + "integrity": "sha512-XzOsnD6lGDP+k+vGgTYAryVGu8N89qpjMN5BVFUj75dGVFP3FzIVAufJAraxirpDwEQZA7Gjs0Vo5p4UmnnjsA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.15.0.tgz", + "integrity": "sha512-+ScJA4Epbx/ZQGjDnbvTAcb8ZD06b+TlIka2UkujbKf1I/A+yrvEcJwG3/27zMmvcWMQyeCJhbL9TlSjzL0B7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.15.0.tgz", + "integrity": "sha512-1cUSvYgnyTakM4FDyf/GxUCDcqmj/hUh1NOizEOJU7+D5xEfFGCxgcNOs3hYBeRMUCcGmGkt01EhD3ILgKpGHQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.15.0.tgz", + "integrity": "sha512-3A1FbHDbBUvpJXFAZwVsiROIcstVHP9AX/cwnyIhAp+xyQ1cBCxywKtuzmw0Av1MDNNg/y/9dDHtNypfRa8bdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.15.0.tgz", + "integrity": "sha512-hYPbhg9ow6/mXIkojc8LOeiip2sCTuw1taWyoOXTOWk9vawIXz8x7B4KkgWUAtvAElssxhSyEXr2EZycH/FGzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.15.0.tgz", + "integrity": "sha512-511qln5mPSUKwv7HI28S1jCD1FK+2WbX5THM9A9annr3c1kzmfnf8Oe3ZakubEjob3IV6OPnNNcesfy+adIrmw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.15.0.tgz", + "integrity": "sha512-4qKKGTDIv2bQZ+afhPWqPL+94+dLtk4lw1iwbcylKlLNqQ/Yyjof2CFYBxf6npiDzPV+zf4EWRiHb26/4Vsm9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.15.0.tgz", + "integrity": "sha512-nEtaFBHp1OnbOf+tz66DtID579sNRHGgMC23to8HUyVuOCpCMD0CvRNqiDGLErLNnwApWIUtUl1VvuovCWUxwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.15.0.tgz", + "integrity": "sha512-5O49NykwSgX6iT2HgZ6cAoGHt6T/FqNMB5OqFOGxU/y1GyFSHquox1sK2OqApQc0ANxiHFQEMNDLNVCL7AUDnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.15.0.tgz", + "integrity": "sha512-YA0hTwCunmKNeTOFWdJuKhdXse9jBqgo34FDo+9aS0spfCkp+wj0o1bCcOOTu+0P48O95GTfkLTAaVonwNuIdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/react": { + "version": "18.2.79", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", + "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz", + "integrity": "sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz", + "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/type-utils": "7.7.0", + "@typescript-eslint/utils": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz", + "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz", + "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz", + "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/utils": "7.7.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz", + "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz", + "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz", + "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "semver": "^7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz", + "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.7.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@vkruglikov/react-telegram-web-app": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vkruglikov/react-telegram-web-app/-/react-telegram-web-app-2.1.9.tgz", + "integrity": "sha512-m8D5oUuSQN1z8xiG3ufh7tBbWpmp4zLtZv2rNsC2LP0Rp5vJI/hwZNk65h7wLDE740wPlE2R6bOSikm6FdUTtQ==", + "peerDependencies": { + "react": "^18", + "react-dom": "^18" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bootstrap": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.6" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001611", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001611.tgz", + "integrity": "sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.745", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz", + "integrity": "sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.6.tgz", + "integrity": "sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.15.0.tgz", + "integrity": "sha512-i0ir57IMF5o7YvNYyUNeIGG+IZaaucnGZAOsSctO2tPLXlCEaZzyBa+QhpHNSgtpyLMoDev2DyN6a7J1dQA8Tw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.15.0", + "@rollup/rollup-android-arm64": "4.15.0", + "@rollup/rollup-darwin-arm64": "4.15.0", + "@rollup/rollup-darwin-x64": "4.15.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.15.0", + "@rollup/rollup-linux-arm-musleabihf": "4.15.0", + "@rollup/rollup-linux-arm64-gnu": "4.15.0", + "@rollup/rollup-linux-arm64-musl": "4.15.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.15.0", + "@rollup/rollup-linux-riscv64-gnu": "4.15.0", + "@rollup/rollup-linux-s390x-gnu": "4.15.0", + "@rollup/rollup-linux-x64-gnu": "4.15.0", + "@rollup/rollup-linux-x64-musl": "4.15.0", + "@rollup/rollup-win32-arm64-msvc": "4.15.0", + "@rollup/rollup-win32-ia32-msvc": "4.15.0", + "@rollup/rollup-win32-x64-msvc": "4.15.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", + "integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..4b1408a --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,36 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@mui/icons-material": "^5.15.15", + "@mui/material": "^5.15.15", + "@vkruglikov/react-telegram-web-app": "^2.1.9", + "axios": "^1.6.8", + "bootstrap": "^5.2.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", + "styled-components": "^6.1.8", + "yup": "^1.4.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..0f96094 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import clsx from "clsx"; +import {useShowPopup} from "@vkruglikov/react-telegram-web-app"; + +export interface AppProps { + className?: string; +} + +export const App: React.FC = ({className}) => { + const showPopup = useShowPopup(); + + const showPopupOnClick = async () => { + const message = + "Thanks for using react-mini-app! I hope it helps you to create awesome Telegram Mini apps!"; + await showPopup({title: "Hey!", message: message}); + }; + + return ( +
+
+ +

react-mini-app

+
+ + +
+
+ ); +}; + +export default App; \ No newline at end of file diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..aded3e3 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import 'bootstrap/dist/css/bootstrap.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/style.ts b/frontend/src/style.ts new file mode 100644 index 0000000..1b79272 --- /dev/null +++ b/frontend/src/style.ts @@ -0,0 +1,58 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 0 20px; + background: radial-gradient(#9933ff, #6600cc); +`; + +export const Form = styled.form` + display: flex; + flex-direction: column; + width: 100%; + max-width: 400px; + background-color: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); +`; + +export const Label = styled.label` + font-size: 16px; + font-weight: 500; + margin-bottom: 8px; +`; + +export const Input = styled.input` + font-size: 16px; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 16px; + width: 100%; + + &:focus { + outline: none; + border-color: #9933ff; + } +`; + +export const SubmitButton = styled.button` + font-size: 16px; + font-weight: 500; + padding: 8px 16px; + border: none; + border-radius: 4px; + background-color: #9933ff; + color: white; + cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background-color: #8022ee; + } +`; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..5a33944 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/photos/.gitkeep b/photos/.gitkeep deleted file mode 100644 index e69de29..0000000 From 27244d6dfb367a54fa7dae4a7bd32cf2e9135f19 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:18:14 +0300 Subject: [PATCH 031/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 1248 ++++++++++++++++++++++-------------------------- pyproject.toml | 45 +- 2 files changed, 579 insertions(+), 714 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0eba968..2d69c9e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiofiles" @@ -13,132 +13,127 @@ files = [ [[package]] name = "aiogram" -version = "2.25.1" -description = "Is a pretty simple and fully asynchronous framework for Telegram Bot API" +version = "3.5.0" +description = "Modern and fully asynchronous framework for Telegram Bot API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aiogram-2.25.1-py3-none-any.whl", hash = "sha256:7bb770cd0459f1dbaea00578bf13fb2e6a1812f22adf94a988c11a7c0d5f33e1"}, - {file = "aiogram-2.25.1.tar.gz", hash = "sha256:59ad78fc0ebbef1fd471c15778a4594b60117e0d7373bc2ce7bcd192074d527d"}, + {file = "aiogram-3.5.0-py3-none-any.whl", hash = "sha256:70b5804671b87214768a2a63f19f1457684bd0c6cb6abd23e73bb16207fd7e58"}, + {file = "aiogram-3.5.0.tar.gz", hash = "sha256:1793deb24f36a6fc7b678c31d9a831cef7972765710a47a3e139645a99facba4"}, ] [package.dependencies] -aiohttp = ">=3.8.0,<3.9.0" -Babel = ">=2.9.1,<2.10.0" -certifi = ">=2021.10.8" -magic-filter = ">=1.0.9" +aiofiles = ">=23.2.1,<23.3.0" +aiohttp = ">=3.9.0,<3.10.0" +certifi = ">=2023.7.22" +magic-filter = ">=1.0.12,<1.1" +pydantic = ">=2.4.1,<2.8" +typing-extensions = ">=4.7.0,<=5.0" [package.extras] -fast = ["ujson (>=1.35)", "uvloop (>=0.16.0,<0.17.0)"] -proxy = ["aiohttp-socks (>=0.5.3,<0.6.0)"] +cli = ["aiogram-cli (>=1.0.3,<1.1.0)"] +dev = ["black (>=23.10.0,<23.11.0)", "isort (>=5.12.0,<5.13.0)", "mypy (>=1.6.1,<1.7.0)", "packaging (>=23.1,<24.0)", "pre-commit (>=3.5.0,<3.6.0)", "ruff (>=0.1.1,<0.2.0)", "toml (>=0.10.2,<0.11.0)"] +docs = ["furo (>=2023.9.10,<2023.10.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.16.1,<2.17.0)", "pymdown-extensions (>=10.3,<11.0)", "sphinx (>=7.2.6,<7.3.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.1.0,<2.2.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.3.2a0,<0.4.0)", "towncrier (>=23.6.0,<23.7.0)"] +fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)"] +i18n = ["babel (>=2.13.0,<2.14.0)"] +proxy = ["aiohttp-socks (>=0.8.3,<0.9.0)"] +redis = ["redis[hiredis] (>=5.0.1,<5.1.0)"] +test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.19.0,<3.20.0)", "pytest (>=7.4.2,<7.5.0)", "pytest-aiohttp (>=1.0.5,<1.1.0)", "pytest-asyncio (>=0.21.1,<0.22.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-html (>=4.0.2,<4.1.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.12.0,<3.13.0)", "pytest-mypy (>=0.10.3,<0.11.0)", "pytz (>=2023.3,<2024.0)"] [[package]] name = "aiohttp" -version = "3.8.6" +version = "3.9.2" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, + {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, + {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, + {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, + {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, + {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, + {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, + {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, + {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, + {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, + {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, + {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, ] [package.dependencies] aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" @@ -154,6 +149,37 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + +[[package]] +name = "anyio" +version = "4.3.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "apscheduler" version = "3.10.4" @@ -182,31 +208,6 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] -[[package]] -name = "asgiref" -version = "3.7.2" -description = "ASGI specs, helper code, and adapters" -optional = false -python-versions = ">=3.7" -files = [ - {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, - {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, -] - -[package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] - -[[package]] -name = "async-lru" -version = "2.0.4" -description = "Simple LRU cache for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, - {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, -] - [[package]] name = "async-timeout" version = "4.0.3" @@ -218,63 +219,6 @@ files = [ {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] -[[package]] -name = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} - -[package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] - [[package]] name = "attrs" version = "23.2.0" @@ -295,19 +239,16 @@ tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] -name = "babel" -version = "2.9.1" -description = "Internationalization utilities" +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7,<4.0" files = [ - {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, - {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] -[package.dependencies] -pytz = ">=2015.7" - [[package]] name = "better-profanity" version = "0.7.0" @@ -582,13 +523,13 @@ cron = ["capturer (>=2.4)"] [[package]] name = "cyclonedx-python-lib" -version = "6.4.1" +version = "6.4.4" description = "Python library for CycloneDX" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "cyclonedx_python_lib-6.4.1-py3-none-any.whl", hash = "sha256:42d50052c4604e8d6a91753e51bca33d668fb82adc1aab3f4eb54b89fa61cbc0"}, - {file = "cyclonedx_python_lib-6.4.1.tar.gz", hash = "sha256:aca5d8cf10f8d8420ba621e0cf4a24b98708afb68ca2ca72d7f2cc6394c75681"}, + {file = "cyclonedx_python_lib-6.4.4-py3-none-any.whl", hash = "sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341"}, + {file = "cyclonedx_python_lib-6.4.4.tar.gz", hash = "sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8"}, ] [package.dependencies] @@ -641,49 +582,15 @@ files = [ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] -[[package]] -name = "django" -version = "4.2" -description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Django-4.2-py3-none-any.whl", hash = "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78"}, - {file = "Django-4.2.tar.gz", hash = "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"}, -] - -[package.dependencies] -asgiref = ">=3.6.0,<4" -sqlparse = ">=0.3.1" -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -argon2 = ["argon2-cffi (>=19.1.0)"] -bcrypt = ["bcrypt"] - -[[package]] -name = "django-jazzmin" -version = "2.6.0" -description = "Drop-in theme for django admin, that utilises AdminLTE 3 & Bootstrap 4 to make yo' admin look jazzy" -optional = false -python-versions = ">=3.6.2" -files = [ - {file = "django_jazzmin-2.6.0-py3-none-any.whl", hash = "sha256:fb554c2d564649c65243b13385121fdbdda58521f49544f9d7cb9c414a4908d4"}, - {file = "django_jazzmin-2.6.0.tar.gz", hash = "sha256:5bb07055cf19183030724f976904fd8b6337559727959340a43832fab0531812"}, -] - -[package.dependencies] -django = ">=2.2" - [[package]] name = "environs" -version = "10.2.0" +version = "10.3.0" description = "simplified environment variable parsing" optional = false python-versions = ">=3.8" files = [ - {file = "environs-10.2.0-py3-none-any.whl", hash = "sha256:579dddb252ef4bb83a302df82a99c98f6f3db30f043d1b7acff36264b0bfdc69"}, - {file = "environs-10.2.0.tar.gz", hash = "sha256:9513dd388c1eeb8e82f1ea5a701356abfb7a3d79925bff937ade67fe096e420d"}, + {file = "environs-10.3.0-py3-none-any.whl", hash = "sha256:feeaf28f17fd0499f9cd7c0fcf408c6d82c308e69e335eb92d09322fc9ed8138"}, + {file = "environs-10.3.0.tar.gz", hash = "sha256:cc421ddb143fa30183568164755aa113a160e555cd19e97e664c478662032c24"}, ] [package.dependencies] @@ -698,18 +605,18 @@ tests = ["environs[django]", "pytest"] [[package]] name = "filelock" -version = "3.13.1" +version = "3.13.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -730,13 +637,13 @@ pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "flatbuffers" -version = "23.5.26" +version = "24.3.25" description = "The FlatBuffers serialization format for Python" optional = false python-versions = "*" files = [ - {file = "flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1"}, - {file = "flatbuffers-23.5.26.tar.gz", hash = "sha256:9ea1144cac05ce5d86e2859f431c6cd5e66cd9c78c558317c7955fb8d4c78d89"}, + {file = "flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812"}, + {file = "flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4"}, ] [[package]] @@ -825,6 +732,17 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + [[package]] name = "html5lib" version = "1.1" @@ -846,6 +764,51 @@ chardet = ["chardet (>=2.2)"] genshi = ["genshi"] lxml = ["lxml"] +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "humanfriendly" version = "10.0" @@ -862,13 +825,13 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "identify" -version = "2.5.33" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -876,13 +839,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -910,29 +873,15 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] -[[package]] -name = "jsonfield" -version = "3.1.0" -description = "A reusable Django field that allows you to store validated JSON in your model." -optional = false -python-versions = ">=3.6" -files = [ - {file = "jsonfield-3.1.0-py3-none-any.whl", hash = "sha256:df857811587f252b97bafba42e02805e70a398a7a47870bc6358a0308dd689ed"}, - {file = "jsonfield-3.1.0.tar.gz", hash = "sha256:7e4e84597de21eeaeeaaa7cc5da08c61c48a9b64d0c446b2d71255d01812887a"}, -] - -[package.dependencies] -Django = ">=2.2" - [[package]] name = "license-expression" -version = "30.2.0" +version = "30.3.0" description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "license-expression-30.2.0.tar.gz", hash = "sha256:599928edd995c43fc335e0af342076144dc71cb858afa1ed9c1c30c4e81794f5"}, - {file = "license_expression-30.2.0-py3-none-any.whl", hash = "sha256:1a7dc2bb2d09cdc983d072e4f9adc787e107e09def84cbb3919baaaf4f8e6fa1"}, + {file = "license-expression-30.3.0.tar.gz", hash = "sha256:1295406f736b4f395ff069aec1cebfad53c0fcb3cf57df0f5ec58fc7b905aea5"}, + {file = "license_expression-30.3.0-py3-none-any.whl", hash = "sha256:ae0ba9a829d6909c785dc2f0131f13d10d68318e4a5f28af5ef152d6b52f9b41"}, ] [package.dependencies] @@ -982,22 +931,21 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "marshmallow" -version = "3.20.2" +version = "3.21.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, - {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, + {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, + {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["pre-commit (>=2.4,<4.0)"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -1041,67 +989,67 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msgpack" -version = "1.0.7" +version = "1.0.8" description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, - {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, - {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, - {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, - {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, - {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, - {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, - {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, - {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, - {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, - {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, - {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, - {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, - {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, - {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, - {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, - {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, - {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, - {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, - {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, - {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, - {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, - {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, - {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, - {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, - {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, - {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] [[package]] @@ -1205,38 +1153,38 @@ files = [ [[package]] name = "mypy" -version = "1.8.0" +version = "1.9.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, + {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, + {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, + {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, + {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, + {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, + {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, + {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, + {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, + {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, + {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, + {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, + {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, + {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, + {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, + {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, + {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, + {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, + {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, + {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, + {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, + {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, + {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, ] [package.dependencies] @@ -1292,81 +1240,81 @@ opencv-python-headless = "*" [[package]] name = "numpy" -version = "1.26.3" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, - {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, - {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, - {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, - {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, - {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, - {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, - {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, - {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, - {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, - {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, - {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, - {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, - {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, - {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, - {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, - {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, - {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, - {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, - {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, - {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, - {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, - {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] [[package]] name = "onnxruntime" -version = "1.17.0" +version = "1.17.3" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.17.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d2b22a25a94109cc983443116da8d9805ced0256eb215c5e6bc6dcbabefeab96"}, - {file = "onnxruntime-1.17.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4c87d83c6f58d1af2675fc99e3dc810f2dbdb844bcefd0c1b7573632661f6fc"}, - {file = "onnxruntime-1.17.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dba55723bf9b835e358f48c98a814b41692c393eb11f51e02ece0625c756b797"}, - {file = "onnxruntime-1.17.0-cp310-cp310-win32.whl", hash = "sha256:ee48422349cc500273beea7607e33c2237909f58468ae1d6cccfc4aecd158565"}, - {file = "onnxruntime-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f34cc46553359293854e38bdae2ab1be59543aad78a6317e7746d30e311110c3"}, - {file = "onnxruntime-1.17.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:16d26badd092c8c257fa57c458bb600d96dc15282c647ccad0ed7b2732e6c03b"}, - {file = "onnxruntime-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f1273bebcdb47ed932d076c85eb9488bc4768fcea16d5f2747ca692fad4f9d3"}, - {file = "onnxruntime-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb60fd3c2c1acd684752eb9680e89ae223e9801a9b0e0dc7b28adabe45a2e380"}, - {file = "onnxruntime-1.17.0-cp311-cp311-win32.whl", hash = "sha256:4b038324586bc905299e435f7c00007e6242389c856b82fe9357fdc3b1ef2bdc"}, - {file = "onnxruntime-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:93d39b3fa1ee01f034f098e1c7769a811a21365b4883f05f96c14a2b60c6028b"}, - {file = "onnxruntime-1.17.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:90c0890e36f880281c6c698d9bc3de2afbeee2f76512725ec043665c25c67d21"}, - {file = "onnxruntime-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7466724e809a40e986b1637cba156ad9fc0d1952468bc00f79ef340bc0199552"}, - {file = "onnxruntime-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d47bee7557a8b99c8681b6882657a515a4199778d6d5e24e924d2aafcef55b0a"}, - {file = "onnxruntime-1.17.0-cp312-cp312-win32.whl", hash = "sha256:bb1bf1ee575c665b8bbc3813ab906e091a645a24ccc210be7932154b8260eca1"}, - {file = "onnxruntime-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac2f286da3494b29b4186ca193c7d4e6a2c1f770c4184c7192c5da142c3dec28"}, - {file = "onnxruntime-1.17.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1ec485643b93e0a3896c655eb2426decd63e18a278bb7ccebc133b340723624f"}, - {file = "onnxruntime-1.17.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83c35809cda898c5a11911c69ceac8a2ac3925911854c526f73bad884582f911"}, - {file = "onnxruntime-1.17.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa464aa4d81df818375239e481887b656e261377d5b6b9a4692466f5f3261edc"}, - {file = "onnxruntime-1.17.0-cp38-cp38-win32.whl", hash = "sha256:b7b337cd0586f7836601623cbd30a443df9528ef23965860d11c753ceeb009f2"}, - {file = "onnxruntime-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:fbb9faaf51d01aa2c147ef52524d9326744c852116d8005b9041809a71838878"}, - {file = "onnxruntime-1.17.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:5a06ab84eaa350bf64b1d747b33ccf10da64221ed1f38f7287f15eccbec81603"}, - {file = "onnxruntime-1.17.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d3d11db2c8242766212a68d0b139745157da7ce53bd96ba349a5c65e5a02357"}, - {file = "onnxruntime-1.17.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5632077c3ab8b0cd4f74b0af9c4e924be012b1a7bcd7daa845763c6c6bf14b7d"}, - {file = "onnxruntime-1.17.0-cp39-cp39-win32.whl", hash = "sha256:61a12732cba869b3ad2d4e29ab6cb62c7a96f61b8c213f7fcb961ba412b70b37"}, - {file = "onnxruntime-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:461fa0fc7d9c392c352b6cccdedf44d818430f3d6eacd924bb804fdea2dcfd02"}, + {file = "onnxruntime-1.17.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d86dde9c0bb435d709e51bd25991c9fe5b9a5b168df45ce119769edc4d198b15"}, + {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d87b68bf931ac527b2d3c094ead66bb4381bac4298b65f46c54fe4d1e255865"}, + {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e950cf0333cf114a155f9142e71da344d2b08dfe202763a403ae81cc02ebd1"}, + {file = "onnxruntime-1.17.3-cp310-cp310-win32.whl", hash = "sha256:0962a4d0f5acebf62e1f0bf69b6e0adf16649115d8de854c1460e79972324d68"}, + {file = "onnxruntime-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:468ccb8a0faa25c681a41787b1594bf4448b0252d3efc8b62fd8b2411754340f"}, + {file = "onnxruntime-1.17.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e8cd90c1c17d13d47b89ab076471e07fb85467c01dcd87a8b8b5cdfbcb40aa51"}, + {file = "onnxruntime-1.17.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a058b39801baefe454eeb8acf3ada298c55a06a4896fafc224c02d79e9037f60"}, + {file = "onnxruntime-1.17.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f823d5eb4807007f3da7b27ca972263df6a1836e6f327384eb266274c53d05d"}, + {file = "onnxruntime-1.17.3-cp311-cp311-win32.whl", hash = "sha256:b66b23f9109e78ff2791628627a26f65cd335dcc5fbd67ff60162733a2f7aded"}, + {file = "onnxruntime-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:570760ca53a74cdd751ee49f13de70d1384dcf73d9888b8deac0917023ccda6d"}, + {file = "onnxruntime-1.17.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:77c318178d9c16e9beadd9a4070d8aaa9f57382c3f509b01709f0f010e583b99"}, + {file = "onnxruntime-1.17.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23da8469049b9759082e22c41a444f44a520a9c874b084711b6343672879f50b"}, + {file = "onnxruntime-1.17.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2949730215af3f9289008b2e31e9bbef952012a77035b911c4977edea06f3f9e"}, + {file = "onnxruntime-1.17.3-cp312-cp312-win32.whl", hash = "sha256:6c7555a49008f403fb3b19204671efb94187c5085976ae526cb625f6ede317bc"}, + {file = "onnxruntime-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:58672cf20293a1b8a277a5c6c55383359fcdf6119b2f14df6ce3b140f5001c39"}, + {file = "onnxruntime-1.17.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4395ba86e3c1e93c794a00619ef1aec597ab78f5a5039f3c6d2e9d0695c0a734"}, + {file = "onnxruntime-1.17.3-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf354c04344ec38564fc22394e1fe08aa6d70d790df00159205a0055c4a4d3f"}, + {file = "onnxruntime-1.17.3-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a94b600b7af50e922d44b95a57981e3e35103c6e3693241a03d3ca204740bbda"}, + {file = "onnxruntime-1.17.3-cp38-cp38-win32.whl", hash = "sha256:5a335c76f9c002a8586c7f38bc20fe4b3725ced21f8ead835c3e4e507e42b2ab"}, + {file = "onnxruntime-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f56a86fbd0ddc8f22696ddeda0677b041381f4168a2ca06f712ef6ec6050d6d"}, + {file = "onnxruntime-1.17.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:e0ae39f5452278cd349520c296e7de3e90d62dc5b0157c6868e2748d7f28b871"}, + {file = "onnxruntime-1.17.3-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ff2dc012bd930578aff5232afd2905bf16620815f36783a941aafabf94b3702"}, + {file = "onnxruntime-1.17.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf6c37483782e4785019b56e26224a25e9b9a35b849d0169ce69189867a22bb1"}, + {file = "onnxruntime-1.17.3-cp39-cp39-win32.whl", hash = "sha256:351bf5a1140dcc43bfb8d3d1a230928ee61fcd54b0ea664c8e9a889a8e3aa515"}, + {file = "onnxruntime-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:57a3de15778da8d6cc43fbf6cf038e1e746146300b5f0b1fbf01f6f795dc6440"}, ] [package.dependencies] @@ -1401,13 +1349,13 @@ numpy = [ [[package]] name = "packageurl-python" -version = "0.13.4" +version = "0.15.0" description = "A purl aka. Package URL parser and builder" optional = false python-versions = ">=3.7" files = [ - {file = "packageurl-python-0.13.4.tar.gz", hash = "sha256:6eb5e995009cc73387095e0b507ab65df51357d25ddc5fce3d3545ad6dcbbee8"}, - {file = "packageurl_python-0.13.4-py3-none-any.whl", hash = "sha256:62aa13d60a0082ff115784fefdfe73a12f310e455365cca7c6d362161067f35f"}, + {file = "packageurl-python-0.15.0.tar.gz", hash = "sha256:f219b2ce6348185a27bd6a72e6fdc9f984e6c9fa157effa7cb93e341c49cdcc2"}, + {file = "packageurl_python-0.15.0-py3-none-any.whl", hash = "sha256:cdc6bd42dc30c4fc7f8f0ccb721fc31f8c33985dbffccb6e6be4c72874de48ca"}, ] [package.extras] @@ -1418,13 +1366,13 @@ test = ["pytest"] [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -1438,37 +1386,26 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] -[[package]] -name = "pbr" -version = "6.0.0" -description = "Python Build Reasonableness" -optional = false -python-versions = ">=2.6" -files = [ - {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, - {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, -] - [[package]] name = "pip" -version = "23.3.2" +version = "24.0" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.7" files = [ - {file = "pip-23.3.2-py3-none-any.whl", hash = "sha256:5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76"}, - {file = "pip-23.3.2.tar.gz", hash = "sha256:7fd9972f96db22c8077a1ee2691b172c8089b17a5652a44494a9ecb0d78f9149"}, + {file = "pip-24.0-py3-none-any.whl", hash = "sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"}, + {file = "pip-24.0.tar.gz", hash = "sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2"}, ] [[package]] name = "pip-api" -version = "0.0.30" +version = "0.0.33" description = "An unofficial, importable pip API" optional = false python-versions = ">=3.7" files = [ - {file = "pip-api-0.0.30.tar.gz", hash = "sha256:a05df2c7aa9b7157374bcf4273544201a0c7bae60a9c65bcf84f3959ef3896f3"}, - {file = "pip_api-0.0.30-py3-none-any.whl", hash = "sha256:2a0314bd31522eb9ffe8a99668b0d07fee34ebc537931e7b6483001dbedcbdc9"}, + {file = "pip-api-0.0.33.tar.gz", hash = "sha256:1c2522ae21efcb034d89cc99f6cf1025293b31c63c29ee98b23f03a85f36bdae"}, + {file = "pip_api-0.0.33-py3-none-any.whl", hash = "sha256:b8d6eb5a87d3a9e112a20a8e9d24a6fc12d4e1c94d7595eeaf74be11ad47276c"}, ] [package.dependencies] @@ -1476,13 +1413,13 @@ pip = "*" [[package]] name = "pip-audit" -version = "2.7.0" +version = "2.7.2" description = "A tool for scanning Python environments for known vulnerabilities" optional = false python-versions = ">=3.8" files = [ - {file = "pip_audit-2.7.0-py3-none-any.whl", hash = "sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522"}, - {file = "pip_audit-2.7.0.tar.gz", hash = "sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75"}, + {file = "pip_audit-2.7.2-py3-none-any.whl", hash = "sha256:49907430115baacb8bb7ffc1a2b689acfeac9d8534a43bffad3c73f8d8b32d52"}, + {file = "pip_audit-2.7.2.tar.gz", hash = "sha256:a12905e42dd452f43a2dbf895606d59c35348deed27b8cbaff8516423576fdfb"}, ] [package.dependencies] @@ -1499,7 +1436,7 @@ toml = ">=0.10" [package.extras] dev = ["build", "bump (>=1.3.2)", "pip-audit[doc,lint,test]"] doc = ["pdoc"] -lint = ["interrogate", "mypy", "ruff (<0.1.12)", "types-html5lib", "types-requests", "types-toml"] +lint = ["interrogate", "mypy", "ruff (<0.2.3)", "types-html5lib", "types-requests", "types-toml"] test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"] [[package]] @@ -1538,13 +1475,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest- [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -1553,13 +1490,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.6.0" +version = "3.7.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, + {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, ] [package.dependencies] @@ -1571,114 +1508,33 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "4.25.2" +version = "5.26.1" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, - {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, - {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, - {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, - {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, - {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, - {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, - {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, - {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, -] - -[[package]] -name = "psycopg2-binary" -version = "2.9.9" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, + {file = "protobuf-5.26.1-cp310-abi3-win32.whl", hash = "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23"}, + {file = "protobuf-5.26.1-cp310-abi3-win_amd64.whl", hash = "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33"}, + {file = "protobuf-5.26.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d"}, + {file = "protobuf-5.26.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca"}, + {file = "protobuf-5.26.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7"}, + {file = "protobuf-5.26.1-cp38-cp38-win32.whl", hash = "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc"}, + {file = "protobuf-5.26.1-cp38-cp38-win_amd64.whl", hash = "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e"}, + {file = "protobuf-5.26.1-cp39-cp39-win32.whl", hash = "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c"}, + {file = "protobuf-5.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c"}, + {file = "protobuf-5.26.1-py3-none-any.whl", hash = "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932"}, + {file = "protobuf-5.26.1.tar.gz", hash = "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51"}, ] [[package]] name = "py-serializable" -version = "1.0.0" +version = "1.0.3" description = "Library for serializing and deserializing Python Objects to and from JSON and XML." optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.8" files = [ - {file = "py_serializable-1.0.0-py3-none-any.whl", hash = "sha256:845a9399a16550e8703c3fb0da4fbb746a4e5f6cc4c95647c315c71fd6567cd5"}, - {file = "py_serializable-1.0.0.tar.gz", hash = "sha256:524df68c46315d7272959ae5296244e5a1e1e28330472ec214394162c39f545e"}, + {file = "py_serializable-1.0.3-py3-none-any.whl", hash = "sha256:afba815f465b9fe7ab1c1a56d1aa8880c8a9e67a6e28b7ed62d4696fa369caf8"}, + {file = "py_serializable-1.0.3.tar.gz", hash = "sha256:da3cb4b1f3cc5cc5ebecdd3dadbabd5f65d764357366fa64ee9cbaf0d4b70dcf"}, ] [package.dependencies] @@ -1697,55 +1553,113 @@ files = [ [[package]] name = "pydantic" -version = "1.10.13" -description = "Data validation and settings management using python type hints" +version = "2.7.0" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"}, + {file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.18.1" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"}, + {file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"}, + {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"}, + {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"}, + {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"}, + {file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"}, + {file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"}, + {file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"}, + {file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"}, + {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"}, + {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"}, + {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"}, + {file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"}, + {file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"}, + {file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"}, + {file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"}, + {file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"}, + {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"}, + {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"}, + {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"}, + {file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"}, + {file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"}, + {file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"}, + {file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"}, + {file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"}, + {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"}, + {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"}, + {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"}, + {file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"}, + {file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"}, + {file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"}, + {file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"}, + {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"}, + {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"}, + {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"}, + {file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"}, + {file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"}, + {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"}, + {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"}, + {file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyflakes" @@ -1775,13 +1689,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [package.extras] @@ -1818,24 +1732,6 @@ pluggy = ">=0.12,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] -[[package]] -name = "pytest-asyncio" -version = "0.23.3" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-asyncio-0.23.3.tar.gz", hash = "sha256:af313ce900a62fbe2b1aed18e37ad757f1ef9940c6b6a88e2954de38d6b1fb9f"}, - {file = "pytest_asyncio-0.23.3-py3-none-any.whl", hash = "sha256:37a9d912e8338ee7b4a3e917381d1c95bfc8682048cb0fbc35baba316ec1faba"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - [[package]] name = "python-dotenv" version = "1.0.1" @@ -1886,7 +1782,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1921,19 +1816,34 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "que-sdk" +version = "0.1.2" +description = "" +optional = false +python-versions = "<4.0,>=3.11" +files = [ + {file = "que_sdk-0.1.2-py3-none-any.whl", hash = "sha256:8a977239e46607e2462eee7ea97e404348e41be9b2ee73e9a2955456eaae10a9"}, + {file = "que_sdk-0.1.2.tar.gz", hash = "sha256:0ab09bf4d3af24ffdd693361569cd83346c751ed2d8b3c7093e9f7506c726d39"}, +] + +[package.dependencies] +backoff = ">=2.2.1,<3.0.0" +httpx = ">=0.27.0,<0.28.0" + [[package]] name = "redis" -version = "5.0.1" +version = "5.0.3" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"}, - {file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"}, + {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, + {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, ] [package.dependencies] -async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} [package.extras] hiredis = ["hiredis (>=1.0.0)"] @@ -1962,13 +1872,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.7.0" +version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.7.0" files = [ - {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, - {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, ] [package.dependencies] @@ -2006,19 +1916,19 @@ files = [ [[package]] name = "setuptools" -version = "69.0.3" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -2031,6 +1941,17 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -2042,22 +1963,6 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] -[[package]] -name = "sqlparse" -version = "0.4.4" -description = "A non-validating SQL parser." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, - {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, -] - -[package.extras] -dev = ["build", "flake8"] -doc = ["sphinx"] -test = ["pytest", "pytest-cov"] - [[package]] name = "sympy" version = "1.12" @@ -2072,23 +1977,6 @@ files = [ [package.dependencies] mpmath = ">=0.19" -[[package]] -name = "testresources" -version = "2.0.1" -description = "Testresources, a pyunit extension for managing expensive test resources" -optional = false -python-versions = "*" -files = [ - {file = "testresources-2.0.1-py2.py3-none-any.whl", hash = "sha256:67a361c3a2412231963b91ab04192209aa91a1aa052f0ab87245dbea889d1282"}, - {file = "testresources-2.0.1.tar.gz", hash = "sha256:ee9d1982154a1e212d4e4bac6b610800bfb558e4fb853572a827bc14a96e4417"}, -] - -[package.dependencies] -pbr = ">=1.8" - -[package.extras] -test = ["docutils", "fixtures", "testtools"] - [[package]] name = "toml" version = "0.10.2" @@ -2102,24 +1990,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] name = "tzdata" -version = "2023.4" +version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, - {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] @@ -2141,13 +2029,13 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] @@ -2156,25 +2044,15 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "uuid" -version = "1.30" -description = "UUID object and generation functions (Python 2.3 or higher)" -optional = false -python-versions = "*" -files = [ - {file = "uuid-1.30.tar.gz", hash = "sha256:1f87cc004ac5120466f36c5beae48b4c48cc411968eed0eaecd3da82aa96193f"}, -] - [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.25.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, + {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, ] [package.dependencies] @@ -2183,7 +2061,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -2303,4 +2181,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "7b8ccf81efa5f306de213446f2c3290ae238eb0b3571e6e749f9701865d6aefc" +content-hash = "a346fda0befa4fb6392617585e7b1017279edc57b1fe022e4c68c979a5edd525" diff --git a/pyproject.toml b/pyproject.toml index 29c82e9..bfe6b6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,46 +8,32 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -aiogram = "2.25.1" -environs = "10.2.0" -asyncpg = "0.29.0" -Django = "4.2" -psycopg2-binary = "2.9.9" -jsonfield = ">=3.1.0,<3.2.0" -asgiref = "3.7.2" -uuid = ">=1.30,<2.0" -pip = "23.3.2" -setuptools = "69.0.3" -django-jazzmin = ">=2.6.0,<2.7.0" +aiogram = "^3.4.1" +environs = "^10.2.0" better-profanity = ">=0.7.0,<0.8.0" -pytest = "7.4.4" -aiohttp = "<3.9.0" -async-lru = "2.0.4" -pytest-asyncio = "0.23.3" -apscheduler = "3.10.4" -nudenet = "3.0.8" -yarl = "1.9.4" -aiofiles = "23.2.1" -pre-commit = "3.6.0" -testresources = "^2.0.1" -pydantic = "1.10.13" -redis = "5.0.1" +pytest = "^7.4.4" +aiohttp = "<3.9.3" +apscheduler = "^3.10.4" +nudenet = "^3.0.8" +pre-commit = "^3.6.0" +pydantic = "^2.4.1" +redis = "^5.0.1" betterlogging = ">=0.2.1,<0.3.0" -flake8 = "7.0.0" -black = "23.12.1" +flake8 = "^7.0.0" +black = "^23.12.1" mypy = "^1.8.0" isort = "^5.13.2" deptry = "^0.12.0" ruff = "^0.1.14" pip-audit = "^2.7.0" +que-sdk = "^0.1.0" [tool.black] line-length = 99 exclude = [ "venv", - "django_project/telegrambot/common/migrations", - "django_project/telegrambot/usersmanage/migrations", - ".git" + ".git", + "deprecate/" ] target-version = ['py311'] @@ -64,6 +50,7 @@ disallow_untyped_calls = true warn_redundant_casts = true warn_unused_configs = true strict_equality = true +exclude = ["deprecated"] [tool.isort] profile = "black" @@ -77,7 +64,7 @@ include_trailing_comma = true line_length = 99 use_parentheses = true known_third_party = ['django', 'rest_framework_extensions', 'aiogram'] -known_first_party = ["common", "usersmanage",] +known_first_party = ["common", "usersmanage", ] skip = ["__init__.py", "app.py"] [tool.ruff] From 43b105dc7dfe670b5c46e853668383547337369b Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:18:39 +0300 Subject: [PATCH 032/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20requireme?= =?UTF-8?q?nts.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 123 ----------------------------------------------- 1 file changed, 123 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 70b05bc..0000000 --- a/requirements.txt +++ /dev/null @@ -1,123 +0,0 @@ -aiofiles==23.2.1 -aiogram==3.4.1 -aiohttp==3.9.3 -aiosignal==1.3.1 -annotated-types==0.6.0 -APScheduler==3.10.4 -asgiref==3.7.2 -async-lru==2.0.4 -async-timeout==4.0.3 -asyncpg==0.29.0 -attrs==23.2.0 -Babel==2.9.1 -better-profanity==0.7.0 -betterlogging==0.2.1 -black==23.12.1 -boolean.py==4.0 -build==1.0.3 -CacheControl==0.14.0 -certifi==2024.2.2 -cfgv==3.4.0 -chardet==5.2.0 -charset-normalizer==3.3.2 -cleo==2.1.0 -click==8.1.7 -colorama==0.4.6 -coloredlogs==15.0.1 -crashtest==0.4.1 -cyclonedx-python-lib==6.4.1 -defusedxml==0.7.1 -deptry==0.12.0 -distlib==0.3.8 -dulwich==0.21.7 -environs==10.2.0 -fastjsonschema==2.19.1 -filelock==3.13.1 -flake8==7.0.0 -flatbuffers==23.5.26 -frozenlist==1.4.1 -html5lib==1.1 -humanfriendly==10.0 -identify==2.5.33 -idna==3.6 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -installer==0.7.0 -isort==5.13.2 -jaraco.classes==3.3.1 -jsonfield==3.1.0 -keyring==24.3.0 -license-expression==30.2.0 -magic-filter==1.0.12 -markdown-it-py==3.0.0 -marshmallow==3.20.2 -mccabe==0.7.0 -mdurl==0.1.2 -more-itertools==10.2.0 -mpmath==1.3.0 -msgpack==1.0.7 -multidict==6.0.5 -mypy==1.8.0 -mypy-extensions==1.0.0 -nodeenv==1.8.0 -nudenet==3.0.8 -numpy==1.26.3 -onnxruntime==1.17.0 -opencv-python-headless==4.9.0.80 -packageurl-python==0.13.4 -packaging==23.2 -pathspec==0.12.1 -pbr==6.0.0 -pexpect==4.9.0 -pip-api==0.0.30 -pip-requirements-parser==32.0.1 -pip_audit==2.7.0 -pkginfo==1.9.6 -platformdirs==4.2.0 -pluggy==1.4.0 -poetry==1.7.1 -poetry-core==1.8.1 -poetry-plugin-export==1.6.0 -pre-commit==3.6.0 -protobuf==4.25.2 -psycopg2-binary==2.9.9 -ptyprocess==0.7.0 -py-serializable==1.0.0 -pycodestyle==2.11.1 -pydantic==2.5.3 -pydantic_core==2.14.6 -pyflakes==3.2.0 -Pygments==2.17.2 -pyparsing==3.1.1 -pyproject_hooks==1.0.0 -pyreadline3==3.4.1 -pytest==7.4.4 -pytest-asyncio==0.23.3 -python-dotenv==1.0.1 -pytz==2024.1 -pywin32-ctypes==0.2.2 -PyYAML==6.0.1 -rapidfuzz==3.6.1 -redis==5.0.1 -requests==2.31.0 -requests-toolbelt==1.0.0 -rich==13.7.0 -ruff==0.1.15 -shellingham==1.5.4 -six==1.16.0 -sortedcontainers==2.4.0 -sqlparse==0.4.4 -sympy==1.12 -testresources==2.0.1 -toml==0.10.2 -tomlkit==0.12.3 -trove-classifiers==2024.1.31 -typing_extensions==4.9.0 -tzdata==2023.4 -tzlocal==5.2 -urllib3==2.2.0 -uuid==1.30 -virtualenv==20.25.0 -webencodings==0.5.1 -yarl==1.9.4 -zipp==3.17.0 From 1cf986808fd8ca04dfb5f3a6be7e4f0f8697e31b Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:19:08 +0300 Subject: [PATCH 033/148] =?UTF-8?q?=F0=9F=93=9D=20update=20badges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 7b8c81a..70e6412 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,8 @@ enhancing its capabilities. ### :alembic: Built With ![Python](https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python&logoColor=blue)\ -![Django](https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=green)\ ![AIOHTTP](https://img.shields.io/badge/aiohttp-%232C5bb4.svg?style=for-the-badge&logo=aiohttp&logoColor=white)\ ![Poetry](https://img.shields.io/badge/Poetry-%233B82F6.svg?style=for-the-badge&logo=poetry&logoColor=0B3D8D)\ -![Babel](https://img.shields.io/badge/Babel-F9DC3e?style=for-the-badge&logo=babel&logoColor=black)\ -![Postgresql](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white)\ ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)\ ![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white) From 7598d9eb7dc3fc0db53805783f6d66a562213da6 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:20:25 +0300 Subject: [PATCH 034/148] =?UTF-8?q?=F0=9F=94=A7=20remove=20unused=20var=20?= =?UTF-8?q?and=20add=20new=20env=20var?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dist | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.env.dist b/.env.dist index bbf4e5d..3887437 100644 --- a/.env.dist +++ b/.env.dist @@ -5,15 +5,5 @@ IP= TIMEZONE= MODERATE_CHAT= -POSTGRES_USER= -POSTGRES_PASSWORD= -DB_HOST= -DB_PORT= -POSTGRES_DB= - -SECRET_KEY= -API_KEY= - -QIWI_KEY= -PHONE_NUMBER= -SECRET_P2= \ No newline at end of file +SIGNATURE_SECRET_KEY= +USE_REDIS= \ No newline at end of file From 645fe52c337976d9a1226c3b93730ea9117c2e1a Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:20:36 +0300 Subject: [PATCH 035/148] =?UTF-8?q?=F0=9F=94=A7=20add=20black=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/justfile b/justfile index 09527a4..b419de4 100644 --- a/justfile +++ b/justfile @@ -27,6 +27,9 @@ isort: @echo "🔄 Running isort..." @poetry run isort $(git ls-files '*.py') +black: + @poetry run black $(git ls-files '*.py') + # Audit packages audit: @echo "🔍 Auditing packages..." From c7869d4cea92d4e5ef44575e6b84f6e6407ab7d2 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:21:46 +0300 Subject: [PATCH 036/148] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20deprecate=20cod?= =?UTF-8?q?e=20that=20needs=20to=20be=20cleaned=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deprecated/admin/inline/customers.py | 1 - deprecated/admin/inline/mailing.py | 1 - deprecated/admin/inline/payments.py | 1 - deprecated/admin/inline/ref.py | 1 - deprecated/admin/inline/reply_menu.py | 1 - deprecated/admin/inline/setting.py | 1 - deprecated/app_scheduler.py | 1 - deprecated/create_forms_funcs.py | 8 ++--- deprecated/default/admin_default.py | 1 - deprecated/default/get_contact_default.py | 1 - deprecated/default/get_location_default.py | 1 - deprecated/default/get_photo.py | 1 - deprecated/determin_location.py | 5 ++- .../handlers/admins/advert/advertisement.py | 4 +-- .../handlers/admins/advert/mailing/create.py | 8 ++--- deprecated/handlers/admins/customers/users.py | 8 ++--- deprecated/handlers/admins/monitoring.py | 12 +++---- deprecated/handlers/admins/settings/admins.py | 14 ++++---- .../handlers/admins/settings/logs_user.py | 12 +++---- .../handlers/admins/settings/setting.py | 4 +-- .../handlers/admins/settings/tech_works.py | 12 +++---- deprecated/handlers/echo_handler.py | 2 +- deprecated/handlers/errors/error_handler.py | 1 - deprecated/handlers/groups/event_moderate.py | 10 +++--- deprecated/handlers/groups/start.py | 6 ++-- deprecated/handlers/users/back.py | 26 +++++++------- deprecated/handlers/users/brandbook.py | 12 +++---- deprecated/handlers/users/buy_unban.py | 14 ++++---- deprecated/handlers/users/change_datas.py | 34 ++++++++++--------- .../handlers/users/change_event_datas.py | 8 ++--- deprecated/handlers/users/event.py | 26 +++++++------- deprecated/handlers/users/event_list.py | 8 ++--- deprecated/handlers/users/filters.py | 30 ++++++++-------- deprecated/handlers/users/registration.py | 33 +++++++++--------- deprecated/handlers/users/start.py | 16 ++++----- deprecated/handlers/users/support.py | 2 +- deprecated/handlers/users/user_profile.py | 12 +++---- deprecated/handlers/users/verification.py | 8 ++--- deprecated/handlers/users/view_event.py | 8 ++--- deprecated/handlers/users/view_ques.py | 18 +++++----- deprecated/inline/admin_inline.py | 1 - deprecated/inline/back_inline.py | 1 - deprecated/inline/cancel_inline.py | 1 - .../inline/change_data_profile_inline.py | 1 - deprecated/inline/filters_inline.py | 1 - deprecated/inline/guide_inline.py | 1 - deprecated/inline/language_inline.py | 1 - deprecated/inline/main_menu_inline.py | 8 ++--- deprecated/inline/menu_profile_inline.py | 1 - deprecated/inline/payments_inline.py | 11 +++--- deprecated/inline/poster_inline.py | 6 +--- deprecated/inline/questionnaires_inline.py | 1 - deprecated/inline/registration_inline.py | 1 - deprecated/inline/settings_menu.py | 1 - deprecated/inline/support_inline.py | 6 +--- deprecated/language_ware.py | 4 ++- 56 files changed, 196 insertions(+), 222 deletions(-) diff --git a/deprecated/admin/inline/customers.py b/deprecated/admin/inline/customers.py index b5af811..9214a48 100644 --- a/deprecated/admin/inline/customers.py +++ b/deprecated/admin/inline/customers.py @@ -5,7 +5,6 @@ from aiogram.utils.callback_data import ( CallbackData, ) - from loader import ( _, ) diff --git a/deprecated/admin/inline/mailing.py b/deprecated/admin/inline/mailing.py index 1c693e3..ed58cc0 100644 --- a/deprecated/admin/inline/mailing.py +++ b/deprecated/admin/inline/mailing.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/admin/inline/payments.py b/deprecated/admin/inline/payments.py index 99149d6..1ab0d6f 100644 --- a/deprecated/admin/inline/payments.py +++ b/deprecated/admin/inline/payments.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/admin/inline/ref.py b/deprecated/admin/inline/ref.py index 021f3d6..fa696a5 100644 --- a/deprecated/admin/inline/ref.py +++ b/deprecated/admin/inline/ref.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/admin/inline/reply_menu.py b/deprecated/admin/inline/reply_menu.py index 2aa8669..ac366b8 100644 --- a/deprecated/admin/inline/reply_menu.py +++ b/deprecated/admin/inline/reply_menu.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/admin/inline/setting.py b/deprecated/admin/inline/setting.py index d9f7243..e92a6c2 100644 --- a/deprecated/admin/inline/setting.py +++ b/deprecated/admin/inline/setting.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/app_scheduler.py b/deprecated/app_scheduler.py index d68c5f2..b99d7a1 100644 --- a/deprecated/app_scheduler.py +++ b/deprecated/app_scheduler.py @@ -1,7 +1,6 @@ from aiogram.types import ( Message, ) - from loader import ( _, bot, diff --git a/deprecated/create_forms_funcs.py b/deprecated/create_forms_funcs.py index 1e56b4c..95a1f9b 100644 --- a/deprecated/create_forms_funcs.py +++ b/deprecated/create_forms_funcs.py @@ -7,19 +7,19 @@ from aiogram.utils.exceptions import ( BadRequest, ) - from loader import ( bot, ) -from src.tgbot.keyboards.inline.questionnaires_inline import ( - questionnaires_keyboard, -) + from deprecated.get_next_user_func import ( get_next_user, ) from deprecated.send_form_func import ( send_questionnaire, ) +from src.tgbot.keyboards.inline.questionnaires_inline import ( + questionnaires_keyboard, +) async def create_questionnaire( diff --git a/deprecated/default/admin_default.py b/deprecated/default/admin_default.py index 728e579..51d66c5 100644 --- a/deprecated/default/admin_default.py +++ b/deprecated/default/admin_default.py @@ -2,7 +2,6 @@ KeyboardButton, ReplyKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/default/get_contact_default.py b/deprecated/default/get_contact_default.py index 43e6f82..196919f 100644 --- a/deprecated/default/get_contact_default.py +++ b/deprecated/default/get_contact_default.py @@ -2,7 +2,6 @@ KeyboardButton, ReplyKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/default/get_location_default.py b/deprecated/default/get_location_default.py index 22aad08..0181a5a 100644 --- a/deprecated/default/get_location_default.py +++ b/deprecated/default/get_location_default.py @@ -2,7 +2,6 @@ KeyboardButton, ReplyKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/default/get_photo.py b/deprecated/default/get_photo.py index e19c7ea..cc4dcbd 100644 --- a/deprecated/default/get_photo.py +++ b/deprecated/default/get_photo.py @@ -2,7 +2,6 @@ KeyboardButton, ReplyKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/determin_location.py b/deprecated/determin_location.py index 0427666..10c37af 100644 --- a/deprecated/determin_location.py +++ b/deprecated/determin_location.py @@ -9,20 +9,19 @@ from aiogram.types import ( Message, ) - from loader import ( _, client, logger, ) + from src.infrastructure.YandexMap.exceptions import ( NothingFound, ) - from src.tgbot.keyboards.inline.registration_inline import ( confirm_keyboard, ) -from src.tgbot.services.app.AsyncObj import ( +from src.tgbot.misc.async_obj import ( AsyncObj, ) diff --git a/deprecated/handlers/admins/advert/advertisement.py b/deprecated/handlers/admins/advert/advertisement.py index f77816c..b96eaf2 100644 --- a/deprecated/handlers/admins/advert/advertisement.py +++ b/deprecated/handlers/admins/advert/advertisement.py @@ -5,12 +5,12 @@ CallbackQuery, Message, ) - from loader import ( _, dp, ) -from src.tgbot.filters.IsAdminFilter import ( + +from src.tgbot.filters.is_admin_filter import ( IsAdmin, ) from src.tgbot.keyboards.admin.inline.mailing import ( diff --git a/deprecated/handlers/admins/advert/mailing/create.py b/deprecated/handlers/admins/advert/mailing/create.py index 1b07c29..93b0356 100644 --- a/deprecated/handlers/admins/advert/mailing/create.py +++ b/deprecated/handlers/admins/advert/mailing/create.py @@ -15,19 +15,19 @@ escape_md, quote_html, ) - from loader import ( _, bot, dp, logger, ) -from src.tgbot.filters.IsAdminFilter import ( - IsAdmin, -) + from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.filters.is_admin_filter import ( + IsAdmin, +) from src.tgbot.keyboards.admin.inline.mailing import ( add_buttons_keyboard, confirm_with_button_keyboard, diff --git a/deprecated/handlers/admins/customers/users.py b/deprecated/handlers/admins/customers/users.py index 46ef3e7..8a9ebc8 100644 --- a/deprecated/handlers/admins/customers/users.py +++ b/deprecated/handlers/admins/customers/users.py @@ -5,16 +5,16 @@ CallbackQuery, Message, ) - from loader import ( dp, ) -from src.tgbot.filters.IsAdminFilter import ( - IsAdmin, -) + from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.filters.is_admin_filter import ( + IsAdmin, +) from src.tgbot.keyboards.admin.inline.customers import ( manipulation_callback, user_blocking_keyboard, diff --git a/deprecated/handlers/admins/monitoring.py b/deprecated/handlers/admins/monitoring.py index 5982bd6..2548c12 100644 --- a/deprecated/handlers/admins/monitoring.py +++ b/deprecated/handlers/admins/monitoring.py @@ -1,26 +1,26 @@ from aiogram import ( types, ) - from loader import ( _, dp, ) -from src.tgbot.filters.IsAdminFilter import ( - IsAdmin, + +from deprecated.create_forms_funcs import ( + monitoring_questionnaire, ) from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.filters.is_admin_filter import ( + IsAdmin, +) from src.tgbot.keyboards.inline.admin_inline import ( start_monitoring_keyboard, ) from src.tgbot.keyboards.inline.questionnaires_inline import ( action_keyboard_monitoring, ) -from deprecated.create_forms_funcs import ( - monitoring_questionnaire, -) @dp.message_handler(IsAdmin(), text="👀 Мониторинг") diff --git a/deprecated/handlers/admins/settings/admins.py b/deprecated/handlers/admins/settings/admins.py index b825dd6..e4d8662 100644 --- a/deprecated/handlers/admins/settings/admins.py +++ b/deprecated/handlers/admins/settings/admins.py @@ -5,20 +5,17 @@ CallbackQuery, Message, ) - from loader import ( dp, ) + from src.tgbot.config import ( change_env, load_config, ) -from src.tgbot.filters.IsAdminFilter import ( +from src.tgbot.filters.is_admin_filter import ( IsAdmin, ) -from src.tgbot.services.app.set_bot_commands import ( - set_default_commands, -) from src.tgbot.keyboards.admin.inline.reply_menu import ( admin_cancel_keyboard, settings_keyboard, @@ -26,7 +23,12 @@ from src.tgbot.keyboards.admin.inline.setting import ( add_admins_keyboard, ) -from src.tgbot.services.app.states import AdminsActions +from src.tgbot.misc.states import ( + AdminsActions, +) +from src.tgbot.services.app.set_bot_commands import ( + set_default_commands, +) @dp.callback_query_handler(IsAdmin(), text="admin:admins") diff --git a/deprecated/handlers/admins/settings/logs_user.py b/deprecated/handlers/admins/settings/logs_user.py index 86467cb..4cea0a9 100644 --- a/deprecated/handlers/admins/settings/logs_user.py +++ b/deprecated/handlers/admins/settings/logs_user.py @@ -6,11 +6,15 @@ InputFile, Message, ) - from loader import ( dp, ) -from src.tgbot.filters.IsAdminFilter import ( + +from deprecated.data_operations import ( + backup_configs, + dump_users_to_file, +) +from src.tgbot.filters.is_admin_filter import ( IsAdmin, ) from src.tgbot.handlers.users.back import ( @@ -19,10 +23,6 @@ from src.tgbot.keyboards.admin.inline.reply_menu import ( logs_keyboard, ) -from deprecated.data_operations import ( - backup_configs, - dump_users_to_file, -) @dp.message_handler(IsAdmin(), commands="logs", state="*") diff --git a/deprecated/handlers/admins/settings/setting.py b/deprecated/handlers/admins/settings/setting.py index 17f9a5c..7309d59 100644 --- a/deprecated/handlers/admins/settings/setting.py +++ b/deprecated/handlers/admins/settings/setting.py @@ -1,11 +1,11 @@ from aiogram.types import ( Message, ) - from loader import ( dp, ) -from src.tgbot.filters.IsAdminFilter import ( + +from src.tgbot.filters.is_admin_filter import ( IsAdmin, ) from src.tgbot.keyboards.admin.inline.reply_menu import ( diff --git a/deprecated/handlers/admins/settings/tech_works.py b/deprecated/handlers/admins/settings/tech_works.py index f313998..f826203 100644 --- a/deprecated/handlers/admins/settings/tech_works.py +++ b/deprecated/handlers/admins/settings/tech_works.py @@ -5,26 +5,26 @@ CallbackQuery, Message, ) - from loader import ( _, dp, ) -from src.tgbot.filters.IsAdminFilter import ( - IsAdmin, + +from deprecated.statistics import ( + get_statistics, ) from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.filters.is_admin_filter import ( + IsAdmin, +) from src.tgbot.keyboards.admin.main_menu import ( admin_keyboard, ) from src.tgbot.keyboards.inline.admin_inline import ( tech_works_keyboard, ) -from deprecated.statistics import ( - get_statistics, -) @dp.message_handler(IsAdmin(), commands="admin", state="*") diff --git a/deprecated/handlers/echo_handler.py b/deprecated/handlers/echo_handler.py index 42a8a2a..4c8f3c4 100644 --- a/deprecated/handlers/echo_handler.py +++ b/deprecated/handlers/echo_handler.py @@ -10,12 +10,12 @@ from aiogram.utils.markdown import ( hcode, ) - from loader import ( _, dp, logger, ) + from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) diff --git a/deprecated/handlers/errors/error_handler.py b/deprecated/handlers/errors/error_handler.py index 91b0c6c..3ade325 100644 --- a/deprecated/handlers/errors/error_handler.py +++ b/deprecated/handlers/errors/error_handler.py @@ -10,7 +10,6 @@ TelegramAPIError, Unauthorized, ) - from loader import ( dp, ) diff --git a/deprecated/handlers/groups/event_moderate.py b/deprecated/handlers/groups/event_moderate.py index 4cb0038..3d4df52 100644 --- a/deprecated/handlers/groups/event_moderate.py +++ b/deprecated/handlers/groups/event_moderate.py @@ -3,21 +3,21 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, bot, dp, ) + +from src.infrastructure.db_api import ( + db_commands, +) from src.tgbot.config import ( load_config, ) -from src.tgbot.filters.IsAdminFilter import ( +from src.tgbot.filters.is_admin_filter import ( IsAdmin, ) -from src.infrastructure.db_api import ( - db_commands, -) from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) diff --git a/deprecated/handlers/groups/start.py b/deprecated/handlers/groups/start.py index ac7a9ca..32acd9b 100644 --- a/deprecated/handlers/groups/start.py +++ b/deprecated/handlers/groups/start.py @@ -4,15 +4,15 @@ from aiogram.types import ( Message, ) - from loader import ( _, dp, ) -from src.tgbot.filters.FiltersChat import ( + +from src.tgbot.filters.filters_chat import ( IsGroup, ) -from src.tgbot.filters.IsAdminFilter import ( +from src.tgbot.filters.is_admin_filter import ( IsAdmin, ) diff --git a/deprecated/handlers/users/back.py b/deprecated/handlers/users/back.py index 8334067..71cac31 100644 --- a/deprecated/handlers/users/back.py +++ b/deprecated/handlers/users/back.py @@ -9,33 +9,33 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, dp, ) -from src.tgbot.handlers.users.event import ( - view_meetings_handler, - view_own_event, + +from deprecated.inline.filters_inline import ( + filters_keyboard, +) +from deprecated.message_operations import ( + delete_message, + display_profile, + information_menu, + registration_menu, ) from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.handlers.users.event import ( + view_meetings_handler, + view_own_event, +) from src.tgbot.keyboards.inline.admin_inline import ( unban_user_keyboard, ) -from deprecated.inline.filters_inline import ( - filters_keyboard, -) from src.tgbot.keyboards.inline.menu_profile_inline import ( get_profile_keyboard, ) -from deprecated.message_operations import ( - delete_message, - display_profile, - information_menu, - registration_menu, -) class Command(ABC): diff --git a/deprecated/handlers/users/brandbook.py b/deprecated/handlers/users/brandbook.py index dd899ea..8a7ee20 100644 --- a/deprecated/handlers/users/brandbook.py +++ b/deprecated/handlers/users/brandbook.py @@ -1,22 +1,22 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, dp, ) + +from deprecated.message_operations import ( + handle_guide_callback, + information_menu, + send_photo_with_caption, +) from src.tgbot.keyboards.inline.back_inline import ( only_back_keyboard, ) from src.tgbot.keyboards.inline.guide_inline import ( guide_callback, ) -from deprecated.message_operations import ( - handle_guide_callback, - information_menu, - send_photo_with_caption, -) @dp.callback_query_handler(text="information") diff --git a/deprecated/handlers/users/buy_unban.py b/deprecated/handlers/users/buy_unban.py index 71b2ab5..9c0f6ec 100644 --- a/deprecated/handlers/users/buy_unban.py +++ b/deprecated/handlers/users/buy_unban.py @@ -6,20 +6,20 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, dp, wallet, ) -from src.tgbot.config import ( - load_config, + +from src.infrastructure.Yoomoney.types import ( + PaymentSource, ) from src.infrastructure.db_api import ( db_commands, ) -from src.infrastructure.yoomoney.types import ( - PaymentSource, +from src.tgbot.config import ( + load_config, ) from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, @@ -44,7 +44,7 @@ async def get_payment_menu(call: CallbackQuery) -> None: ) -@dp.callback_query_handler(text="yoomoney") +@dp.callback_query_handler(text="Yoomoney") async def get_payment(call: CallbackQuery, state: FSMContext) -> None: payment_form = await wallet.create_payment_form( amount_rub=2, @@ -64,7 +64,7 @@ async def get_payment(call: CallbackQuery, state: FSMContext) -> None: ) -@dp.callback_query_handler(text="yoomoney:check_payment", state="payment") +@dp.callback_query_handler(text="Yoomoney:check_payment", state="payment") async def check_payment(call: CallbackQuery, state: FSMContext) -> None: data = await state.get_data() payment_is_completed: bool = await wallet.check_payment_on_successful( diff --git a/deprecated/handlers/users/change_datas.py b/deprecated/handlers/users/change_datas.py index a160527..2404598 100644 --- a/deprecated/handlers/users/change_datas.py +++ b/deprecated/handlers/users/change_datas.py @@ -18,22 +18,33 @@ from django.db import ( DataError, ) +from loader import ( + _, + dp, + logger, +) +from deprecated.determin_location import ( + Location, + RegistrationStrategy, +) +from deprecated.photo_operations import ( + saving_censored_photo, + update_normal_photo, +) from src.infrastructure.NudeNet.predictor import ( classification_image, generate_censored_image, ) +from src.infrastructure.NudeNet.profanity_filter import ( + censored_message, +) from src.infrastructure.YandexMap.exceptions import ( NothingFound, ) from src.infrastructure.db_api import ( db_commands, ) -from loader import ( - _, - dp, - logger, -) from src.tgbot.handlers.users.back import ( delete_message, ) @@ -47,18 +58,9 @@ from src.tgbot.keyboards.inline.main_menu_inline import ( start_keyboard, ) -from deprecated.determin_location import ( - Location, - RegistrationStrategy, -) -from deprecated.photo_operations import ( - saving_censored_photo, - update_normal_photo, -) -from src.tgbot.services.app.profanityFilter import ( - censored_message, +from src.tgbot.misc.states import ( + NewData, ) -from src.tgbot.services.app.states import NewData @dp.callback_query_handler(text="change_profile") diff --git a/deprecated/handlers/users/change_event_datas.py b/deprecated/handlers/users/change_event_datas.py index 4101608..6413415 100644 --- a/deprecated/handlers/users/change_event_datas.py +++ b/deprecated/handlers/users/change_event_datas.py @@ -7,17 +7,17 @@ CallbackQuery, Message, ) - from loader import ( _, dp, ) -from src.tgbot.handlers.users.back import ( - delete_message, -) + from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.handlers.users.back import ( + delete_message, +) from src.tgbot.keyboards.inline.poster_inline import ( change_datas_keyboard, ) diff --git a/deprecated/handlers/users/event.py b/deprecated/handlers/users/event.py index 9ab8c51..5c62ecd 100644 --- a/deprecated/handlers/users/event.py +++ b/deprecated/handlers/users/event.py @@ -19,15 +19,22 @@ from django.db import ( DataError, ) - from loader import ( _, bot, dp, logger, ) -from src.tgbot.config import ( - load_config, + +from deprecated.determin_location import ( + EventStrategy, + Location, +) +from deprecated.extra_features import ( + check_event_date, +) +from deprecated.templates_messages import ( + ME, ) from src.infrastructure.YandexMap.exceptions import ( NothingFound, @@ -35,6 +42,9 @@ from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.config import ( + load_config, +) from src.tgbot.keyboards.inline.calendar import ( SimpleCalendar, calendar_callback, @@ -44,16 +54,6 @@ cancel_registration_keyboard, poster_keyboard, ) -from deprecated.determin_location import ( - EventStrategy, - Location, -) -from deprecated.extra_features import ( - check_event_date, -) -from deprecated.templates_messages import ( - ME, -) @dp.callback_query_handler(text="meetings") diff --git a/deprecated/handlers/users/event_list.py b/deprecated/handlers/users/event_list.py index 505f103..510aaa6 100644 --- a/deprecated/handlers/users/event_list.py +++ b/deprecated/handlers/users/event_list.py @@ -6,18 +6,18 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, dp, ) -from src.infrastructure.db_api import ( - db_commands, -) + from deprecated.extra_features import ( create_form, get_next_registration, ) +from src.infrastructure.db_api import ( + db_commands, +) @dp.callback_query_handler(text="my_appointment") diff --git a/deprecated/handlers/users/filters.py b/deprecated/handlers/users/filters.py index 2ed45cc..86ca136 100644 --- a/deprecated/handlers/users/filters.py +++ b/deprecated/handlers/users/filters.py @@ -13,13 +13,22 @@ from aiogram.utils.exceptions import ( BadRequest, ) - from loader import ( _, dp, ) -from src.tgbot.handlers.users.back import ( - delete_message, + +from deprecated.determin_location import ( + FiltersStrategy, + Location, +) +from deprecated.inline.filters_inline import ( + event_filters_keyboard, + filters_keyboard, +) +from deprecated.message_operations import ( + choice_gender, + show_dating_filters, ) from src.infrastructure.YandexMap.exceptions import ( NothingFound, @@ -27,21 +36,12 @@ from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.handlers.users.back import ( + delete_message, +) from src.tgbot.keyboards.inline.change_data_profile_inline import ( gender_keyboard, ) -from deprecated.inline.filters_inline import ( - event_filters_keyboard, - filters_keyboard, -) -from deprecated.determin_location import ( - FiltersStrategy, - Location, -) -from deprecated.message_operations import ( - choice_gender, - show_dating_filters, -) @dp.callback_query_handler(text="filters") diff --git a/deprecated/handlers/users/registration.py b/deprecated/handlers/users/registration.py index c1c56a4..1f46b6e 100644 --- a/deprecated/handlers/users/registration.py +++ b/deprecated/handlers/users/registration.py @@ -22,17 +22,31 @@ from django.db import ( DataError, ) - from loader import ( _, client, dp, logger, ) + +from deprecated.determin_location import ( + Location, + RegistrationStrategy, +) +from deprecated.message_operations import ( + choice_gender, +) +from deprecated.photo_operations import ( + saving_censored_photo, + saving_normal_photo, +) from src.infrastructure.NudeNet.predictor import ( classification_image, generate_censored_image, ) +from src.infrastructure.NudeNet.profanity_filter import ( + censored_message, +) from src.infrastructure.YandexMap.exceptions import ( NothingFound, ) @@ -54,22 +68,9 @@ from src.tgbot.keyboards.inline.registration_inline import ( second_registration_keyboard, ) - -from src.tgbot.services.app.profanityFilter import ( - censored_message, -) -from deprecated.determin_location import ( - Location, - RegistrationStrategy, -) -from deprecated.message_operations import ( - choice_gender, -) -from deprecated.photo_operations import ( - saving_censored_photo, - saving_normal_photo, +from src.tgbot.misc.states import ( + RegData, ) -from src.tgbot.services.app.states import RegData @dp.callback_query_handler(text="registration") diff --git a/deprecated/handlers/users/start.py b/deprecated/handlers/users/start.py index 5f4fba6..e7a6452 100644 --- a/deprecated/handlers/users/start.py +++ b/deprecated/handlers/users/start.py @@ -11,25 +11,25 @@ from aiogram.utils.exceptions import ( BadRequest, ) - from loader import ( _, dp, ) -from src.tgbot.filters import ( - IsPrivate, + +from deprecated.message_operations import ( + check_user_in_db, + delete_message, + registration_menu, ) from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.filters import ( + IsPrivate, +) from src.tgbot.keyboards.inline.language_inline import ( language_keyboard, ) -from deprecated.message_operations import ( - check_user_in_db, - delete_message, - registration_menu, -) @dp.message_handler(IsPrivate(), CommandStart()) diff --git a/deprecated/handlers/users/support.py b/deprecated/handlers/users/support.py index 74a1ad1..8269b0f 100644 --- a/deprecated/handlers/users/support.py +++ b/deprecated/handlers/users/support.py @@ -7,12 +7,12 @@ from aiogram.utils.exceptions import ( BadRequest, ) - from loader import ( _, bot, dp, ) + from src.tgbot.handlers.users.back import ( delete_message, ) diff --git a/deprecated/handlers/users/user_profile.py b/deprecated/handlers/users/user_profile.py index 0a82dfa..2cdb9f2 100644 --- a/deprecated/handlers/users/user_profile.py +++ b/deprecated/handlers/users/user_profile.py @@ -1,23 +1,23 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, dp, ) -from src.tgbot.handlers.users.back import ( - delete_message, + +from deprecated.message_operations import ( + display_profile, ) from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.handlers.users.back import ( + delete_message, +) from src.tgbot.keyboards.inline.menu_profile_inline import ( get_profile_keyboard, ) -from deprecated.message_operations import ( - display_profile, -) @dp.callback_query_handler(text="my_profile") diff --git a/deprecated/handlers/users/verification.py b/deprecated/handlers/users/verification.py index f9ec80a..8cd68d3 100644 --- a/deprecated/handlers/users/verification.py +++ b/deprecated/handlers/users/verification.py @@ -7,17 +7,17 @@ CallbackQuery, ReplyKeyboardRemove, ) - from loader import ( _, dp, ) -from src.tgbot.handlers.users.back import ( - delete_message, -) + from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.handlers.users.back import ( + delete_message, +) from src.tgbot.keyboards.default.get_contact_default import ( contact_keyboard, ) diff --git a/deprecated/handlers/users/view_event.py b/deprecated/handlers/users/view_event.py index 371a1d2..a6d9719 100644 --- a/deprecated/handlers/users/view_event.py +++ b/deprecated/handlers/users/view_event.py @@ -4,21 +4,21 @@ from aiogram.types import ( CallbackQuery, ) - from loader import ( _, bot, dp, ) -from src.tgbot.keyboards.inline.poster_inline import ( - poster_keyboard, -) + from deprecated.extra_features import ( add_events_to_user, check_event_date, create_form, get_next_random_event_id, ) +from src.tgbot.keyboards.inline.poster_inline import ( + poster_keyboard, +) @dp.callback_query_handler(text="view_poster") diff --git a/deprecated/handlers/users/view_ques.py b/deprecated/handlers/users/view_ques.py index f6233f8..5718036 100644 --- a/deprecated/handlers/users/view_ques.py +++ b/deprecated/handlers/users/view_ques.py @@ -7,27 +7,30 @@ from django.db import ( IntegrityError, ) - from loader import ( _, bot, dp, logger, ) -from src.tgbot.config import ( - load_config, + +from deprecated.get_next_user_func import ( + get_next_user, +) +from deprecated.message_operations import ( + delete_message, ) from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.config import ( + load_config, +) from src.tgbot.keyboards.inline.questionnaires_inline import ( action_keyboard, action_reciprocity_keyboard, action_report_keyboard, ) -from deprecated.message_operations import ( - delete_message, -) from src.tgbot.services.dating import ( ChooseReportReason, DislikeAction, @@ -41,9 +44,6 @@ StartFindingSuccess, StoppedAction, ) -from deprecated.get_next_user_func import ( - get_next_user, -) @dp.callback_query_handler(text="find_ques") diff --git a/deprecated/inline/admin_inline.py b/deprecated/inline/admin_inline.py index 4385d54..d37fdde 100644 --- a/deprecated/inline/admin_inline.py +++ b/deprecated/inline/admin_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/back_inline.py b/deprecated/inline/back_inline.py index 1441c4e..f8410fb 100644 --- a/deprecated/inline/back_inline.py +++ b/deprecated/inline/back_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/cancel_inline.py b/deprecated/inline/cancel_inline.py index 98f4d83..9ab0001 100644 --- a/deprecated/inline/cancel_inline.py +++ b/deprecated/inline/cancel_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/change_data_profile_inline.py b/deprecated/inline/change_data_profile_inline.py index edaf9af..7c4d637 100644 --- a/deprecated/inline/change_data_profile_inline.py +++ b/deprecated/inline/change_data_profile_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/filters_inline.py b/deprecated/inline/filters_inline.py index fb23064..4769e3f 100644 --- a/deprecated/inline/filters_inline.py +++ b/deprecated/inline/filters_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/guide_inline.py b/deprecated/inline/guide_inline.py index 9c5c167..8b84e34 100644 --- a/deprecated/inline/guide_inline.py +++ b/deprecated/inline/guide_inline.py @@ -5,7 +5,6 @@ from aiogram.utils.callback_data import ( CallbackData, ) - from loader import ( _, ) diff --git a/deprecated/inline/language_inline.py b/deprecated/inline/language_inline.py index ceb6b77..35450d2 100644 --- a/deprecated/inline/language_inline.py +++ b/deprecated/inline/language_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/main_menu_inline.py b/deprecated/inline/main_menu_inline.py index dad63d3..5429669 100644 --- a/deprecated/inline/main_menu_inline.py +++ b/deprecated/inline/main_menu_inline.py @@ -4,16 +4,16 @@ InlineKeyboardMarkup, Message, ) - from loader import ( _, ) -from src.tgbot.config import ( - load_config, -) + from src.infrastructure.db_api import ( db_commands, ) +from src.tgbot.config import ( + load_config, +) async def start_keyboard( diff --git a/deprecated/inline/menu_profile_inline.py b/deprecated/inline/menu_profile_inline.py index f4bdf2e..f07067c 100644 --- a/deprecated/inline/menu_profile_inline.py +++ b/deprecated/inline/menu_profile_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/payments_inline.py b/deprecated/inline/payments_inline.py index f8fd51e..3a6b74d 100644 --- a/deprecated/inline/payments_inline.py +++ b/deprecated/inline/payments_inline.py @@ -2,18 +2,17 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) -from yarl import ( - URL, -) - from loader import ( _, ) +from yarl import ( + URL, +) async def payment_menu_keyboard() -> InlineKeyboardMarkup: markup = InlineKeyboardMarkup() - yoomoney = InlineKeyboardButton(text=_("💳 ЮMoney"), callback_data="yoomoney") + yoomoney = InlineKeyboardButton(text=_("💳 ЮMoney"), callback_data="Yoomoney") markup.add(yoomoney) return markup @@ -22,7 +21,7 @@ async def yoomoney_keyboard(url: str | URL = None) -> InlineKeyboardMarkup: markup = InlineKeyboardMarkup(row_width=1) pay_yoomoney = InlineKeyboardButton(text=_("💳 Оплатить"), url=url) check_prices = InlineKeyboardButton( - text=_("🔄 Проверить оплату"), callback_data="yoomoney:check_payment" + text=_("🔄 Проверить оплату"), callback_data="Yoomoney:check_payment" ) markup.add(pay_yoomoney, check_prices) return markup diff --git a/deprecated/inline/poster_inline.py b/deprecated/inline/poster_inline.py index 63bcc37..56954a5 100644 --- a/deprecated/inline/poster_inline.py +++ b/deprecated/inline/poster_inline.py @@ -1,17 +1,13 @@ -from typing import ( - Union, -) - from aiogram.types import ( CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, ) - from loader import ( _, ) + from src.infrastructure.db_api import ( db_commands, ) diff --git a/deprecated/inline/questionnaires_inline.py b/deprecated/inline/questionnaires_inline.py index e224c3b..3358d59 100644 --- a/deprecated/inline/questionnaires_inline.py +++ b/deprecated/inline/questionnaires_inline.py @@ -5,7 +5,6 @@ from aiogram.utils.callback_data import ( CallbackData, ) - from loader import ( _, ) diff --git a/deprecated/inline/registration_inline.py b/deprecated/inline/registration_inline.py index a7c3a2a..32a6d53 100644 --- a/deprecated/inline/registration_inline.py +++ b/deprecated/inline/registration_inline.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/settings_menu.py b/deprecated/inline/settings_menu.py index ad4fb86..6c5cba5 100644 --- a/deprecated/inline/settings_menu.py +++ b/deprecated/inline/settings_menu.py @@ -2,7 +2,6 @@ InlineKeyboardButton, InlineKeyboardMarkup, ) - from loader import ( _, ) diff --git a/deprecated/inline/support_inline.py b/deprecated/inline/support_inline.py index 98ad609..ba97abe 100644 --- a/deprecated/inline/support_inline.py +++ b/deprecated/inline/support_inline.py @@ -1,8 +1,4 @@ import random -from typing import ( - Optional, - Union, -) from aiogram.types import ( InlineKeyboardButton, @@ -11,11 +7,11 @@ from aiogram.utils.callback_data import ( CallbackData, ) - from loader import ( _, dp, ) + from src.tgbot.config import ( load_config, ) diff --git a/deprecated/language_ware.py b/deprecated/language_ware.py index f8d93ee..7156380 100644 --- a/deprecated/language_ware.py +++ b/deprecated/language_ware.py @@ -5,7 +5,9 @@ from aiogram import ( types, ) -from aiogram.utils.i18n import I18nMiddleware +from aiogram.utils.i18n import ( + I18nMiddleware, +) from src.tgbot.config import ( LOCALES_DIR, From 4c09128c6cc27d99c08814c234bc2b5d5f2f1779 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:22:47 +0300 Subject: [PATCH 037/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20yoomoney?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/yoomoney/__init__.py | 6 -- src/infrastructure/yoomoney/authorization.py | 36 -------- src/infrastructure/yoomoney/exceptions.py | 6 -- src/infrastructure/yoomoney/get_token.py | 27 ------ src/infrastructure/yoomoney/request.py | 54 ------------ src/infrastructure/yoomoney/types/__init__.py | 13 --- .../yoomoney/types/account_info.py | 34 -------- .../yoomoney/types/operation_details.py | 38 -------- .../yoomoney/types/operation_history.py | 29 ------- src/infrastructure/yoomoney/types/payment.py | 15 ---- src/infrastructure/yoomoney/wallet.py | 87 ------------------- 11 files changed, 345 deletions(-) delete mode 100644 src/infrastructure/yoomoney/__init__.py delete mode 100644 src/infrastructure/yoomoney/authorization.py delete mode 100644 src/infrastructure/yoomoney/exceptions.py delete mode 100644 src/infrastructure/yoomoney/get_token.py delete mode 100644 src/infrastructure/yoomoney/request.py delete mode 100644 src/infrastructure/yoomoney/types/__init__.py delete mode 100644 src/infrastructure/yoomoney/types/account_info.py delete mode 100644 src/infrastructure/yoomoney/types/operation_details.py delete mode 100644 src/infrastructure/yoomoney/types/operation_history.py delete mode 100644 src/infrastructure/yoomoney/types/payment.py delete mode 100644 src/infrastructure/yoomoney/wallet.py diff --git a/src/infrastructure/yoomoney/__init__.py b/src/infrastructure/yoomoney/__init__.py deleted file mode 100644 index 7ebc8e3..0000000 --- a/src/infrastructure/yoomoney/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .authorization import ( - authorize_app, -) -from .wallet import ( - YooMoneyWallet, -) diff --git a/src/infrastructure/yoomoney/authorization.py b/src/infrastructure/yoomoney/authorization.py deleted file mode 100644 index 0582e15..0000000 --- a/src/infrastructure/yoomoney/authorization.py +++ /dev/null @@ -1,36 +0,0 @@ -from src.infrastructure.yoomoney.request import ( - send_request, -) - -AUTH_APP_URL = ( - "https://yoomoney.ru/oauth/authorize?client_id={client_id}&response_type=code" - "&redirect_uri={redirect_uri}&scope={permissions}" -) - -GET_TOKEN_URL = ( - "https://yoomoney.ru/oauth/token?code={code}&client_id={client_id}&" - "grant_type=authorization_code&redirect_uri={redirect_uri}" -) - - -async def authorize_app(client_id, redirect_uri, app_permissions: list[str, ...]): - formatted_auth_app_url = AUTH_APP_URL.format( - client_id=client_id, - redirect_uri=redirect_uri, - permissions="%20".join(app_permissions), - ) - response = await send_request(formatted_auth_app_url, response_without_data=True) - - print(f"Перейдите по URL и подтвердите доступ для приложения\n{response.url}") - code = input("Введите код в консоль > ").strip() - - get_token_url = GET_TOKEN_URL.format( - code=code, client_id=client_id, redirect_uri=redirect_uri - ) - _, data = await send_request(get_token_url) - - access_token = data.get("access_token") - if not access_token: - return print(f"Не удалось получить токен. {data.get('error', '')}") - - return print(f"Ваш токен — {access_token}. Сохраните его в безопасном месте!") diff --git a/src/infrastructure/yoomoney/exceptions.py b/src/infrastructure/yoomoney/exceptions.py deleted file mode 100644 index cce856a..0000000 --- a/src/infrastructure/yoomoney/exceptions.py +++ /dev/null @@ -1,6 +0,0 @@ -class UnresolvedRequestMethod(Exception): - pass - - -class BadResponse(Exception): - pass diff --git a/src/infrastructure/yoomoney/get_token.py b/src/infrastructure/yoomoney/get_token.py deleted file mode 100644 index 6b73ff0..0000000 --- a/src/infrastructure/yoomoney/get_token.py +++ /dev/null @@ -1,27 +0,0 @@ -import asyncio - -from src.tgbot.config import ( - load_config, -) -from src.tgbot.utils.yoomoney import ( - authorize_app, -) - - -async def main(): - await authorize_app( - client_id=load_config().misc.client_id, - redirect_uri=load_config().misc.redirect_url, - app_permissions=[ - "account-info", - "operation-history", - "operation-details", - "incoming-transfers", - "payment-p2p", - "payment-shop", - ], - ) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/infrastructure/yoomoney/request.py b/src/infrastructure/yoomoney/request.py deleted file mode 100644 index d727078..0000000 --- a/src/infrastructure/yoomoney/request.py +++ /dev/null @@ -1,54 +0,0 @@ -from aiohttp import ( - ClientResponse, - ClientSession, -) -from aiohttp.client_exceptions import ( - ContentTypeError, -) - -from src.infrastructure.yoomoney.exceptions import ( - BadResponse, - UnresolvedRequestMethod, -) - -ALLOWED_METHODS = ("post", "get") - - -async def send_request( - url: str, method: str = "post", response_without_data: bool = False, **kwargs -) -> (ClientResponse, dict | None): - headers = {"Content-Type": "application/x-www-form-urlencoded"} - if add_headers := kwargs.pop("headers", {}): - headers |= add_headers - - method = method.lower().strip() - await check_method(method) - - async with ClientSession() as session: - async with getattr(session, method)(url, headers=headers, **kwargs) as response: - await post_handle_response(response) - - if response_without_data: - return response - - return response, await response.json() - - -async def check_method(method: str): - if method not in ALLOWED_METHODS: - raise UnresolvedRequestMethod - - -async def post_handle_response(response: ClientResponse): - try: - response_data = await response.json() - if isinstance(response_data, dict) and response_data.get("error"): - raise BadResponse( - f"error — {response_data.get('error')}, response is {response}" - ) - - except ContentTypeError: - pass - - if response.status >= 400: - raise BadResponse(response) diff --git a/src/infrastructure/yoomoney/types/__init__.py b/src/infrastructure/yoomoney/types/__init__.py deleted file mode 100644 index a4a57fe..0000000 --- a/src/infrastructure/yoomoney/types/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .account_info import ( - AccountInfo, -) -from .operation_details import ( - OperationDetails, -) -from .operation_history import ( - Operation, -) -from .payment import ( - PaymentForm, - PaymentSource, -) diff --git a/src/infrastructure/yoomoney/types/account_info.py b/src/infrastructure/yoomoney/types/account_info.py deleted file mode 100644 index 1dec5a3..0000000 --- a/src/infrastructure/yoomoney/types/account_info.py +++ /dev/null @@ -1,34 +0,0 @@ -from pydantic import ( - BaseModel, - Field, -) - - -class BalanceDetails(BaseModel): - total: int - available: int - deposition_pending: int | None - blocked: int | None - debt: int | None - hold: int | None - - -class LinkedCard(BaseModel): - pan_fragment: str - card_type: str = Field(None, alias="type") - - -class AccountInfo(BaseModel): - """ - Получение информации о состоянии счета пользователя. - - https://yoomoney.ru/docs/wallet/user-account/account-info - """ - - account: str # номер счета - balance: int # баланс счета - currency: str # код валюты счета - account_status: str - account_type: str - balance_details: BalanceDetails | None - cards_linked: list[LinkedCard, ...] | None diff --git a/src/infrastructure/yoomoney/types/operation_details.py b/src/infrastructure/yoomoney/types/operation_details.py deleted file mode 100644 index cab8efb..0000000 --- a/src/infrastructure/yoomoney/types/operation_details.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import ( - datetime, -) -from typing import ( - Literal, -) - -from pydantic import ( - BaseModel, - Field, -) - - -class OperationDetails(BaseModel): - """ - Детальная информация об операции из истории. - - https://yoomoney.ru/docs/wallet/user-account/operation-details - """ - - error: str | None - operation_id: str - status: str - pattern_id: str | None - direction: Literal["in"] | Literal["out"] - amount: int - amount_due: int | None - fee: int | None - operation_datetime: datetime = Field(alias="datetime") - title: str - sender: int | None - recipient: str | None - recipient_type: str | None - message: str | None - comment: str | None - label: str | None - details: str | None - operation_type: str = Field(alias="type") diff --git a/src/infrastructure/yoomoney/types/operation_history.py b/src/infrastructure/yoomoney/types/operation_history.py deleted file mode 100644 index 6155bf7..0000000 --- a/src/infrastructure/yoomoney/types/operation_history.py +++ /dev/null @@ -1,29 +0,0 @@ -from datetime import ( - datetime, -) -from typing import ( - Literal, -) - -from pydantic import ( - BaseModel, - Field, -) - - -class Operation(BaseModel): - """ - Описание платежной операции. - - https://yoomoney.ru/docs/wallet/user-account/operation-history#response-operation - """ - - operation_id: str - status: str - execution_datetime: datetime = Field(alias="datetime") - title: str - pattern_id: str | None - direction: Literal["in"] | Literal["out"] - amount: int - label: str | None - operation_type: str = Field(alias="type") diff --git a/src/infrastructure/yoomoney/types/payment.py b/src/infrastructure/yoomoney/types/payment.py deleted file mode 100644 index 3639fce..0000000 --- a/src/infrastructure/yoomoney/types/payment.py +++ /dev/null @@ -1,15 +0,0 @@ -from dataclasses import ( - dataclass, -) - - -@dataclass(frozen=True, slots=True) -class PaymentSource: - BANK_CARD = "AC" - YOOMONEY_WALLET = "PC" - - -@dataclass(frozen=True, slots=True) -class PaymentForm: - link_for_customer: str - payment_label: str diff --git a/src/infrastructure/yoomoney/wallet.py b/src/infrastructure/yoomoney/wallet.py deleted file mode 100644 index fbe28b7..0000000 --- a/src/infrastructure/yoomoney/wallet.py +++ /dev/null @@ -1,87 +0,0 @@ -from src.infrastructure.yoomoney.request import ( - send_request, -) -from src.infrastructure.yoomoney.types import ( - AccountInfo, - Operation, - OperationDetails, - PaymentForm, - PaymentSource, -) - - -class YooMoneyWallet: - def __init__(self, access_token: str): - self.host = "https://yoomoney.ru" - self.__headers = dict(Authorization=f"Bearer {access_token}") - - @property - async def account_info(self) -> AccountInfo: - url = self.host + "/api/account-info" - response, data = await send_request(url, headers=self.__headers) - return AccountInfo.parse_obj(data) - - async def get_operation_details(self, operation_id: str) -> OperationDetails: - url = self.host + "/api/operation-details" - response, data = await send_request( - url, headers=self.__headers, data={"operation_id": operation_id} - ) - return OperationDetails.parse_obj(data) - - async def get_operation_history( - self, label: str | None = None - ) -> list[Operation, ...]: - """ - Получение последних 30 операций. - - На 10.03.2023 API yoomoney напросто игнорирует указанные в документации параметры - - https://yoomoney.ru/docs/payment-buttons/using-api/forms?lang=ru#parameters - """ - history_url = self.host + "/api/operation-history" - response, data = await send_request(history_url, headers=self.__headers) - if operations := data.get("operations"): - parsed = [Operation.parse_obj(operation) for operation in operations] - if label: - parsed = [operation for operation in parsed if operation.label == label] - return parsed - - async def create_payment_form( - self, - amount_rub: int, - unique_label: str, - success_redirect_url: str | None = None, - payment_source: PaymentSource = PaymentSource.BANK_CARD, - ) -> PaymentForm: - account_info = await self.account_info - quickpay_url = "https://yoomoney.ru/quickpay/confirm.xml?" - params = { - "receiver": account_info.account, - "quickpay-form": "button", - "paymentType": payment_source, - "sum": amount_rub, - "successURL": success_redirect_url, - "label": unique_label, - } - params = {k: v for k, v in params.items() if v} - response = await send_request( - quickpay_url, response_without_data=True, params=params - ) - - return PaymentForm( - link_for_customer=str(response.url), payment_label=unique_label - ) - - async def check_payment_on_successful(self, label: str) -> bool: - need_operations = await self.get_operation_history(label=label) - return bool(need_operations) and need_operations.pop().status == "success" - - async def revoke_token(self) -> None: - url = self.host + "/api/revoke" - response = await send_request( - url=url, response_without_data=True, headers=self.__headers - ) - print( - f"Запрос на отзыв токена завершен с кодом {response.status} " - "https://yoomoney.ru/docs/wallet/using-api/authorization/revoke-access-token#response" - ) From 6a6421ce3da7100c15b3e407dc60bfe9b2d680ec Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:24:18 +0300 Subject: [PATCH 038/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__init__.py | 4 +++- src/infrastructure/NudeNet/__init__.py | 9 +++++++++ src/infrastructure/NudeNet/predictor.py | 11 +++++------ .../NudeNet/profanity_filter.py} | 0 src/infrastructure/YandexMap/__init__.py | 17 +++++++++++++++++ src/infrastructure/YandexMap/api.py | 8 ++++---- 6 files changed, 38 insertions(+), 11 deletions(-) rename src/{tgbot/services/app/profanityFilter.py => infrastructure/NudeNet/profanity_filter.py} (100%) diff --git a/src/__init__.py b/src/__init__.py index 5086911..8c7f012 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,4 +1,6 @@ -from . import tgbot +from . import ( + tgbot, +) __all__ = ( "tgbot", diff --git a/src/infrastructure/NudeNet/__init__.py b/src/infrastructure/NudeNet/__init__.py index e69de29..9c46a5f 100644 --- a/src/infrastructure/NudeNet/__init__.py +++ b/src/infrastructure/NudeNet/__init__.py @@ -0,0 +1,9 @@ +from .predictor import ( + classification_image, + generate_censored_image, +) + +__all__ = ( + "classification_image", + "generate_censored_image" +) diff --git a/src/infrastructure/NudeNet/predictor.py b/src/infrastructure/NudeNet/predictor.py index 0a8389f..1ef7731 100644 --- a/src/infrastructure/NudeNet/predictor.py +++ b/src/infrastructure/NudeNet/predictor.py @@ -1,15 +1,14 @@ import pathlib -from typing import ( - Union, -) -from loader import ( - detector, +from nudenet import ( + NudeDetector, ) +detector = NudeDetector() + async def classification_image(image_path: str | pathlib.Path) -> list[dict]: - return detector.detect(image_path) + return detector.detect(image_path=image_path) async def generate_censored_image( diff --git a/src/tgbot/services/app/profanityFilter.py b/src/infrastructure/NudeNet/profanity_filter.py similarity index 100% rename from src/tgbot/services/app/profanityFilter.py rename to src/infrastructure/NudeNet/profanity_filter.py diff --git a/src/infrastructure/YandexMap/__init__.py b/src/infrastructure/YandexMap/__init__.py index e69de29..18fadac 100644 --- a/src/infrastructure/YandexMap/__init__.py +++ b/src/infrastructure/YandexMap/__init__.py @@ -0,0 +1,17 @@ +from .api import ( + YaClient, +) +from .exceptions import ( + InvalidKey, + NothingFound, + UnexpectedResponse, + YandexGeocoderException, +) + +__all__ = ( + "YaClient", + "InvalidKey", + "NothingFound", + "YandexGeocoderException", + "UnexpectedResponse", +) diff --git a/src/infrastructure/YandexMap/api.py b/src/infrastructure/YandexMap/api.py index 4d597cb..a9c9d0f 100644 --- a/src/infrastructure/YandexMap/api.py +++ b/src/infrastructure/YandexMap/api.py @@ -5,14 +5,14 @@ import aiohttp -from src.infrastructure.YandexMap.exceptions import ( +from .exceptions import ( InvalidKey, NothingFound, UnexpectedResponse, ) -class Client: +class YaClient: __slots__ = ("api_key",) api_key: str @@ -22,8 +22,8 @@ def __init__(self, api_key: str): async def _request(self, address: str) -> Any: async with aiohttp.ClientSession() as session: async with session.get( - url="https://geocode-maps.yandex.ru/1.x/", - params=dict(format="json", apikey=self.api_key, geocode=address), + url="https://geocode-maps.yandex.ru/1.x/", + params=dict(format="json", apikey=self.api_key, geocode=address), ) as response: if response.status == 200: a = await response.json() From f2968c2ee40289ceaff2a2b9662409bf767f319c Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:24:50 +0300 Subject: [PATCH 039/148] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20update=20Handle?= =?UTF-8?q?r=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tgbot/types.py b/src/tgbot/types.py index 75f4756..1af46fc 100644 --- a/src/tgbot/types.py +++ b/src/tgbot/types.py @@ -5,7 +5,7 @@ ) from aiogram.types import ( - Message, + TelegramObject, ) -Handler = Callable[[Message, dict[str, Any]], Awaitable[Any]] +Handler = Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]] From 2028195e683b457518551e237f82eb2048bb9385 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:26:10 +0300 Subject: [PATCH 040/148] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20add=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{FiltersChat.py => filters_chat.py} | 6 ++++-- .../app/AsyncObj.py => misc/async_obj.py} | 8 ++++---- src/tgbot/services/app/broadcaster.py | 18 ++++++++++++------ 3 files changed, 20 insertions(+), 12 deletions(-) rename src/tgbot/filters/{FiltersChat.py => filters_chat.py} (76%) rename src/tgbot/{services/app/AsyncObj.py => misc/async_obj.py} (84%) diff --git a/src/tgbot/filters/FiltersChat.py b/src/tgbot/filters/filters_chat.py similarity index 76% rename from src/tgbot/filters/FiltersChat.py rename to src/tgbot/filters/filters_chat.py index c940bcc..1f4b893 100644 --- a/src/tgbot/filters/FiltersChat.py +++ b/src/tgbot/filters/filters_chat.py @@ -1,11 +1,13 @@ -from aiogram.filters import BaseFilter +from aiogram.filters import ( + BaseFilter, +) from aiogram.types import ( Message, ) class ChatTypeFilter(BaseFilter): - def __init__(self, chat_type: str | list): + def __init__(self, chat_type: str | list) -> None: self.chat_type = chat_type async def __call__(self, message: Message) -> bool: diff --git a/src/tgbot/services/app/AsyncObj.py b/src/tgbot/misc/async_obj.py similarity index 84% rename from src/tgbot/services/app/AsyncObj.py rename to src/tgbot/misc/async_obj.py index 32f7654..8b32eab 100644 --- a/src/tgbot/services/app/AsyncObj.py +++ b/src/tgbot/misc/async_obj.py @@ -1,11 +1,11 @@ import asyncio from typing import ( - NoReturn, + NoReturn, Coroutine, Any, Generator, ) class AsyncObj: - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: """ Init. @@ -26,10 +26,10 @@ async def __initobj(self) -> "AsyncObj": await self.__ainit__(*self.__stored_args[0], **self.__stored_args[1]) return self - def __await__(self): + def __await__(self) -> Generator[Any, None, "AsyncObj"]: return self.__initobj().__await__() - def __init_subclass__(cls, **kwargs) -> NoReturn: + def __init_subclass__(cls, **kwargs) -> None: # __ainit__ must be async assert asyncio.iscoroutinefunction(cls.__ainit__) diff --git a/src/tgbot/services/app/broadcaster.py b/src/tgbot/services/app/broadcaster.py index ce9dd64..db25a99 100644 --- a/src/tgbot/services/app/broadcaster.py +++ b/src/tgbot/services/app/broadcaster.py @@ -1,15 +1,21 @@ import asyncio import logging -from typing import Union +from typing import ( + Union, +) -from aiogram import Bot -from aiogram import exceptions -from aiogram.types import InlineKeyboardMarkup +from aiogram import ( + Bot, + exceptions, +) +from aiogram.types import ( + InlineKeyboardMarkup, +) async def send_message( bot: Bot, - user_id: Union[int, str], + user_id: int | str, text: str, disable_notification: bool = False, reply_markup: InlineKeyboardMarkup = None, @@ -32,7 +38,7 @@ async def send_message( reply_markup=reply_markup, ) except exceptions.TelegramBadRequest as e: - logging.error("Telegram server says - Bad Request: chat not found") + logging.error("Telegram server says - Bad Request: chat not found {e}".format(e=e)) except exceptions.TelegramForbiddenError: logging.error(f"Target [ID:{user_id}]: got TelegramForbiddenError") except exceptions.TelegramRetryAfter as e: From 70c4a8e70e34f9e7ca6c49a1c6433f59e885eb10 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:28:46 +0300 Subject: [PATCH 041/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/filters/__init__.py | 21 ++++--------------- .../{IsAdminFilter.py => is_admin_filter.py} | 4 +++- src/tgbot/handlers/__init__.py | 1 - 3 files changed, 7 insertions(+), 19 deletions(-) rename src/tgbot/filters/{IsAdminFilter.py => is_admin_filter.py} (83%) diff --git a/src/tgbot/filters/__init__.py b/src/tgbot/filters/__init__.py index c53cc1c..128f965 100644 --- a/src/tgbot/filters/__init__.py +++ b/src/tgbot/filters/__init__.py @@ -1,23 +1,10 @@ -from src.tgbot.filters.FiltersChat import ( - IsPrivate, - IsGroup, +from .filters_chat import ( + ChatTypeFilter, ) - -from src.tgbot.filters.IsAdminFilter import ( - IsAdmin +from .is_admin_filter import ( + IsAdmin, ) __all__ = ( - "IsGroup", - "IsPrivate", "IsAdmin", ) - -# def setup(dp: Dispatcher): -# logger.info("Подключение filters...") -# text_messages = [ -# dp.message_handlers, -# dp.edited_message_handlers, -# ] -# logger.info(text_messages) -# dp.filters_factory.bind(IsPrivate) diff --git a/src/tgbot/filters/IsAdminFilter.py b/src/tgbot/filters/is_admin_filter.py similarity index 83% rename from src/tgbot/filters/IsAdminFilter.py rename to src/tgbot/filters/is_admin_filter.py index 4e96735..126803c 100644 --- a/src/tgbot/filters/IsAdminFilter.py +++ b/src/tgbot/filters/is_admin_filter.py @@ -1,7 +1,9 @@ from aiogram import ( types, ) -from aiogram.filters import BaseFilter +from aiogram.filters import ( + BaseFilter, +) from src.tgbot.config import ( load_config, diff --git a/src/tgbot/handlers/__init__.py b/src/tgbot/handlers/__init__.py index f12779d..c3cddfd 100644 --- a/src/tgbot/handlers/__init__.py +++ b/src/tgbot/handlers/__init__.py @@ -4,7 +4,6 @@ routers_list = [ start_router, - # echo_router, # echo_router must be last ] From 4373c9c7bdf479b8394c0f57823c8561af7cca5f Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:29:41 +0300 Subject: [PATCH 042/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/BanCheck.py | 65 -------------------- src/tgbot/middlewares/IsMaintenanceCheck.py | 51 ---------------- src/tgbot/middlewares/LinkCheck.py | 68 --------------------- src/tgbot/middlewares/Log.py | 18 ------ src/tgbot/middlewares/Throttling.py | 66 -------------------- 5 files changed, 268 deletions(-) delete mode 100644 src/tgbot/middlewares/BanCheck.py delete mode 100644 src/tgbot/middlewares/IsMaintenanceCheck.py delete mode 100644 src/tgbot/middlewares/LinkCheck.py delete mode 100644 src/tgbot/middlewares/Log.py delete mode 100644 src/tgbot/middlewares/Throttling.py diff --git a/src/tgbot/middlewares/BanCheck.py b/src/tgbot/middlewares/BanCheck.py deleted file mode 100644 index 2c75cf7..0000000 --- a/src/tgbot/middlewares/BanCheck.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import ( - NoReturn, -) - -from aiogram import ( - types, -) -from aiogram.dispatcher.handler import ( - CancelHandler, -) -from aiogram.dispatcher.middlewares import ( - BaseMiddleware, -) - -from loader import ( - _, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.keyboards.inline.admin_inline import ( - unban_user_keyboard, -) - - -class BanMiddleware(BaseMiddleware): - def __init__(self): - super(BanMiddleware, self).__init__() - - @staticmethod - async def is_banned(user): - try: - return user.is_banned - except AttributeError: - return False - - async def on_process_message(self, message: types.Message, data: dict) -> None: - await self.check_ban_user(obj=message) - - async def on_process_callback_query( - self, call: types.CallbackQuery, data: dict - ) -> None: - user = await db_commands.select_user(telegram_id=call.from_user.id) - if (user is not None and await self.is_banned(user=user)) and ( - call.data != "unban" - and call.data != "unban_menu" - and call.data != "yoomoney:check_payment" - and call.data != "cancel_payment" - and call.data != "yoomoney" - ): - await self.check_ban_user(obj=call) - - async def check_ban_user( - self, obj: types.CallbackQuery | types.Message - ) -> NoReturn: - user = await db_commands.select_user(telegram_id=obj.from_user.id) - - text = _("😢 Вы заблокированы!") - markup = await unban_user_keyboard() - if await self.is_banned(user=user): - try: - await obj.answer(text=text, reply_markup=markup) - except TypeError: - await obj.message.answer(text=text, reply_markup=markup) - raise CancelHandler() diff --git a/src/tgbot/middlewares/IsMaintenanceCheck.py b/src/tgbot/middlewares/IsMaintenanceCheck.py deleted file mode 100644 index c62b39f..0000000 --- a/src/tgbot/middlewares/IsMaintenanceCheck.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import ( - NoReturn, -) - -from aiogram import ( - types, -) -from aiogram.dispatcher.handler import ( - CancelHandler, -) -from aiogram.dispatcher.middlewares import ( - BaseMiddleware, -) - -from loader import ( - _, -) -from src.tgbot.config import ( - load_config, -) -from src.infrastructure.db_api import ( - db_commands, -) - - -class IsMaintenance(BaseMiddleware): - def __init__(self): - super(IsMaintenance, self).__init__() - - async def on_process_message(self, message: types.Message, data: dict): - await self.check_tech_works(obj=message) - - async def on_process_callback_query(self, call: types.CallbackQuery, data: dict): - await self.check_tech_works(obj=call) - - @staticmethod - async def check_tech_works( - obj: types.CallbackQuery | types.Message - ) -> NoReturn: - text = _("Ведутся технические работы") - try: - setting = await db_commands.select_setting_tech_work() - tech_works = setting.get("technical_works", False) - if tech_works and obj.from_user.id not in load_config().tg_bot.admin_ids: - try: - await obj.answer(text=text, show_alert=True) - except TypeError: - await obj.answer(text=text) - raise CancelHandler() - except AttributeError: - pass diff --git a/src/tgbot/middlewares/LinkCheck.py b/src/tgbot/middlewares/LinkCheck.py deleted file mode 100644 index dea450c..0000000 --- a/src/tgbot/middlewares/LinkCheck.py +++ /dev/null @@ -1,68 +0,0 @@ -import asyncio -from typing import ( - NoReturn, - Union, -) - -from aiogram import ( - types, -) -from aiogram.dispatcher.handler import ( - CancelHandler, -) -from aiogram.dispatcher.middlewares import ( - BaseMiddleware, -) - -from src.tgbot.config import ( - load_config, -) -from src.infrastructure.db_api import ( - db_commands, -) -from src.tgbot.keyboards.inline.necessary_links_inline import ( - necessary_links_keyboard, -) - - -class LinkCheckMiddleware(BaseMiddleware): - async def on_process_message(self, message: types.Message, data: dict) -> None: - if isinstance(message.chat.type, types.ChatType): - await self._check_links_and_handle(message.from_user.id, obj=message) - - async def on_process_callback_query( - self, call: types.CallbackQuery, data: dict - ) -> None: - await self._check_links_and_handle(call.from_user.id, obj=call) - - @staticmethod - async def _check_links_and_handle( - user_id: int, obj: types.CallbackQuery | types.Message - ) -> NoReturn: - links_db = await db_commands.select_all_links() - subscribed_links = set() - - async def check_subscription(link_id): - check = await bot.get_chat_member(chat_id=link_id, user_id=user_id) - return check.status != "left" - - for link in links_db: - if await check_subscription(link["telegram_link_id"]): - subscribed_links.add(link["telegram_link_id"]) - text, markup = ( - "Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " - "подпишитесь! Ссылки ниже: " - ), await necessary_links_keyboard( - telegram_id=user_id, - links_db=links_db, - ) - if ( - len(subscribed_links) != len(links_db) - and obj.from_user.id not in load_config().tg_bot.admin_ids - ): - try: - await obj.answer(text=text, reply_markup=markup) - except TypeError: - await obj.message.answer(text=text, reply_markup=markup) - await asyncio.sleep(1) - raise CancelHandler() diff --git a/src/tgbot/middlewares/Log.py b/src/tgbot/middlewares/Log.py deleted file mode 100644 index a34dc75..0000000 --- a/src/tgbot/middlewares/Log.py +++ /dev/null @@ -1,18 +0,0 @@ -from aiogram import ( - types, -) -from aiogram.dispatcher.middlewares import ( - BaseMiddleware, -) - -from loader import ( - logger, -) - - -class LogMiddleware(BaseMiddleware): - async def on_process_message(self, message: types.Message, data: dict): - logger.info(message) - - async def on_process_callback_query(self, call: types.CallbackQuery, data: dict): - logger.info(call) diff --git a/src/tgbot/middlewares/Throttling.py b/src/tgbot/middlewares/Throttling.py deleted file mode 100644 index 2a6a394..0000000 --- a/src/tgbot/middlewares/Throttling.py +++ /dev/null @@ -1,66 +0,0 @@ -import asyncio -from typing import ( - NoReturn, -) - -from aiogram import ( - Dispatcher, - types, -) -from aiogram.dispatcher import ( - DEFAULT_RATE_LIMIT, -) -from aiogram.dispatcher.handler import ( - CancelHandler, - current_handler, -) -from aiogram.dispatcher.middlewares import ( - BaseMiddleware, -) -from aiogram.utils.exceptions import ( - Throttled, -) - - -class ThrottlingMiddleware(BaseMiddleware): - def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix="antiflood_"): - self.rate_limit = limit - self.prefix = key_prefix - super(ThrottlingMiddleware, self).__init__() - - # noinspection PyUnusedLocal - async def on_process_message(self, message: types.Message, data: dict) -> NoReturn: - handler = current_handler.get() - dispatcher = Dispatcher.get_current() - if handler: - limit = getattr(handler, "throttling_rate_limit", self.rate_limit) - key = getattr( - handler, "throttling_key", f"{self.prefix}_{handler.__name__}" - ) - else: - limit = self.rate_limit - key = f"{self.prefix}_message" - try: - await dispatcher.throttle(key, rate=limit) - except Throttled as t: - await self.message_throttled(message, t) - raise CancelHandler() - - async def message_throttled( - self, message: types.Message, throttled: Throttled - ) -> None: - handler = current_handler.get() - dispatcher = Dispatcher.get_current() - if handler: - key = getattr( - handler, "throttling_key", f"{self.prefix}_{handler.__name__}" - ) - else: - key = f"{self.prefix}_message" - delta = throttled.rate - throttled.delta - if throttled.exceeded_count <= 2: - await message.reply("Too many requests! ") - await asyncio.sleep(delta) - thr = await dispatcher.check_key(key) - if thr.exceeded_count == throttled.exceeded_count: - await message.reply("Unlocked.") From 9caaf6ceea9c026b4025e85cdc4d9742c643ced0 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:30:11 +0300 Subject: [PATCH 043/148] =?UTF-8?q?=F0=9F=9A=A7=20rewriting=20middlewares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/__init__.py | 6 +- src/tgbot/middlewares/check_ban.py | 62 ++++++++++++++++++ src/tgbot/middlewares/check_maintenance.py | 51 +++++++++++++++ .../middlewares/{Config.py => config.py} | 16 +++-- src/tgbot/middlewares/link_check.py | 65 +++++++++++++++++++ .../{SchedulerWare.py => scheduler.py} | 17 ++--- .../{AgentSupport.py => tech_support.py} | 13 ++-- 7 files changed, 212 insertions(+), 18 deletions(-) create mode 100644 src/tgbot/middlewares/check_ban.py create mode 100644 src/tgbot/middlewares/check_maintenance.py rename src/tgbot/middlewares/{Config.py => config.py} (55%) create mode 100644 src/tgbot/middlewares/link_check.py rename src/tgbot/middlewares/{SchedulerWare.py => scheduler.py} (65%) rename src/tgbot/middlewares/{AgentSupport.py => tech_support.py} (83%) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index 5ed3183..c025fb4 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -19,7 +19,9 @@ # from .Throttling import ( # ThrottlingMiddleware, # ) -from .Config import ConfigMiddleware +from .config import ( + MiscMiddleware, +) __all__ = ( # "AgentSupport", @@ -31,5 +33,5 @@ # "ThrottlingMiddleware", # "SupportMiddleware", # "LinkCheckMiddleware", - "ConfigMiddleware", + "MiscMiddleware", ) diff --git a/src/tgbot/middlewares/check_ban.py b/src/tgbot/middlewares/check_ban.py new file mode 100644 index 0000000..3e7d2bc --- /dev/null +++ b/src/tgbot/middlewares/check_ban.py @@ -0,0 +1,62 @@ +from typing import ( + Any, + Dict, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + TelegramObject, +) + +from src.tgbot.types import ( + Handler, +) + + +class BanMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Handler, + event: TelegramObject, + data: Dict[str, Any] + ) -> Any: + pass + + # @staticmethod + # async def is_banned(user): + # try: + # return user.is_banned + # except AttributeError: + # return False + + # async def on_process_message(self, message: types.Message, data: dict) -> None: + # await self.check_ban_user(obj=message) + # + # async def on_process_callback_query( + # self, call: types.CallbackQuery, data: dict + # ) -> None: + # user = await db_commands.select_user(telegram_id=call.from_user.id) + # if (user is not None and await self.is_banned(user=user)) and ( + # call.data != "unban" + # and call.data != "unban_menu" + # and call.data != "Yoomoney:check_payment" + # and call.data != "cancel_payment" + # and call.data != "Yoomoney" + # ): + # await self.check_ban_user(obj=call) + # + # async def check_ban_user( + # self, obj: types.CallbackQuery | types.Message + # ) -> NoReturn: + # user = await db_commands.select_user(telegram_id=obj.from_user.id) + # + # text = _("😢 Вы заблокированы!") + # markup = await unban_user_keyboard() + # if await self.is_banned(user=user): + # try: + # await obj.answer(text=text, reply_markup=markup) + # except TypeError: + # await obj.message.answer(text=text, reply_markup=markup) + # raise CancelHandler() diff --git a/src/tgbot/middlewares/check_maintenance.py b/src/tgbot/middlewares/check_maintenance.py new file mode 100644 index 0000000..672f296 --- /dev/null +++ b/src/tgbot/middlewares/check_maintenance.py @@ -0,0 +1,51 @@ +from typing import ( + Any, + Dict, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + TelegramObject, +) + +from src.tgbot.types import ( + Handler, +) + + +class IsMaintenance(BaseMiddleware): + async def __call__( + self, + handler: Handler, + event: TelegramObject, + data: Dict[str, Any] + ) -> Any: + pass + + # def __init__(self): + # super(IsMaintenance, self).__init__() + # + # async def on_process_message(self, message: types.Message, data: dict): + # await self.check_tech_works(obj=message) + # + # async def on_process_callback_query(self, call: types.CallbackQuery, data: dict): + # await self.check_tech_works(obj=call) + # + # @staticmethod + # async def check_tech_works( + # obj: types.CallbackQuery | types.Message + # ) -> NoReturn: + # text = _("Ведутся технические работы") + # try: + # setting = await db_commands.select_setting_tech_work() + # tech_works = setting.get("technical_works", False) + # if tech_works and obj.from_user.id not in load_config().tg_bot.admin_ids: + # try: + # await obj.answer(text=text, show_alert=True) + # except TypeError: + # await obj.answer(text=text) + # raise CancelHandler() + # except AttributeError: + # pass diff --git a/src/tgbot/middlewares/Config.py b/src/tgbot/middlewares/config.py similarity index 55% rename from src/tgbot/middlewares/Config.py rename to src/tgbot/middlewares/config.py index e2ea2ce..04992ed 100644 --- a/src/tgbot/middlewares/Config.py +++ b/src/tgbot/middlewares/config.py @@ -7,23 +7,31 @@ BaseMiddleware, ) from aiogram.types import ( - Message, + TelegramObject, +) +from que_sdk import ( + QueClient, ) +from src.tgbot.config import ( + Config, +) from src.tgbot.types import ( Handler, ) -class ConfigMiddleware(BaseMiddleware): - def __init__(self, config) -> None: +class MiscMiddleware(BaseMiddleware): + def __init__(self, config: Config, client: QueClient) -> None: self.config = config + self.client = client async def __call__( self, handler: Handler, - event: Message, + event: TelegramObject, data: Dict[str, Any], ) -> Any: data["config"] = self.config + data["que-client"] = self.client return await handler(event, data) diff --git a/src/tgbot/middlewares/link_check.py b/src/tgbot/middlewares/link_check.py new file mode 100644 index 0000000..98b5887 --- /dev/null +++ b/src/tgbot/middlewares/link_check.py @@ -0,0 +1,65 @@ +from typing import ( + Any, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + TelegramObject, +) + +from src.tgbot.types import ( + Handler, +) + + +class LinkCheckMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Handler, + event: TelegramObject, + data: dict[str, Any] + ) -> Any: + pass + + # async def on_process_message(self, message: types.Message, data: dict) -> None: + # if isinstance(message.chat.type, types.ChatType): + # await self._check_links_and_handle(message.from_user.id, obj=message) + # + # async def on_process_callback_query( + # self, call: types.CallbackQuery, data: dict + # ) -> None: + # await self._check_links_and_handle(call.from_user.id, obj=call) + # + # @staticmethod + # async def _check_links_and_handle( + # user_id: int, obj: types.CallbackQuery | types.Message + # ) -> NoReturn: + # links_db = await db_commands.select_all_links() + # subscribed_links = set() + # + # async def check_subscription(link_id): + # check = await bot.get_chat_member(chat_id=link_id, user_id=user_id) + # return check.status != "left" + # + # for link in links_db: + # if await check_subscription(link["telegram_link_id"]): + # subscribed_links.add(link["telegram_link_id"]) + # text, markup = ( + # "Вы подписались не на все каналы! Чтобы продолжить пользоваться ботом, " + # "подпишитесь! Ссылки ниже: " + # ), await necessary_links_keyboard( + # telegram_id=user_id, + # links_db=links_db, + # ) + # if ( + # len(subscribed_links) != len(links_db) + # and obj.from_user.id not in load_config().tg_bot.admin_ids + # ): + # try: + # await obj.answer(text=text, reply_markup=markup) + # except TypeError: + # await obj.message.answer(text=text, reply_markup=markup) + # await asyncio.sleep(1) + # raise CancelHandler() diff --git a/src/tgbot/middlewares/SchedulerWare.py b/src/tgbot/middlewares/scheduler.py similarity index 65% rename from src/tgbot/middlewares/SchedulerWare.py rename to src/tgbot/middlewares/scheduler.py index b40c9b8..5f6a2c9 100644 --- a/src/tgbot/middlewares/SchedulerWare.py +++ b/src/tgbot/middlewares/scheduler.py @@ -1,31 +1,32 @@ from typing import ( Any, - Awaitable, - Callable, - Dict, ) -from aiogram.dispatcher.middlewares import ( +from aiogram import ( BaseMiddleware, ) -from aiogram.types.base import ( +from aiogram.types import ( TelegramObject, ) from apscheduler.schedulers.asyncio import ( AsyncIOScheduler, ) +from src.tgbot.types import ( + Handler, +) + class SchedulerMiddleware(BaseMiddleware): def __init__(self, scheduler: AsyncIOScheduler): super(SchedulerMiddleware, self).__init__() self.scheduler = scheduler - def __call__( + async def __call__( self, - handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]], + handler: Handler, event: TelegramObject, data: dict[str, Any], ) -> Any: data["appscheduler"] = self.scheduler - return handler(event, data) + return await handler(event, data) diff --git a/src/tgbot/middlewares/AgentSupport.py b/src/tgbot/middlewares/tech_support.py similarity index 83% rename from src/tgbot/middlewares/AgentSupport.py rename to src/tgbot/middlewares/tech_support.py index 7e53473..4d9cb2a 100644 --- a/src/tgbot/middlewares/AgentSupport.py +++ b/src/tgbot/middlewares/tech_support.py @@ -1,12 +1,17 @@ from typing import ( - Dict, Any, ) + Any, +) from aiogram import ( BaseMiddleware, ) -from aiogram.types import TelegramObject +from aiogram.types import ( + TelegramObject, +) -from src.tgbot.types import Handler +from src.tgbot.types import ( + Handler, +) class SupportMiddleware(BaseMiddleware): @@ -15,7 +20,7 @@ async def __call__( self, handler: Handler, event: TelegramObject, - data: Dict[str, Any] + data: dict[str, Any] ) -> Any: pass # dispatcher = Dispatcher.get_current() From 52eaaca4189dda18bebf49ed7e8b7f11ce365d19 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:31:52 +0300 Subject: [PATCH 044/148] =?UTF-8?q?=F0=9F=92=84=20update=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/app/set_bot_commands.py | 35 ++++++++++------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/tgbot/services/app/set_bot_commands.py b/src/tgbot/services/app/set_bot_commands.py index bcc41c4..f1fb5c6 100644 --- a/src/tgbot/services/app/set_bot_commands.py +++ b/src/tgbot/services/app/set_bot_commands.py @@ -1,7 +1,8 @@ import logging from aiogram import ( - types, Bot, + Bot, + types, ) from src.tgbot.config import ( @@ -11,7 +12,7 @@ async def set_user_commands( bot: Bot, user_id: int, commands: list[types.BotCommand] -): +) -> None: try: await bot.set_my_commands( commands=commands, scope=types.BotCommandScopeChat(chat_id=user_id) @@ -23,24 +24,20 @@ async def set_user_commands( async def set_default_commands(bot: Bot, config: Config) -> None: default_commands = [ types.BotCommand(command="start", description="🟢 Запустить бота"), - types.BotCommand(command="catalog", description="🏪 Открыть каталог"), - types.BotCommand(command="profile", description="👨 Личный кабинет"), - types.BotCommand(command="order", description="🚚 Статус заказа"), - types.BotCommand(command="cart", description="📂 Корзина"), - ] - - admin_commands = [ - types.BotCommand(command="admin", description="⚒ Админ-Меню"), - types.BotCommand(command="users", description="🫂 Пользователи"), - types.BotCommand(command="settings", description="⚙️ Настройки"), - types.BotCommand(command="logs", description="🗒 Логи"), ] await bot.set_my_commands(default_commands, scope=types.BotCommandScopeDefault()) - for admin_id in config.tg_bot.admin_ids: - await set_user_commands( - bot=bot, - user_id=admin_id, - commands=admin_commands + default_commands - ) + # admin_commands = [ + # types.BotCommand(command="admin", description="⚒ Админ-Меню"), + # types.BotCommand(command="users", description="🫂 Пользователи"), + # types.BotCommand(command="settings", description="⚙️ Настройки"), + # types.BotCommand(command="logs", description="🗒 Логи"), + # ] + # + # for admin_id in config.tg_bot.admin_ids: + # await set_user_commands( + # bot=bot, + # user_id=admin_id, + # commands=admin_commands + default_commands + # ) From e044bfed4095e533a06b9ecabae760f567ce0ffa Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:32:49 +0300 Subject: [PATCH 045/148] =?UTF-8?q?=E2=9C=A8=20add=20func=20for=20handle?= =?UTF-8?q?=20user=20req?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/__init__.py | 1 + src/tgbot/services/app/__init__.py | 24 +++++-- src/tgbot/services/app/user_operations.py | 83 +++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/tgbot/services/app/user_operations.py diff --git a/src/tgbot/services/__init__.py b/src/tgbot/services/__init__.py index e69de29..1afb6e3 100644 --- a/src/tgbot/services/__init__.py +++ b/src/tgbot/services/__init__.py @@ -0,0 +1 @@ +from .app import * diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py index b54b049..fce57b3 100644 --- a/src/tgbot/services/app/__init__.py +++ b/src/tgbot/services/app/__init__.py @@ -1,4 +1,20 @@ -from .broadcaster import ( - send_message, - broadcast, -) \ No newline at end of file +from . import ( + broadcaster, +) +from .user_operations import ( + get_user_data, + handle_login_t_me, + handle_not_founded_user, + handle_send_start_message, + handle_signup, + welcoming_message, +) + +__all__ = ( + "broadcaster", + "welcoming_message", + "get_user_data", + "handle_login_t_me", + "handle_signup", + "handle_not_founded_user", +) diff --git a/src/tgbot/services/app/user_operations.py b/src/tgbot/services/app/user_operations.py new file mode 100644 index 0000000..7df5747 --- /dev/null +++ b/src/tgbot/services/app/user_operations.py @@ -0,0 +1,83 @@ +from typing import ( + Literal, +) + +from aiogram import ( + types, +) +from aiogram.fsm.context import ( + FSMContext, +) +from que_sdk import ( + QueClient, + SignUpSchema, + TMELoginSchema, +) + +from src.tgbot.config import ( + Config, +) +from src.tgbot.keyboards import ( + reply, +) +from src.tgbot.misc import ( + security, +) + + +def welcoming_message(username: str, message_type: Literal["welcome", "greet_auth_user"]) -> str: + messages = { + "welcome": "Добро пожаловать, {username}! Вы создали новый аккаунт".format(username=username), + "greet_auth_user": "Привет {username} вы вошли в аккаунт".format(username=username) + } + + return messages[message_type] + + +async def get_user_data(client: QueClient, storage: dict): + access_token = storage.get("access_token") + status_code, response = await client.get_user_me(access_token) + + return status_code, response + + +async def handle_send_start_message(username: str, message: types.Message): + await message.answer( + text=welcoming_message(username=username, message_type="greet_auth_user"), + reply_markup=reply.main_menu() + ) + + +async def handle_login_t_me(client: QueClient, config: Config, message: types.Message, state: FSMContext): + auth_data = security.generate_signature(telegram_id=message.from_user.id, secret_key=config.misc.secret_key) + status_code, response = await client.login_t_me(data_in=TMELoginSchema(**auth_data)) + access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') + + await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) + + return status_code, response + + +async def handle_signup(client: QueClient, message: types.Message, state: FSMContext, config: Config): + username = message.from_user.username + status_code, response = await client.signup( + data_in=SignUpSchema( + username=username, + telegram_id=message.from_user.id, + ) + ) + + await message.answer( + text=welcoming_message(username=username, message_type="welcome"), + reply_markup=reply.main_menu() + ) + await handle_login_t_me(client=client, state=state, config=config, message=message) + + return status_code, response + + +async def handle_not_founded_user(message: types.Message): + await message.answer( + text="Мы не смогли найти ваш в аккаунт. Создайте новый или войдите с помощью логина и пароля", + reply_markup=reply.login_signup_menu() + ) From 8c528d7ad5029ea3dec5637d43fe6ba88a2f16ee Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:33:33 +0300 Subject: [PATCH 046/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/config.py | 69 ++------------------------------------------- 1 file changed, 3 insertions(+), 66 deletions(-) diff --git a/src/tgbot/config.py b/src/tgbot/config.py index b003260..84d7c2e 100644 --- a/src/tgbot/config.py +++ b/src/tgbot/config.py @@ -1,10 +1,10 @@ -import inspect from dataclasses import ( dataclass, ) from functools import ( lru_cache, ) +import inspect from pathlib import ( Path, ) @@ -12,49 +12,6 @@ from environs import ( Env, ) -from yarl import ( - URL, -) - - -@dataclass(frozen=True, slots=True) -class DataBaseConfig: - """ - Database configuration class. - This class holds the settings for the database, such as host, password, port, etc. - - Attributes - ---------- - host : str - The host where the database server is located. - password : str - The password used to authenticate with the database. - user : str - The username used to authenticate with the database. - database : str - The name of the database. - port : int - The port where the database server is listening. - """ - user: str - password: str - host: str - database: str - port: str - - @staticmethod - def from_env(env: Env): - """ - Creates the DbConfig object from environment variables. - """ - host = env.str("DB_HOST") - password = env.str("POSTGRES_PASSWORD") - user = env.str("POSTGRES_USER") - database = env.str("POSTGRES_DB") - port = env.int("DB_PORT", 5432) - return DataBaseConfig( - host=host, password=password, user=user, database=database, port=port - ) @dataclass @@ -86,7 +43,7 @@ def dsn(self) -> str: return f"redis://{self.redis_host}:{self.redis_port}/0" @staticmethod - def from_env(env: Env): + def from_env(env: Env) -> "RedisConfig": """ Creates the RedisConfig object from environment variables. """ @@ -104,7 +61,6 @@ class TgBot: support_ids: list[int] timezone: str ip: str - I18N_DOMAIN: str moderate_chat: int use_redis: bool @@ -118,7 +74,6 @@ def from_env(env: Env) -> "TgBot": support_ids = list(map(int, env.list("SUPPORTS"))) ip = env.str("IP") timezone = env.str("TIMEZONE") - I18N_DOMAIN = "dating" moderate_chat = env.int("MODERATE_CHAT") use_redis = env.bool("USE_REDIS") return TgBot( @@ -127,7 +82,6 @@ def from_env(env: Env) -> "TgBot": support_ids=support_ids, ip=ip, timezone=timezone, moderate_chat=moderate_chat, - I18N_DOMAIN=I18N_DOMAIN, use_redis=use_redis, ) @@ -135,37 +89,21 @@ def from_env(env: Env) -> "TgBot": @dataclass(frozen=True, slots=True) class Miscellaneous: secret_key: str - yandex_api_key: str - client_id: str - redirect_url: URL - yoomoney_key: str - production: bool @staticmethod def from_env(env: Env) -> "Miscellaneous": """ Creates the Miscellaneous object from environment variables. """ - secret_key = env.str("SECRET_KEY") - yandex_api_key = env.str("API_KEY") - client_id = env.str("CLIENT_ID") - redirect_url = env.str("REDIRECT_URI") - yoomoney_key = env.str("YOOMONEY_KEY") - production = env.bool("PRODUCTION") + secret_key = env.str("SIGNATURE_SECRET_KEY") return Miscellaneous( secret_key=secret_key, - yandex_api_key=yandex_api_key, - client_id=client_id, - redirect_url=redirect_url, - production=production, - yoomoney_key=yoomoney_key ) @dataclass(frozen=True, slots=True) class Config: tg_bot: TgBot - db: DataBaseConfig misc: Miscellaneous redis: RedisConfig | None = None @@ -209,6 +147,5 @@ def load_config() -> Config: return Config( tg_bot=TgBot.from_env(env), - db=DataBaseConfig.from_env(env), misc=Miscellaneous.from_env(env), ) From f5154d0e89e7d493d8c238ae8e0c8ea3be439223 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:34:03 +0300 Subject: [PATCH 047/148] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20update=20keyboa?= =?UTF-8?q?rds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/__init__.py | 7 +++- .../{inline/__init__.py => inline.py} | 0 src/tgbot/keyboards/reply.py | 33 +++++++++++++++++++ src/tgbot/keyboards/reply/__init__.py | 0 4 files changed, 39 insertions(+), 1 deletion(-) rename src/tgbot/keyboards/{inline/__init__.py => inline.py} (100%) create mode 100644 src/tgbot/keyboards/reply.py delete mode 100644 src/tgbot/keyboards/reply/__init__.py diff --git a/src/tgbot/keyboards/__init__.py b/src/tgbot/keyboards/__init__.py index a5a25a5..1e14f37 100644 --- a/src/tgbot/keyboards/__init__.py +++ b/src/tgbot/keyboards/__init__.py @@ -1,4 +1,9 @@ from . import ( - reply, inline, + reply, +) + +__all__ = ( + "inline", + "reply", ) diff --git a/src/tgbot/keyboards/inline/__init__.py b/src/tgbot/keyboards/inline.py similarity index 100% rename from src/tgbot/keyboards/inline/__init__.py rename to src/tgbot/keyboards/inline.py diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py new file mode 100644 index 0000000..6a0ddcb --- /dev/null +++ b/src/tgbot/keyboards/reply.py @@ -0,0 +1,33 @@ +from aiogram import ( + types, +) +from aiogram.types import ( + WebAppInfo, +) +from aiogram.utils.keyboard import ( + ReplyKeyboardBuilder, +) + + +def main_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.add( + types.KeyboardButton(text="👤 Аккаунт") + ) + builder.row( + types.KeyboardButton(text="💜 Знакомства"), + types.KeyboardButton(text="🎭 Мероприятия"), + ) + builder.row( + types.KeyboardButton(text="❔ О проекте") + ) + return builder.as_markup(resize_keyboard=True) + + +def login_signup_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton(text="✏️ Создать аккаунт"), + types.KeyboardButton(text="🛂 Войти в аккаунт", web_app=WebAppInfo(url="https://light-clouds-sleep.loca.lt")), + ) + return builder.as_markup(resize_keyboard=True) diff --git a/src/tgbot/keyboards/reply/__init__.py b/src/tgbot/keyboards/reply/__init__.py deleted file mode 100644 index e69de29..0000000 From e7af9197b9ade1fc584cf86b8f473b44f9b3e2e3 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:35:05 +0300 Subject: [PATCH 048/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/app/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py index fce57b3..5d74ab7 100644 --- a/src/tgbot/services/app/__init__.py +++ b/src/tgbot/services/app/__init__.py @@ -17,4 +17,5 @@ "handle_login_t_me", "handle_signup", "handle_not_founded_user", + "handle_send_start_message" ) From 9d6d8a61281d89b8cf9edd0ac7213e3ddaff8e42 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:35:13 +0300 Subject: [PATCH 049/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 87 +++++++++++++--------- src/tgbot/{services/app => misc}/states.py | 5 +- 2 files changed, 55 insertions(+), 37 deletions(-) rename src/tgbot/{services/app => misc}/states.py (92%) diff --git a/bot.py b/bot.py index 31cdd40..d957698 100644 --- a/bot.py +++ b/bot.py @@ -1,20 +1,40 @@ import asyncio import logging -import betterlogging as bl from aiogram import ( - Dispatcher, Bot, + Bot, + Dispatcher, +) +from aiogram.client.default import ( + DefaultBotProperties, +) +from aiogram.enums import ( + ParseMode, +) +from aiogram.fsm.storage.memory import ( + MemoryStorage, +) +from aiogram.fsm.storage.redis import ( + DefaultKeyBuilder, + RedisStorage, +) +import betterlogging as bl +from que_sdk import ( + QueClient, +) + +from src.tgbot.config import ( + Config, + load_config, +) +from src.tgbot.handlers import ( + routers_list, ) -from aiogram.client.default import DefaultBotProperties -from aiogram.enums import ParseMode -from aiogram.fsm.storage.memory import MemoryStorage -from aiogram.fsm.storage.redis import RedisStorage, DefaultKeyBuilder - -from src.tgbot.config import Config, load_config -from src.tgbot.handlers import routers_list -from src.tgbot.middlewares import ConfigMiddleware -from src.tgbot.services.app import ( - broadcaster +from src.tgbot.middlewares import ( + MiscMiddleware, +) +from src.tgbot.services import ( + broadcaster, ) @@ -22,26 +42,7 @@ async def on_startup(bot: Bot, admin_ids: list[int]) -> None: await broadcaster.broadcast(bot, admin_ids, "Бот запущен") -def register_global_middlewares( - dp: Dispatcher, - config: Config -) -> None: - middleware_types = [ - ConfigMiddleware(config) - # ThrottlingMiddleware(), - # LinkCheckMiddleware(), - # SupportMiddleware(), - # IsMaintenance(), - # SchedulerMiddleware(scheduler), - # BanMiddleware(), - # LogMiddleware(), - ] - for middleware_type in middleware_types: - dp.message.outer_middleware(middleware_type) - dp.callback_query.outer_middleware(middleware_type) - - -def setup_logging(): +def setup_logging() -> None: """ Set up logging configuration for the application. @@ -67,7 +68,21 @@ def setup_logging(): logger.info("Starting bot") -def get_storage(config): +def register_global_middlewares( + dp: Dispatcher, + config: Config, + client: QueClient +) -> None: + logging.info("Setup middlewares...") + middleware_types = [ + MiscMiddleware(config, client), + ] + for middleware_type in middleware_types: + dp.message.outer_middleware(middleware_type) + dp.callback_query.outer_middleware(middleware_type) + + +def get_storage(config: Config) -> MemoryStorage | RedisStorage: """ Return storage based on the provided configuration. @@ -87,18 +102,18 @@ def get_storage(config): return MemoryStorage() -async def main(): +async def main() -> None: setup_logging() config = load_config() storage = get_storage(config) - + client = QueClient() bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) dp = Dispatcher(storage=storage) dp.include_routers(*routers_list) - register_global_middlewares(dp, config) + register_global_middlewares(dp, config, client) await on_startup(bot, config.tg_bot.admin_ids) await dp.start_polling(bot) diff --git a/src/tgbot/services/app/states.py b/src/tgbot/misc/states.py similarity index 92% rename from src/tgbot/services/app/states.py rename to src/tgbot/misc/states.py index 0a2cb7d..7bfb86d 100644 --- a/src/tgbot/services/app/states.py +++ b/src/tgbot/misc/states.py @@ -1,4 +1,7 @@ -from aiogram.fsm.state import StatesGroup, State +from aiogram.fsm.state import ( + State, + StatesGroup, +) class AdminsActions(StatesGroup): From f4a6fa499d9e6e1680acab1696bf56ad136e5928 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:36:37 +0300 Subject: [PATCH 050/148] =?UTF-8?q?=F0=9F=9B=82=20generation=20signature?= =?UTF-8?q?=20for=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/misc/__init__.py | 5 +++++ src/tgbot/misc/security.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/tgbot/misc/__init__.py create mode 100644 src/tgbot/misc/security.py diff --git a/src/tgbot/misc/__init__.py b/src/tgbot/misc/__init__.py new file mode 100644 index 0000000..75463fb --- /dev/null +++ b/src/tgbot/misc/__init__.py @@ -0,0 +1,5 @@ +from . import ( + security, +) + +__all__ = ("security",) diff --git a/src/tgbot/misc/security.py b/src/tgbot/misc/security.py new file mode 100644 index 0000000..f67ccaa --- /dev/null +++ b/src/tgbot/misc/security.py @@ -0,0 +1,14 @@ +import hashlib +import hmac +import secrets +import time +from typing import Any + + +def generate_signature(telegram_id: int, secret_key: str) -> dict[str, Any]: + timestamp = int(time.time()) + nonce = secrets.randbits(32) % (999999 - 100000) + 100000 + data_to_sign = f"{telegram_id}{timestamp}{nonce}" + + signature = hmac.new(secret_key.encode(), data_to_sign.encode(), hashlib.sha256).hexdigest() + return {"telegram_id": telegram_id, "signature": signature, "nonce": nonce, "timestamp": timestamp} From 94be36376640796051afade7855871d030f367a6 Mon Sep 17 00:00:00 2001 From: dromanov Date: Tue, 23 Apr 2024 18:37:30 +0300 Subject: [PATCH 051/148] =?UTF-8?q?=E2=9C=A8=20add=20handler=20for=20signi?= =?UTF-8?q?n/signup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/start.py | 54 ++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 4009c27..d1eab9a 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -1,9 +1,55 @@ -from aiogram import Router, types -from aiogram.filters import CommandStart +import http +from typing import Any + +from aiogram import ( + F, + Router, + types, +) +from aiogram.filters import ( + CommandStart, +) +from aiogram.fsm.context import ( + FSMContext, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot.config import ( + Config, +) +from src.tgbot.services import ( + get_user_data, + handle_login_t_me, + handle_not_founded_user, + handle_signup, +) +from src.tgbot.services.app import ( + handle_send_start_message, +) start_router = Router() @start_router.message(CommandStart()) -async def start_handler(message: types.Message): - await message.answer("Привет {user}".format(user=message.from_user.full_name)) +async def start_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + config: Config = middleware_data.get("config") + client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + + if not storage: + status_code, response = await handle_login_t_me(client, config, message, state) + if status_code == http.HTTPStatus.NOT_FOUND: + await handle_not_founded_user(message=message) + storage = await state.get_data() + status_code, response = await get_user_data(client, storage) + if status_code != http.HTTPStatus.UNAUTHORIZED: + await handle_send_start_message(message=message, username=response.get("username")) + + +@start_router.message(F.text == "✏️ Создать аккаунт") +async def signup_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + client: QueClient = middleware_data.get("que-client") + config: Config = middleware_data.get("config") + await handle_signup(client=client, message=message, state=state, config=config) From 2325b892c53fa17f37bfd584537f65ad377ad795 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 18:58:45 +0300 Subject: [PATCH 052/148] =?UTF-8?q?=F0=9F=93=9D=20update=20description?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bfe6b6c..f935b57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "datingbot" version = "0.1.0" -description = "A Telegram Bot with open source code, which implemented django and a dating system" +description = "A Dating Telegram Bot with open source code" authors = ["David Dzhalaev <72649244+DavidRomanovizc@users.noreply.github.com>"] license = "Attribution-NonCommercial 3.0 Unported" readme = "README.md" From e2217fcfeec719d330118d625c70928d71f97b98 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 19:10:11 +0300 Subject: [PATCH 053/148] =?UTF-8?q?=F0=9F=92=84=20update=20frontend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.eslintrc.cjs | 33 +- frontend/README.md | 8 + frontend/index.html | 6 +- frontend/package-lock.json | 3021 ++++++++++------- frontend/package.json | 18 +- frontend/public/vite.svg | 1 - frontend/src/App.jsx | 20 + frontend/src/App.tsx | 37 - frontend/src/assets/react.svg | 1 - frontend/src/components/Button/Button.css | 9 + frontend/src/components/Button/Button.jsx | 10 + frontend/src/components/Header/Header.css | 11 + frontend/src/components/Header/Header.jsx | 18 + .../src/components/LoginForm/LoginForm.css | 49 + .../src/components/LoginForm/LoginForm.jsx | 44 + frontend/src/configs/AxiosConfig.js | 48 + frontend/src/hooks/UseTelegram.js | 22 + frontend/src/index.css | 93 + frontend/src/main.jsx | 13 + frontend/src/main.tsx | 10 - frontend/src/style.ts | 58 - frontend/src/vite-env.d.ts | 1 - frontend/tsconfig.json | 25 - frontend/tsconfig.node.json | 11 - frontend/{vite.config.ts => vite.config.js} | 0 25 files changed, 2133 insertions(+), 1434 deletions(-) create mode 100644 frontend/README.md delete mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.jsx delete mode 100644 frontend/src/App.tsx delete mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/Button/Button.css create mode 100644 frontend/src/components/Button/Button.jsx create mode 100644 frontend/src/components/Header/Header.css create mode 100644 frontend/src/components/Header/Header.jsx create mode 100644 frontend/src/components/LoginForm/LoginForm.css create mode 100644 frontend/src/components/LoginForm/LoginForm.jsx create mode 100644 frontend/src/configs/AxiosConfig.js create mode 100644 frontend/src/hooks/UseTelegram.js create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.jsx delete mode 100644 frontend/src/main.tsx delete mode 100644 frontend/src/style.ts delete mode 100644 frontend/src/vite-env.d.ts delete mode 100644 frontend/tsconfig.json delete mode 100644 frontend/tsconfig.node.json rename frontend/{vite.config.ts => vite.config.js} (100%) diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 3d504cb..3e212e1 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -1,18 +1,21 @@ module.exports = { - root: true, - env: {browser: true, es2020: true}, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, + settings: { react: { version: '18.2' } }, + plugins: ['react-refresh'], + rules: { + 'react/jsx-no-target-blank': 'off', + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - {allowConstantExport: true}, - ], - }, + }, } diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..f768e33 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,8 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/frontend/index.html b/frontend/index.html index f4fae88..b6ede01 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,14 +1,14 @@ + - - Vite + React + TS + Vite + React
- + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 061d6a3..f3d4cd5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,27 +8,21 @@ "name": "frontend", "version": "0.0.0", "dependencies": { - "@mui/icons-material": "^5.15.15", - "@mui/material": "^5.15.15", - "@vkruglikov/react-telegram-web-app": "^2.1.9", + "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", - "bootstrap": "^5.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.22.3", - "styled-components": "^6.1.8", - "yup": "^1.4.0" + "react-redux": "^9.1.1", + "react-router-dom": "^6.23.0" }, "devDependencies": { "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", - "typescript": "^5.2.2", "vite": "^5.2.0" } }, @@ -106,15 +100,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { "version": "7.24.4", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", @@ -146,15 +131,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -351,17 +327,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", @@ -411,51 +376,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", - "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" - }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -871,16 +791,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -896,18 +806,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", @@ -917,40 +815,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", - "dependencies": { - "@floating-ui/utils": "^0.2.1" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", - "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", - "dependencies": { - "@floating-ui/dom": "^1.6.1" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -965,28 +829,6 @@ "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1054,251 +896,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", - "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", - "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/icons-material": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.15.tgz", - "integrity": "sha512-kkeU/pe+hABcYDH6Uqy8RmIsr2S/y5bP2rp+Gat4CcRjCcVne6KudS1NrZQhUCRysrTDCAhcbcf9gt+/+pGO2g==", - "dependencies": { - "@babel/runtime": "^7.23.9" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", - "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.15", - "@mui/system": "^5.15.15", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", - "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.14", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", - "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", - "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.14", - "@mui/styled-engine": "^5.15.14", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", - "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", - "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1334,27 +931,41 @@ "node": ">= 8" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "node_modules/@reduxjs/toolkit": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.3.tgz", + "integrity": "sha512-76dll9EnJXg4EVcI5YNxZA/9hSAmZsFqzMmNRHvIlzw2WS/twfcVX3ysYrWGJMClwEmChQFC4yRq74tn6fdzRA==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } } }, "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.0.tgz", + "integrity": "sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==", "engines": { "node": ">=14.0.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.15.0.tgz", - "integrity": "sha512-O63bJ7p909pRRQfOJ0k/Jp8gNFMud+ZzLLG5EBWquylHxmRT2k18M2ifg8WyjCgFVdpA7+rI0YZ8EkAtg6dSUw==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz", + "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==", "cpu": [ "arm" ], @@ -1365,9 +976,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.15.0.tgz", - "integrity": "sha512-5UywPdmC9jiVOShjQx4uuIcnTQOf85iA4jgg8bkFoH5NYWFfAfrJpv5eeokmTdSmYwUTT5IrcrBCJNkowhrZDA==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz", + "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==", "cpu": [ "arm64" ], @@ -1378,9 +989,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.15.0.tgz", - "integrity": "sha512-hNkt75uFfWpRxHItCBmbS0ba70WnibJh6yz60WShSWITLlVRbkvAu1E/c7RlliPY4ajhqJd0UPZz//gNalTd4g==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz", + "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==", "cpu": [ "arm64" ], @@ -1391,9 +1002,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.15.0.tgz", - "integrity": "sha512-HnC5bTP7qdfO9nUw/mBhNcjOEZfbS8NwV+nFegiMhYOn1ATAGZF4kfAxR9BuZevBrebWCxMmxm8NCU1CUoz+wQ==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz", + "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==", "cpu": [ "x64" ], @@ -1404,9 +1015,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.15.0.tgz", - "integrity": "sha512-QGOIQIJZeIIqMsc4BUGe8TnV4dkXhSW2EhaQ1G4LqMUNpkyeLztvlDlOoNHn7SR7a4dBANdcEbPkkEzz3rzjzA==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz", + "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==", "cpu": [ "arm" ], @@ -1417,9 +1028,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.15.0.tgz", - "integrity": "sha512-PS/Cp8CinYgoysQ8i4UXYH/TZl06fXszvY/RDkyBYgUB1+tKyOMS925/4FZhfrhkl3XQEKjMc3BKtsxpB9Tz9Q==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz", + "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==", "cpu": [ "arm" ], @@ -1430,9 +1041,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.15.0.tgz", - "integrity": "sha512-XzOsnD6lGDP+k+vGgTYAryVGu8N89qpjMN5BVFUj75dGVFP3FzIVAufJAraxirpDwEQZA7Gjs0Vo5p4UmnnjsA==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz", + "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==", "cpu": [ "arm64" ], @@ -1443,9 +1054,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.15.0.tgz", - "integrity": "sha512-+ScJA4Epbx/ZQGjDnbvTAcb8ZD06b+TlIka2UkujbKf1I/A+yrvEcJwG3/27zMmvcWMQyeCJhbL9TlSjzL0B7Q==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz", + "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==", "cpu": [ "arm64" ], @@ -1456,9 +1067,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.15.0.tgz", - "integrity": "sha512-1cUSvYgnyTakM4FDyf/GxUCDcqmj/hUh1NOizEOJU7+D5xEfFGCxgcNOs3hYBeRMUCcGmGkt01EhD3ILgKpGHQ==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz", + "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==", "cpu": [ "ppc64" ], @@ -1469,9 +1080,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.15.0.tgz", - "integrity": "sha512-3A1FbHDbBUvpJXFAZwVsiROIcstVHP9AX/cwnyIhAp+xyQ1cBCxywKtuzmw0Av1MDNNg/y/9dDHtNypfRa8bdw==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz", + "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==", "cpu": [ "riscv64" ], @@ -1482,9 +1093,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.15.0.tgz", - "integrity": "sha512-hYPbhg9ow6/mXIkojc8LOeiip2sCTuw1taWyoOXTOWk9vawIXz8x7B4KkgWUAtvAElssxhSyEXr2EZycH/FGzQ==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz", + "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==", "cpu": [ "s390x" ], @@ -1495,9 +1106,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.15.0.tgz", - "integrity": "sha512-511qln5mPSUKwv7HI28S1jCD1FK+2WbX5THM9A9annr3c1kzmfnf8Oe3ZakubEjob3IV6OPnNNcesfy+adIrmw==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz", + "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==", "cpu": [ "x64" ], @@ -1508,9 +1119,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.15.0.tgz", - "integrity": "sha512-4qKKGTDIv2bQZ+afhPWqPL+94+dLtk4lw1iwbcylKlLNqQ/Yyjof2CFYBxf6npiDzPV+zf4EWRiHb26/4Vsm9w==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz", + "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==", "cpu": [ "x64" ], @@ -1521,9 +1132,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.15.0.tgz", - "integrity": "sha512-nEtaFBHp1OnbOf+tz66DtID579sNRHGgMC23to8HUyVuOCpCMD0CvRNqiDGLErLNnwApWIUtUl1VvuovCWUxwg==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz", + "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==", "cpu": [ "arm64" ], @@ -1534,9 +1145,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.15.0.tgz", - "integrity": "sha512-5O49NykwSgX6iT2HgZ6cAoGHt6T/FqNMB5OqFOGxU/y1GyFSHquox1sK2OqApQc0ANxiHFQEMNDLNVCL7AUDnQ==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz", + "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==", "cpu": [ "ia32" ], @@ -1547,9 +1158,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.15.0.tgz", - "integrity": "sha512-YA0hTwCunmKNeTOFWdJuKhdXse9jBqgo34FDo+9aS0spfCkp+wj0o1bCcOOTu+0P48O95GTfkLTAaVonwNuIdQ==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz", + "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==", "cpu": [ "x64" ], @@ -1606,21 +1217,17 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.79", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz", "integrity": "sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==", + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1635,249 +1242,36 @@ "@types/react": "*" } }, - "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", - "dependencies": { - "@types/react": "*" - } + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, - "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/@types/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz", - "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==", + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.7.0", - "@typescript-eslint/type-utils": "7.7.0", - "@typescript-eslint/utils": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz", - "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.7.0", - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/typescript-estree": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz", - "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz", - "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "7.7.0", - "@typescript-eslint/utils": "7.7.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz", - "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz", - "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz", - "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.15", - "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.7.0", - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/typescript-estree": "7.7.0", - "semver": "^7.6.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz", - "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "7.7.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0" } }, - "node_modules/@vkruglikov/react-telegram-web-app": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vkruglikov/react-telegram-web-app/-/react-telegram-web-app-2.1.9.tgz", - "integrity": "sha512-m8D5oUuSQN1z8xiG3ufh7tBbWpmp4zLtZv2rNsC2LP0Rp5vJI/hwZNk65h7wLDE740wPlE2R6bOSikm6FdUTtQ==", - "peerDependencies": { - "react": "^18", - "react-dom": "^18" - } - }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1942,13 +1336,143 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", + "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/asynckit": { @@ -1956,6 +1480,21 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", @@ -1972,43 +1511,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/bootstrap": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", - "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "peerDependencies": { - "@popperjs/core": "^2.11.6" - } - }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/browserslist": { @@ -2043,6 +1553,25 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2052,18 +1581,10 @@ "node": ">=6" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001611", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001611.tgz", - "integrity": "sha512-19NuN1/3PjA3QI8Eki55N8my4LzfkMCRLgCVfrl/slbSAchQfV0+GwjPrK3rq37As4UCLlM/DHajbKkAqbv92Q==", + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", "dev": true, "funding": [ { @@ -2094,14 +1615,6 @@ "node": ">=4" } }, - "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2154,70 +1667,126 @@ "node": ">= 8" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, "dependencies": { - "ms": "2.1.2" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=6.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, "engines": { - "node": ">=0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", "dev": true, "dependencies": { - "path-type": "^4.0.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" } }, "node_modules/doctrine": { @@ -2232,20 +1801,169 @@ "node": ">=6.0.0" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "node_modules/electron-to-chromium": { + "version": "1.4.747", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", + "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/electron-to-chromium": { - "version": "1.4.745", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz", - "integrity": "sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==", - "dev": true + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", + "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/esbuild": { "version": "0.20.2", @@ -2358,6 +2076,38 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-react": { + "version": "7.34.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", + "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.17", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.10" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", @@ -2379,6 +2129,18 @@ "eslint": ">=7" } }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -2422,16 +2184,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2502,18 +2254,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2591,34 +2331,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2652,18 +2364,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2719,6 +2419,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2752,6 +2461,42 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2761,6 +2506,42 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2793,160 +2574,544 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.4.tgz", + "integrity": "sha512-cuBuGK40P/sk5IzWa9QPUaAdvPHjkk1c+xYsd9oZw+YQQEV+10G0P5uMpGctZZKnyQ+ibRO08bD25nWLmYi2pw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { - "node": ">=0.8.19" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "call-bind": "^1.0.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -2954,6 +3119,19 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3013,6 +3191,21 @@ "node": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3076,28 +3269,6 @@ "yallist": "^3.0.2" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3118,18 +3289,15 @@ } }, "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/ms": { @@ -3142,6 +3310,7 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, "funding": [ { "type": "github", @@ -3171,10 +3340,113 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.hasown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3270,30 +3542,25 @@ "node": ">=8" } }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "dev": true, "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 0.4" } }, "node_modules/postcss": { @@ -3324,11 +3591,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3342,22 +3604,13 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/property-expr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3416,9 +3669,36 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/react-redux": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.1.tgz", + "integrity": "sha512-5ynfGDzxxsoV73+4czQM56qF43vsmgJsO22rmAvU5tZT2z5Xow/A2uhhxwXuGTxgdReF3zcp7A80gma2onRs1A==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } }, "node_modules/react-refresh": { "version": "0.14.0", @@ -3430,55 +3710,109 @@ } }, "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.0.tgz", + "integrity": "sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==", + "dependencies": { + "@remix-run/router": "1.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz", + "integrity": "sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==", "dependencies": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.16.0", + "react-router": "6.23.0" }, "engines": { "node": ">=14.0.0" }, - "peerDependencies": { - "react": ">=16.8" + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3514,9 +3848,9 @@ } }, "node_modules/rollup": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.15.0.tgz", - "integrity": "sha512-i0ir57IMF5o7YvNYyUNeIGG+IZaaucnGZAOsSctO2tPLXlCEaZzyBa+QhpHNSgtpyLMoDev2DyN6a7J1dQA8Tw==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz", + "integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -3529,22 +3863,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.15.0", - "@rollup/rollup-android-arm64": "4.15.0", - "@rollup/rollup-darwin-arm64": "4.15.0", - "@rollup/rollup-darwin-x64": "4.15.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.15.0", - "@rollup/rollup-linux-arm-musleabihf": "4.15.0", - "@rollup/rollup-linux-arm64-gnu": "4.15.0", - "@rollup/rollup-linux-arm64-musl": "4.15.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.15.0", - "@rollup/rollup-linux-riscv64-gnu": "4.15.0", - "@rollup/rollup-linux-s390x-gnu": "4.15.0", - "@rollup/rollup-linux-x64-gnu": "4.15.0", - "@rollup/rollup-linux-x64-musl": "4.15.0", - "@rollup/rollup-win32-arm64-msvc": "4.15.0", - "@rollup/rollup-win32-ia32-msvc": "4.15.0", - "@rollup/rollup-win32-x64-msvc": "4.15.0", + "@rollup/rollup-android-arm-eabi": "4.16.4", + "@rollup/rollup-android-arm64": "4.16.4", + "@rollup/rollup-darwin-arm64": "4.16.4", + "@rollup/rollup-darwin-x64": "4.16.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.16.4", + "@rollup/rollup-linux-arm-musleabihf": "4.16.4", + "@rollup/rollup-linux-arm64-gnu": "4.16.4", + "@rollup/rollup-linux-arm64-musl": "4.16.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.16.4", + "@rollup/rollup-linux-riscv64-gnu": "4.16.4", + "@rollup/rollup-linux-s390x-gnu": "4.16.4", + "@rollup/rollup-linux-x64-gnu": "4.16.4", + "@rollup/rollup-linux-x64-musl": "4.16.4", + "@rollup/rollup-win32-arm64-msvc": "4.16.4", + "@rollup/rollup-win32-ia32-msvc": "4.16.4", + "@rollup/rollup-win32-x64-msvc": "4.16.4", "fsevents": "~2.3.2" } }, @@ -3571,6 +3905,41 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -3580,43 +3949,46 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "dependencies": { - "yallist": "^4.0.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3638,115 +4010,131 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/styled-components": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", - "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, "dependencies": { - "@emotion/is-prop-valid": "1.2.1", - "@emotion/unitless": "0.8.0", - "@types/stylis": "4.2.0", - "css-to-react-native": "3.2.0", - "csstype": "3.1.2", - "postcss": "8.4.31", - "shallowequal": "1.1.0", - "stylis": "4.3.1", - "tslib": "2.5.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 16" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/styled-components/node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" - }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" } }, - "node_modules/styled-components/node_modules/stylis": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", - "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" - }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/supports-color": { "version": "5.5.0", @@ -3760,17 +4148,24 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/tiny-case": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -3780,40 +4175,6 @@ "node": ">=4" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" - }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3838,17 +4199,92 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=14.17" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/update-browserslist-db": { @@ -3890,10 +4326,18 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.1.tgz", + "integrity": "sha512-6MCBDr76UJmRpbF8pzP27uIoTocf3tITaMJ52mccgAhMJycuh5A/RL6mDZCTwTisj0Qfeq69FtjMCUX27U78oA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { - "version": "5.2.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz", - "integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==", + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", + "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", "dev": true, "dependencies": { "esbuild": "^0.20.1", @@ -3960,6 +4404,85 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3983,28 +4506,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/yup": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", - "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", - "dependencies": { - "property-expr": "^2.0.5", - "tiny-case": "^1.0.3", - "toposort": "^2.0.2", - "type-fest": "^2.19.0" - } - }, - "node_modules/yup/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 4b1408a..e2cb94d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,32 +5,26 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, "dependencies": { - "@mui/icons-material": "^5.15.15", - "@mui/material": "^5.15.15", - "@vkruglikov/react-telegram-web-app": "^2.1.9", + "@reduxjs/toolkit": "^2.2.3", "axios": "^1.6.8", - "bootstrap": "^5.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.22.3", - "styled-components": "^6.1.8", - "yup": "^1.4.0" + "react-redux": "^9.1.1", + "react-router-dom": "^6.23.0" }, "devDependencies": { "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", - "typescript": "^5.2.2", "vite": "^5.2.0" } } diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..e7bc551 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,20 @@ +import Header from "./components/Header/Header.jsx"; +import {Route, Routes} from "react-router-dom"; +import LoginForm from "./components/LoginForm/LoginForm.jsx"; +import React from "react"; + +function App() { + + return ( +
+
+
+ + }/> + +
+
+ ); +} + +export default App; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index 0f96094..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import clsx from "clsx"; -import {useShowPopup} from "@vkruglikov/react-telegram-web-app"; - -export interface AppProps { - className?: string; -} - -export const App: React.FC = ({className}) => { - const showPopup = useShowPopup(); - - const showPopupOnClick = async () => { - const message = - "Thanks for using react-mini-app! I hope it helps you to create awesome Telegram Mini apps!"; - await showPopup({title: "Hey!", message: message}); - }; - - return ( -
-
- -

react-mini-app

-
- - -
-
- ); -}; - -export default App; \ No newline at end of file diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/Button/Button.css b/frontend/src/components/Button/Button.css new file mode 100644 index 0000000..29a3096 --- /dev/null +++ b/frontend/src/components/Button/Button.css @@ -0,0 +1,9 @@ +/*noinspection CssUnresolvedCustomProperty*/ +.button { + padding: 10px 15px; + background: var(--tg-theme-button-color); + color: var(--tg-theme-button-text-color); + border: none; + outline: none; + cursor: pointer; +} \ No newline at end of file diff --git a/frontend/src/components/Button/Button.jsx b/frontend/src/components/Button/Button.jsx new file mode 100644 index 0000000..c6b2e7c --- /dev/null +++ b/frontend/src/components/Button/Button.jsx @@ -0,0 +1,10 @@ +import './Button.css' + +const Button = (props) => { + return ( + // eslint-disable-next-line react/prop-types + + + {user?.username} + + + ) +} + +export default Header; \ No newline at end of file diff --git a/frontend/src/components/LoginForm/LoginForm.css b/frontend/src/components/LoginForm/LoginForm.css new file mode 100644 index 0000000..cc5ef75 --- /dev/null +++ b/frontend/src/components/LoginForm/LoginForm.css @@ -0,0 +1,49 @@ +.login-form { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: 0 20px; + box-sizing: border-box; + margin: 0 auto; +} + +.login-form form { + display: flex; + flex-direction: column; + align-items: center; + background-color: #000000; + padding: 20px; + border-radius: 5px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); +} + +.login-form label { + margin-bottom: 5px; + font-weight: bold; +} + +.login-form input { + margin-bottom: 15px; + padding: 5px; + border: 1px solid #ccc; + border-radius: 3px; +} + +.login-form button { + padding: 10px 20px; + background-color: #007BFF; + color: white; + border: none; + border-radius: 3px; + cursor: pointer; + transition: background-color 0.3s; +} + +.login-form button:hover { + background-color: #0056b3; +} + +.login-form__label { + margin-right: 20px; +} diff --git a/frontend/src/components/LoginForm/LoginForm.jsx b/frontend/src/components/LoginForm/LoginForm.jsx new file mode 100644 index 0000000..c33778e --- /dev/null +++ b/frontend/src/components/LoginForm/LoginForm.jsx @@ -0,0 +1,44 @@ +import "./LoginForm.css" +import React from "react"; +import {appJSON} from "../../configs/AxiosConfig"; +const LoginForm = () => { + + const [data, setData] = React.useState({login: "", password: ""}); + + const handleData = (e, name) => { + setData({...data, [name]: e.target.value}); + } + + async function sendData() { + + try { + const response = appJSON.post("http://127.0.0.1:8080/api/v1/auth/login/", data); + + if (response.status === 200) { + console.log("SUCCESS"); + } + + return response.data; + } catch { + console.error("LOH"); + } + } + + return ( +
+
+
+ + handleData(e, "login")}/> +
+
+ + handleData(e, "password")}/> +
+ +
+
+ ) +} + +export default LoginForm; diff --git a/frontend/src/configs/AxiosConfig.js b/frontend/src/configs/AxiosConfig.js new file mode 100644 index 0000000..f186c91 --- /dev/null +++ b/frontend/src/configs/AxiosConfig.js @@ -0,0 +1,48 @@ +import axios from "axios"; + +axios.defaults.withCredentials = true; + +// eslint-disable-next-line no-undef +const app = axios.create({ + withCredentials: true, + headers: { + "accept": "application/json", + "Content-Type": "application/x-www-form-urlencoded", + } +}); + +export const appJSON = axios.create({ + withCredentials: true, + headers: { + "accept": "application/json", + "Content-Type": "application/json", + } +}); + +export const appFiles = axios.create({ + withCredentials: true, + headers: { + "accept": "application/json", + "Content-Type": "multipart/form-data" + } +}); + + +app.interceptors.request.use((config) => { + return config; +}); + +/* + The below is required if you want your API to return + server message errors. Otherwise, you'll just get + generic status errors. + + If you use the interceptor below, then make sure you + return an "err" (or whatever you decide to name it) message + from your express route: + + res.status(404).json({ err: "You are not authorized to do that." }) + +*/ + +export default app; \ No newline at end of file diff --git a/frontend/src/hooks/UseTelegram.js b/frontend/src/hooks/UseTelegram.js new file mode 100644 index 0000000..226d724 --- /dev/null +++ b/frontend/src/hooks/UseTelegram.js @@ -0,0 +1,22 @@ +export function useTelegram() { + const tg = window.Telegram.WebApp; + const onClose = () => { + tg.close(); + }; + + const onToggleButton = () => { + if (tg.MainButton.isVisible) { + tg.MainButton.hide(); + } else { + tg.MainButton.show(); + } + }; + + return { + onClose, + onToggleButton, + tg, + user: tg.initDataUnsafe?.user, + }; + +} \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..be3d26c --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,93 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 100vw; + min-height: 100vh; + width: 100%; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} + +button:hover { + border-color: #646cff; +} + +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } +} + +.login { + display: flex; + flex-direction: column; + align-items: center; + margin: 0 auto; + gap: 280px; +} + +.App { + min-width: 100vw; + min-height: 100vh; +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..c4414ad --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,13 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' +import './index.css' +import {BrowserRouter} from "react-router-dom" + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + + , +); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx deleted file mode 100644 index aded3e3..0000000 --- a/frontend/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import 'bootstrap/dist/css/bootstrap.css' - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/frontend/src/style.ts b/frontend/src/style.ts deleted file mode 100644 index 1b79272..0000000 --- a/frontend/src/style.ts +++ /dev/null @@ -1,58 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; - padding: 0 20px; - background: radial-gradient(#9933ff, #6600cc); -`; - -export const Form = styled.form` - display: flex; - flex-direction: column; - width: 100%; - max-width: 400px; - background-color: white; - padding: 30px; - border-radius: 8px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); -`; - -export const Label = styled.label` - font-size: 16px; - font-weight: 500; - margin-bottom: 8px; -`; - -export const Input = styled.input` - font-size: 16px; - padding: 8px; - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 16px; - width: 100%; - - &:focus { - outline: none; - border-color: #9933ff; - } -`; - -export const SubmitButton = styled.button` - font-size: 16px; - font-weight: 500; - padding: 8px 16px; - border: none; - border-radius: 4px; - background-color: #9933ff; - color: white; - cursor: pointer; - transition: background-color 0.3s; - - &:hover { - background-color: #8022ee; - } -`; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/frontend/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json deleted file mode 100644 index a7fc6fb..0000000 --- a/frontend/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json deleted file mode 100644 index 97ede7e..0000000 --- a/frontend/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true - }, - "include": ["vite.config.ts"] -} diff --git a/frontend/vite.config.ts b/frontend/vite.config.js similarity index 100% rename from frontend/vite.config.ts rename to frontend/vite.config.js From e3c5764db3ec48a861688afae74c143d43f27f8d Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 19:10:42 +0300 Subject: [PATCH 054/148] =?UTF-8?q?=F0=9F=93=9D=20remove=20unused=20env=20?= =?UTF-8?q?var=20from=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DEVELOPMENT.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index f343844..a5947e8 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -25,17 +25,6 @@ Afterward, fill it with the required data. | IP | str | True | ip for other services | | TIMEZONE | str | True | your time zone for working with the scheduler | | MODERATE_CHAT | str | True | telegram chat where the event will be moderated | -| POSTGRES_USER | str | True | username of the database owner | -| POSTGRES_PASSWORD | str | True | password from the database | -| DB_HOST | str | True | IP address of the database (Name of the service in the docker-compose.yml (User `db`)). | -| DB_PORT | str | True | the database port. Usually the db running on port `5432` | -| POSTGRES_DB | str | True | database name | -| SECRET_KEY | str | True | secret key for django | -| API_KEY | str | True | yandex api key for yandex map | -| QIWI_KEY | str | True | qiwi api key for receiving payments | -| PHONE_NUMBER | str | True | your phone number (need for qiwi) | -| SECRET_P2 | str | True | public p2 key which allows you to issue an invoice and open a transfer form | -| USE_REDIS | bool | False | Optional parameter | Once done, run the following command: From 0265eedd56457a10ab55858fed1f11be1a18c7ff Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 19:11:47 +0300 Subject: [PATCH 055/148] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20add=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/NudeNet/predictor.py | 3 ++- src/infrastructure/YandexMap/api.py | 9 ++++---- src/tgbot/filters/filters_chat.py | 2 +- src/tgbot/misc/async_obj.py | 7 +++--- src/tgbot/misc/security.py | 4 +++- src/tgbot/services/app/user_operations.py | 27 +++++++++++++++++------ 6 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/infrastructure/NudeNet/predictor.py b/src/infrastructure/NudeNet/predictor.py index 1ef7731..8e21409 100644 --- a/src/infrastructure/NudeNet/predictor.py +++ b/src/infrastructure/NudeNet/predictor.py @@ -1,4 +1,5 @@ import pathlib +from typing import Any from nudenet import ( NudeDetector, @@ -7,7 +8,7 @@ detector = NudeDetector() -async def classification_image(image_path: str | pathlib.Path) -> list[dict]: +async def classification_image(image_path: str | pathlib.Path) -> list[dict[str, Any]]: return detector.detect(image_path=image_path) diff --git a/src/infrastructure/YandexMap/api.py b/src/infrastructure/YandexMap/api.py index a9c9d0f..b25357a 100644 --- a/src/infrastructure/YandexMap/api.py +++ b/src/infrastructure/YandexMap/api.py @@ -1,6 +1,5 @@ from typing import ( Any, - Tuple, ) import aiohttp @@ -16,10 +15,10 @@ class YaClient: __slots__ = ("api_key",) api_key: str - def __init__(self, api_key: str): + def __init__(self, api_key: str) -> None: self.api_key = api_key - async def _request(self, address: str) -> Any: + async def _request(self, address: str) -> dict[str, Any] | None: async with aiohttp.ClientSession() as session: async with session.get( url="https://geocode-maps.yandex.ru/1.x/", @@ -35,7 +34,7 @@ async def _request(self, address: str) -> Any: f"status_code={response.status}, body={response.content}" ) - async def coordinates(self, address: str) -> Tuple: + async def coordinates(self, address: str) -> tuple: d = await self._request(address) data = d["GeoObjectCollection"]["featureMember"] @@ -46,7 +45,7 @@ async def coordinates(self, address: str) -> Tuple: longitude, latitude = tuple(coordinates.split(" ")) return longitude, latitude - async def address(self, longitude, latitude) -> Any: + async def address(self, longitude, latitude) -> dict[str, Any] | None: response = await self._request(f"{longitude},{latitude}") data = response.get("GeoObjectCollection", {}).get("featureMember", []) diff --git a/src/tgbot/filters/filters_chat.py b/src/tgbot/filters/filters_chat.py index 1f4b893..8b32593 100644 --- a/src/tgbot/filters/filters_chat.py +++ b/src/tgbot/filters/filters_chat.py @@ -7,7 +7,7 @@ class ChatTypeFilter(BaseFilter): - def __init__(self, chat_type: str | list) -> None: + def __init__(self, chat_type: str | list[str]) -> None: self.chat_type = chat_type async def __call__(self, message: Message) -> bool: diff --git a/src/tgbot/misc/async_obj.py b/src/tgbot/misc/async_obj.py index 8b32eab..c804f64 100644 --- a/src/tgbot/misc/async_obj.py +++ b/src/tgbot/misc/async_obj.py @@ -1,11 +1,12 @@ import asyncio from typing import ( - NoReturn, Coroutine, Any, Generator, + Any, + Generator, ) class AsyncObj: - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: """ Init. @@ -15,7 +16,7 @@ def __init__(self, *args, **kwargs) -> None: self.__stored_args = args, kwargs self.async_initialized = False - async def __ainit__(self, *args, **kwargs) -> None: + async def __ainit__(self, *args: Any, **kwargs: Any) -> None: """Async constructor, you should implement this.""" async def __initobj(self) -> "AsyncObj": diff --git a/src/tgbot/misc/security.py b/src/tgbot/misc/security.py index f67ccaa..6b406f1 100644 --- a/src/tgbot/misc/security.py +++ b/src/tgbot/misc/security.py @@ -2,7 +2,9 @@ import hmac import secrets import time -from typing import Any +from typing import ( + Any, +) def generate_signature(telegram_id: int, secret_key: str) -> dict[str, Any]: diff --git a/src/tgbot/services/app/user_operations.py b/src/tgbot/services/app/user_operations.py index 7df5747..32535c6 100644 --- a/src/tgbot/services/app/user_operations.py +++ b/src/tgbot/services/app/user_operations.py @@ -1,4 +1,6 @@ +import http from typing import ( + Any, Literal, ) @@ -34,31 +36,42 @@ def welcoming_message(username: str, message_type: Literal["welcome", "greet_aut return messages[message_type] -async def get_user_data(client: QueClient, storage: dict): +async def get_user_data(client: QueClient, storage: dict) -> tuple[http.HTTPStatus, dict[str, Any]]: access_token = storage.get("access_token") status_code, response = await client.get_user_me(access_token) return status_code, response -async def handle_send_start_message(username: str, message: types.Message): +async def handle_send_start_message(username: str, message: types.Message) -> None: await message.answer( text=welcoming_message(username=username, message_type="greet_auth_user"), reply_markup=reply.main_menu() ) -async def handle_login_t_me(client: QueClient, config: Config, message: types.Message, state: FSMContext): +async def handle_login_t_me( + client: QueClient, + config: Config, + message: types.Message, + state: FSMContext, +) -> tuple[http.HTTPStatus, dict[str, Any]] | None: auth_data = security.generate_signature(telegram_id=message.from_user.id, secret_key=config.misc.secret_key) status_code, response = await client.login_t_me(data_in=TMELoginSchema(**auth_data)) - access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') + if status_code == http.HTTPStatus.OK: + access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') - await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) + await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) return status_code, response -async def handle_signup(client: QueClient, message: types.Message, state: FSMContext, config: Config): +async def handle_signup( + client: QueClient, + message: types.Message, + state: FSMContext, + config: Config +) -> tuple[http.HTTPStatus, dict[str, Any]]: username = message.from_user.username status_code, response = await client.signup( data_in=SignUpSchema( @@ -76,7 +89,7 @@ async def handle_signup(client: QueClient, message: types.Message, state: FSMCon return status_code, response -async def handle_not_founded_user(message: types.Message): +async def handle_not_founded_user(message: types.Message) -> None: await message.answer( text="Мы не смогли найти ваш в аккаунт. Создайте новый или войдите с помощью логина и пароля", reply_markup=reply.login_signup_menu() From deba550bcf53d50ce5e663808cdf5e5e9c5fe0e7 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 19:14:05 +0300 Subject: [PATCH 056/148] =?UTF-8?q?=F0=9F=92=84=20add=20web=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/reply.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index 6a0ddcb..8d33510 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -28,6 +28,6 @@ def login_signup_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() builder.row( types.KeyboardButton(text="✏️ Создать аккаунт"), - types.KeyboardButton(text="🛂 Войти в аккаунт", web_app=WebAppInfo(url="https://light-clouds-sleep.loca.lt")), + types.KeyboardButton(text="🛂 Войти в аккаунт", web_app=WebAppInfo(url="")), ) return builder.as_markup(resize_keyboard=True) From d2d39b7bf4e555cec5662fe294b790b1db94733b Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 19:14:39 +0300 Subject: [PATCH 057/148] =?UTF-8?q?=E2=9C=A8=20add=20about=20project=20han?= =?UTF-8?q?dler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/start.py | 15 ++++++++++++++- src/tgbot/keyboards/inline.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index d1eab9a..7821dd2 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -1,5 +1,7 @@ import http -from typing import Any +from typing import ( + Any, +) from aiogram import ( F, @@ -19,6 +21,9 @@ from src.tgbot.config import ( Config, ) +from src.tgbot.keyboards import ( + inline, +) from src.tgbot.services import ( get_user_data, handle_login_t_me, @@ -53,3 +58,11 @@ async def signup_handler(message: types.Message, state: FSMContext, **middleware client: QueClient = middleware_data.get("que-client") config: Config = middleware_data.get("config") await handle_signup(client=client, message=message, state=state, config=config) + + +@start_router.message(F.text == "❔ О проекте") +async def about_project_handler(message: types.Message) -> None: + text = ( + "Наша система полностью open-source" + ) + await message.answer(text=text, reply_markup=inline.about_project_menu()) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index e69de29..a6dfd92 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -0,0 +1,17 @@ +from aiogram import ( + types, +) +from aiogram.types import ( + WebAppInfo, +) +from aiogram.utils.keyboard import ( + InlineKeyboardBuilder, +) + + +def about_project_menu() -> types.InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.row( + types.InlineKeyboardButton(text="Github", web_app=WebAppInfo(url="https://github.com/QueGroup")) + ) + return builder.as_markup() From 10b315b98e44891ff2573447d046db4c632027eb Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 26 Apr 2024 19:17:51 +0300 Subject: [PATCH 058/148] =?UTF-8?q?=F0=9F=9A=A7=20user=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/user.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/tgbot/handlers/user.py diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py new file mode 100644 index 0000000..a8538db --- /dev/null +++ b/src/tgbot/handlers/user.py @@ -0,0 +1,8 @@ +from aiogram import Router, F, types + +user_router = Router() + + +@user_router.message(F.text == "👤 Аккаунт") +async def user_handler(message: types.Message): + await message.answer(text="Информация о пользователе") From a7f52d1a39c7f185c5f2dded765122f74891f9e5 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:47:12 +0300 Subject: [PATCH 059/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20whitespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/LoginForm/LoginForm.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/LoginForm/LoginForm.jsx b/frontend/src/components/LoginForm/LoginForm.jsx index c33778e..913ddd9 100644 --- a/frontend/src/components/LoginForm/LoginForm.jsx +++ b/frontend/src/components/LoginForm/LoginForm.jsx @@ -1,6 +1,7 @@ import "./LoginForm.css" import React from "react"; import {appJSON} from "../../configs/AxiosConfig"; + const LoginForm = () => { const [data, setData] = React.useState({login: "", password: ""}); @@ -12,10 +13,10 @@ const LoginForm = () => { async function sendData() { try { - const response = appJSON.post("http://127.0.0.1:8080/api/v1/auth/login/", data); + const response = await appJSON.post("http://127.0.0.1:8080/api/v1/auth/login/", data); if (response.status === 200) { - console.log("SUCCESS"); + console.log("SUCCESS"); } return response.data; @@ -29,11 +30,13 @@ const LoginForm = () => {
- handleData(e, "login")}/> + handleData(e, "login")}/>
- handleData(e, "password")}/> + handleData(e, "password")}/>
From 7b2d25c3057af7a22df18cd2237c4dc35c706c0e Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:51:59 +0300 Subject: [PATCH 060/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20whitespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dist | 2 +- .gitignore | 2 +- Dockerfile | 1 - babel.cfg | 2 +- bot.conf | 2 +- src/tgbot/services/dating/__init__.py | 1 - 6 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.env.dist b/.env.dist index 3887437..af7f594 100644 --- a/.env.dist +++ b/.env.dist @@ -6,4 +6,4 @@ TIMEZONE= MODERATE_CHAT= SIGNATURE_SECRET_KEY= -USE_REDIS= \ No newline at end of file +USE_REDIS= diff --git a/.gitignore b/.gitignore index 7aa9e8c..5efaa84 100644 --- a/.gitignore +++ b/.gitignore @@ -133,4 +133,4 @@ dmypy.json # Django Settings with Secret key /django_project/django_project/settings.py -photos/ \ No newline at end of file +photos/ diff --git a/Dockerfile b/Dockerfile index 0a4cdde..6db380f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,3 @@ WORKDIR /src COPY requirements.txt /src RUN pip install -r /src/requirements.txt COPY . /src - diff --git a/babel.cfg b/babel.cfg index 6e0b734..ec17d42 100644 --- a/babel.cfg +++ b/babel.cfg @@ -4,4 +4,4 @@ [python: src/tgbot/keyboards/**.py] [python: src/tgbot/middlewares/**.py] [python: src/tgbot/utils/**.py] -encoding = utf-8 \ No newline at end of file +encoding = utf-8 diff --git a/bot.conf b/bot.conf index 931814b..ac1562f 100644 --- a/bot.conf +++ b/bot.conf @@ -17,4 +17,4 @@ autorestart=true autorestart=true environment=HOME="/home/ubuntu",USER="ubuntu" stderr_logfile=/home/ubuntu/DatingBot/logfile_err_django.log -stdout_logfile=/home/ubuntu/DatingBot/logfile_django.log \ No newline at end of file +stdout_logfile=/home/ubuntu/DatingBot/logfile_django.log diff --git a/src/tgbot/services/dating/__init__.py b/src/tgbot/services/dating/__init__.py index 8b13789..e69de29 100644 --- a/src/tgbot/services/dating/__init__.py +++ b/src/tgbot/services/dating/__init__.py @@ -1 +0,0 @@ - From 768cc23949c205462b4a4e7846dd8e906f06ec41 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:52:34 +0300 Subject: [PATCH 061/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 251 ++++++++++++++++++++++++++-------------------------- 1 file changed, 126 insertions(+), 125 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2d69c9e..b7458eb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1153,38 +1153,38 @@ files = [ [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [package.dependencies] @@ -1460,18 +1460,19 @@ testing = ["aboutcode-toolkit (>=6.0.0)", "black", "pytest (>=6,!=7.0.0)", "pyte [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" @@ -1553,18 +1554,18 @@ files = [ [[package]] name = "pydantic" -version = "2.7.0" +version = "2.7.1" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"}, - {file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.1" +pydantic-core = "2.18.2" typing-extensions = ">=4.6.1" [package.extras] @@ -1572,90 +1573,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.1" +version = "2.18.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"}, - {file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"}, - {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"}, - {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"}, - {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"}, - {file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"}, - {file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"}, - {file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"}, - {file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"}, - {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"}, - {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"}, - {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"}, - {file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"}, - {file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"}, - {file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"}, - {file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"}, - {file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"}, - {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"}, - {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"}, - {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"}, - {file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"}, - {file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"}, - {file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"}, - {file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"}, - {file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"}, - {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"}, - {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"}, - {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"}, - {file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"}, - {file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"}, - {file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"}, - {file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"}, - {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"}, - {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"}, - {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"}, - {file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"}, - {file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"}, - {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"}, - {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"}, - {file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, ] [package.dependencies] @@ -1818,13 +1819,13 @@ files = [ [[package]] name = "que-sdk" -version = "0.1.2" +version = "0.1.3" description = "" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "que_sdk-0.1.2-py3-none-any.whl", hash = "sha256:8a977239e46607e2462eee7ea97e404348e41be9b2ee73e9a2955456eaae10a9"}, - {file = "que_sdk-0.1.2.tar.gz", hash = "sha256:0ab09bf4d3af24ffdd693361569cd83346c751ed2d8b3c7093e9f7506c726d39"}, + {file = "que_sdk-0.1.3-py3-none-any.whl", hash = "sha256:d5070e80f66ce07cf426e1884e935bec97df86c2637301d4048b18acb43f8971"}, + {file = "que_sdk-0.1.3.tar.gz", hash = "sha256:9729df92a341f565a91a6c468a5e78804608ee2848c1faf81a3e493c72803d52"}, ] [package.dependencies] @@ -1833,13 +1834,13 @@ httpx = ">=0.27.0,<0.28.0" [[package]] name = "redis" -version = "5.0.3" +version = "5.0.4" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, - {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, + {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, + {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, ] [package.dependencies] @@ -2046,13 +2047,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.3" +version = "20.26.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, - {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, + {file = "virtualenv-20.26.0-py3-none-any.whl", hash = "sha256:0846377ea76e818daaa3e00a4365c018bc3ac9760cbb3544de542885aad61fb3"}, + {file = "virtualenv-20.26.0.tar.gz", hash = "sha256:ec25a9671a5102c8d2657f62792a27b48f016664c6873f6beed3800008577210"}, ] [package.dependencies] From 85df6761030cfe25e2f394c46e4247aa91f8d158 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:52:49 +0300 Subject: [PATCH 062/148] =?UTF-8?q?=F0=9F=94=A7=20add=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 34 ++++++++++++++++++++++++++++++++++ justfile | 8 ++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..542899a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +exclude: 'docs|frontend|deprecated|README.md' +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-yaml + - id: check-toml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: [ --py38-plus ] + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.1.8" + hooks: + - id: ruff + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.17 + hooks: + - id: mdformat + additional_dependencies: + - mdformat-gfm + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + args: [ --follow-imports=silent, --disable-error-code=no-untyped-call, --explicit-package-bases ] + exclude: tests/ diff --git a/justfile b/justfile index b419de4..0ecdc95 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -set shell := ["powershell.exe", "-c"] +# set shell := ["powershell.exe", "-c"] # Show help message PHONY: help @@ -33,4 +33,8 @@ black: # Audit packages audit: @echo "🔍 Auditing packages..." - @pip-audit . \ No newline at end of file + @pip-audit . + +# Run pre-commit lint +lint: + @pre-commit run --all-files From 3aab80b144934d74c078eace3173b54c03c64847 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:53:29 +0300 Subject: [PATCH 063/148] =?UTF-8?q?=F0=9F=9A=A8=20ignore=20f403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tgbot/services/__init__.py b/src/tgbot/services/__init__.py index 1afb6e3..23e40fa 100644 --- a/src/tgbot/services/__init__.py +++ b/src/tgbot/services/__init__.py @@ -1 +1 @@ -from .app import * +from .app import * # noqa: F403 From af16616741684199fc698a3d525a9aee8153584d Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:54:04 +0300 Subject: [PATCH 064/148] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20add=20typing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/NudeNet/predictor.py | 4 +++- src/infrastructure/YandexMap/api.py | 4 ++-- src/tgbot/misc/async_obj.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/infrastructure/NudeNet/predictor.py b/src/infrastructure/NudeNet/predictor.py index 8e21409..4f44a57 100644 --- a/src/infrastructure/NudeNet/predictor.py +++ b/src/infrastructure/NudeNet/predictor.py @@ -1,5 +1,7 @@ import pathlib -from typing import Any +from typing import ( + Any, +) from nudenet import ( NudeDetector, diff --git a/src/infrastructure/YandexMap/api.py b/src/infrastructure/YandexMap/api.py index b25357a..0b32875 100644 --- a/src/infrastructure/YandexMap/api.py +++ b/src/infrastructure/YandexMap/api.py @@ -34,7 +34,7 @@ async def _request(self, address: str) -> dict[str, Any] | None: f"status_code={response.status}, body={response.content}" ) - async def coordinates(self, address: str) -> tuple: + async def coordinates(self, address: str) -> tuple[float, float]: d = await self._request(address) data = d["GeoObjectCollection"]["featureMember"] @@ -45,7 +45,7 @@ async def coordinates(self, address: str) -> tuple: longitude, latitude = tuple(coordinates.split(" ")) return longitude, latitude - async def address(self, longitude, latitude) -> dict[str, Any] | None: + async def address(self, longitude: float, latitude: float) -> dict[str, Any] | None: response = await self._request(f"{longitude},{latitude}") data = response.get("GeoObjectCollection", {}).get("featureMember", []) diff --git a/src/tgbot/misc/async_obj.py b/src/tgbot/misc/async_obj.py index c804f64..b9f0068 100644 --- a/src/tgbot/misc/async_obj.py +++ b/src/tgbot/misc/async_obj.py @@ -30,7 +30,7 @@ async def __initobj(self) -> "AsyncObj": def __await__(self) -> Generator[Any, None, "AsyncObj"]: return self.__initobj().__await__() - def __init_subclass__(cls, **kwargs) -> None: + def __init_subclass__(cls, **kwargs: Any) -> None: # __ainit__ must be async assert asyncio.iscoroutinefunction(cls.__ainit__) From bcd0b0ac8df81f8c30cff5067afc6616f977be12 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:55:31 +0300 Subject: [PATCH 065/148] =?UTF-8?q?=E2=9C=A8=20add=20middleware=20for=20ha?= =?UTF-8?q?ndle=20deactivated=20user=20and=20not=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/check_activate.py | 61 +++++++++++++++++++++++++ src/tgbot/middlewares/exceptions.py | 2 + src/tgbot/middlewares/is_auth.py | 57 +++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 src/tgbot/middlewares/check_activate.py create mode 100644 src/tgbot/middlewares/exceptions.py create mode 100644 src/tgbot/middlewares/is_auth.py diff --git a/src/tgbot/middlewares/check_activate.py b/src/tgbot/middlewares/check_activate.py new file mode 100644 index 0000000..e306d48 --- /dev/null +++ b/src/tgbot/middlewares/check_activate.py @@ -0,0 +1,61 @@ +import http +from typing import ( + Any, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + TelegramObject, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot.services import ( + welcoming_message, +) +from src.tgbot.types import ( + Handler, +) + +from .exceptions import ( + CancelHandler, +) + + +class CheckActivateMiddleware(BaseMiddleware): + def __init__(self, client: QueClient) -> None: + self.client = client + self.text = welcoming_message(message_type="deactivate_user") + + async def __call__( + self, + handler: Handler, + event: TelegramObject, + data: dict[str, Any] + ) -> Any: + update = data.get("event_update") + command = update.message.text + state = data.get("state") + storage = await state.get_data() + if storage and command != "/reactivate": + try: + await self.on_process_event(storage=storage) + return await handler(event, data) + except CancelHandler: + await event.answer(text=self.text) + else: + return await handler(event, data) + + async def on_process_event(self, storage: dict[str, Any]) -> Any: + status_code, response = await self.client.get_user_me( + access_token=storage.get("access_token", "") + ) + try: + code = response.get("detail").get("code") + if status_code == http.HTTPStatus.BAD_REQUEST and code == 3002: + raise CancelHandler() + except AttributeError: + pass diff --git a/src/tgbot/middlewares/exceptions.py b/src/tgbot/middlewares/exceptions.py new file mode 100644 index 0000000..6794518 --- /dev/null +++ b/src/tgbot/middlewares/exceptions.py @@ -0,0 +1,2 @@ +class CancelHandler(Exception): + pass diff --git a/src/tgbot/middlewares/is_auth.py b/src/tgbot/middlewares/is_auth.py new file mode 100644 index 0000000..4d82fb3 --- /dev/null +++ b/src/tgbot/middlewares/is_auth.py @@ -0,0 +1,57 @@ +import http +from typing import ( + Any, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + TelegramObject, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot.types import ( + Handler, +) + +from .exceptions import ( + CancelHandler, +) + + +class IsAuthMiddleware(BaseMiddleware): + def __init__(self, client: QueClient) -> None: + self.client = client + self.text = ( + "Вы не вошли в аккаунт" + ) + + async def __call__( + self, + handler: Handler, + event: TelegramObject, + data: dict[str, Any] + ) -> Any: + update = data.get("event_update") + command = update.message.text + state = data.get("state") + storage = await state.get_data() + + if command != "/start": + try: + await self.on_process_event(storage=storage) + return await handler(event, data) + except CancelHandler: + await event.answer(text=self.text) + else: + return await handler(event, data) + + async def on_process_event(self, storage: dict[str, Any]) -> Any: + status_code, response = await self.client.get_user_me( + access_token=storage.get("access_token", "") + ) + if status_code == http.HTTPStatus.UNAUTHORIZED: + raise CancelHandler() From ec26a1be8b349955c4ecf58a38b87682ba665da1 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:55:52 +0300 Subject: [PATCH 066/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/__init__.py | 10 +++++++++- src/tgbot/middlewares/check_ban.py | 3 +-- src/tgbot/middlewares/check_maintenance.py | 3 +-- src/tgbot/middlewares/config.py | 3 +-- src/tgbot/middlewares/scheduler.py | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index c025fb4..44f466d 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -17,11 +17,17 @@ # SchedulerMiddleware, # ) # from .Throttling import ( -# ThrottlingMiddleware, +# ThrottlingMiddleware, # TODO: https://ru.stackoverflow.com/questions/1540655/ # ) +from .check_activate import ( + CheckActivateMiddleware, +) from .config import ( MiscMiddleware, ) +from .is_auth import ( + IsAuthMiddleware, +) __all__ = ( # "AgentSupport", @@ -34,4 +40,6 @@ # "SupportMiddleware", # "LinkCheckMiddleware", "MiscMiddleware", + "CheckActivateMiddleware", + "IsAuthMiddleware", ) diff --git a/src/tgbot/middlewares/check_ban.py b/src/tgbot/middlewares/check_ban.py index 3e7d2bc..baca88e 100644 --- a/src/tgbot/middlewares/check_ban.py +++ b/src/tgbot/middlewares/check_ban.py @@ -1,6 +1,5 @@ from typing import ( Any, - Dict, ) from aiogram import ( @@ -20,7 +19,7 @@ async def __call__( self, handler: Handler, event: TelegramObject, - data: Dict[str, Any] + data: dict[str, Any] ) -> Any: pass diff --git a/src/tgbot/middlewares/check_maintenance.py b/src/tgbot/middlewares/check_maintenance.py index 672f296..7597fb7 100644 --- a/src/tgbot/middlewares/check_maintenance.py +++ b/src/tgbot/middlewares/check_maintenance.py @@ -1,6 +1,5 @@ from typing import ( Any, - Dict, ) from aiogram import ( @@ -20,7 +19,7 @@ async def __call__( self, handler: Handler, event: TelegramObject, - data: Dict[str, Any] + data: dict[str, Any] ) -> Any: pass diff --git a/src/tgbot/middlewares/config.py b/src/tgbot/middlewares/config.py index 04992ed..aa20e9c 100644 --- a/src/tgbot/middlewares/config.py +++ b/src/tgbot/middlewares/config.py @@ -1,6 +1,5 @@ from typing import ( Any, - Dict, ) from aiogram import ( @@ -30,7 +29,7 @@ async def __call__( self, handler: Handler, event: TelegramObject, - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: data["config"] = self.config data["que-client"] = self.client diff --git a/src/tgbot/middlewares/scheduler.py b/src/tgbot/middlewares/scheduler.py index 5f6a2c9..3fb966c 100644 --- a/src/tgbot/middlewares/scheduler.py +++ b/src/tgbot/middlewares/scheduler.py @@ -19,7 +19,7 @@ class SchedulerMiddleware(BaseMiddleware): def __init__(self, scheduler: AsyncIOScheduler): - super(SchedulerMiddleware, self).__init__() + super().__init__() self.scheduler = scheduler async def __call__( From eafefc5ba2f65bfe4f4295b126a406deecddc932 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:56:48 +0300 Subject: [PATCH 067/148] =?UTF-8?q?=F0=9F=9A=A7=20on=20handlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/__init__.py | 4 ++ src/tgbot/handlers/start.py | 16 +++++--- src/tgbot/handlers/user.py | 50 +++++++++++++++++++++-- src/tgbot/keyboards/inline.py | 17 ++++++++ src/tgbot/services/app/user_operations.py | 19 ++++++--- 5 files changed, 92 insertions(+), 14 deletions(-) diff --git a/src/tgbot/handlers/__init__.py b/src/tgbot/handlers/__init__.py index c3cddfd..a9c4dcd 100644 --- a/src/tgbot/handlers/__init__.py +++ b/src/tgbot/handlers/__init__.py @@ -1,9 +1,13 @@ from .start import ( start_router, ) +from .user import ( + user_router, +) routers_list = [ start_router, + user_router, # echo_router, # echo_router must be last ] diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 7821dd2..57b6e7d 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -29,6 +29,7 @@ handle_login_t_me, handle_not_founded_user, handle_signup, + welcoming_message, ) from src.tgbot.services.app import ( handle_send_start_message, @@ -40,17 +41,22 @@ @start_router.message(CommandStart()) async def start_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: config: Config = middleware_data.get("config") - client: QueClient = middleware_data.get("que-client") + que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() if not storage: - status_code, response = await handle_login_t_me(client, config, message, state) + status_code, response = await handle_login_t_me(que_client, config, message, state) if status_code == http.HTTPStatus.NOT_FOUND: await handle_not_founded_user(message=message) storage = await state.get_data() - status_code, response = await get_user_data(client, storage) - if status_code != http.HTTPStatus.UNAUTHORIZED: - await handle_send_start_message(message=message, username=response.get("username")) + status_code, response = await get_user_data(que_client, storage) + if status_code == http.HTTPStatus.BAD_REQUEST: + code = response.get("detail").get("code") + if code == 3002: + await message.answer(text=welcoming_message(message_type="deactivate_user")) + else: + if status_code != http.HTTPStatus.UNAUTHORIZED: + await handle_send_start_message(message=message, response=response) @start_router.message(F.text == "✏️ Создать аккаунт") diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py index a8538db..40ac048 100644 --- a/src/tgbot/handlers/user.py +++ b/src/tgbot/handlers/user.py @@ -1,8 +1,52 @@ -from aiogram import Router, F, types +from typing import ( + Any, +) + +from aiogram import ( + F, + Router, + types, +) +from aiogram.filters import ( + Command, +) +from aiogram.fsm.context import ( + FSMContext, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot.keyboards import ( + reply, +) user_router = Router() @user_router.message(F.text == "👤 Аккаунт") -async def user_handler(message: types.Message): - await message.answer(text="Информация о пользователе") +async def user_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + que_client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + status_code, response = await que_client.get_user_me(access_token=storage.get("access_token")) + days = response.get("days_since_created") + text = ( + "Username: *{username}*\n" + "TelegramID: `{telegram_id}`\n\n" + "Вы с нами {days} дней" + ).format( + username=response.get("username"), + telegram_id=response.get("telegram_id"), + days=days + ) + await message.answer(text=text) + + +@user_router.message(F.text, Command("reactivate")) +async def user_activate_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + que_client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + access_token = storage.get("access_token") + await que_client.reactivate_user(access_token=access_token) + await message.delete() + await message.answer(text="Поздравляем! Вы восстановили аккаунт", reply_markup=reply.main_menu()) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index a6dfd92..95d5b58 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -15,3 +15,20 @@ def about_project_menu() -> types.InlineKeyboardMarkup: types.InlineKeyboardButton(text="Github", web_app=WebAppInfo(url="https://github.com/QueGroup")) ) return builder.as_markup() + + +def user_menu(is_active: bool) -> types.InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.row( + types.InlineKeyboardButton(text="🔁 Изменить", callback_data="user:update"), + ) + + return builder.as_markup() + + +def user_activation_menu() -> types.InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.row( + types.InlineKeyboardButton(text="🔛 Активировать", callback_data="user:activate") + ) + return builder.as_markup() diff --git a/src/tgbot/services/app/user_operations.py b/src/tgbot/services/app/user_operations.py index 32535c6..7dc0219 100644 --- a/src/tgbot/services/app/user_operations.py +++ b/src/tgbot/services/app/user_operations.py @@ -27,23 +27,30 @@ ) -def welcoming_message(username: str, message_type: Literal["welcome", "greet_auth_user"]) -> str: +def welcoming_message(message_type: Literal["welcome", "greet_auth_user", "deactivate_user"], **kwargs: Any) -> str: messages = { - "welcome": "Добро пожаловать, {username}! Вы создали новый аккаунт".format(username=username), - "greet_auth_user": "Привет {username} вы вошли в аккаунт".format(username=username) + "welcome": "Добро пожаловать, {username}! Вы создали новый аккаунт", + "greet_auth_user": "Привет {username} вы вошли в аккаунт", + "deactivate_user": "Из-за отключения вашего аккаунта, доступ к приложению ограничен.\n" + "Для возобновления работы с нашим приложением, пожалуйста, активируйте ваш аккаунт.\n" + "Чтобы активировать аккаунт, используйте /reactivate", } - return messages[message_type] + return messages[message_type].format(**kwargs) -async def get_user_data(client: QueClient, storage: dict) -> tuple[http.HTTPStatus, dict[str, Any]]: +async def get_user_data(client: QueClient, storage: dict[str, Any]) -> tuple[http.HTTPStatus, dict[str, Any]]: access_token = storage.get("access_token") status_code, response = await client.get_user_me(access_token) return status_code, response -async def handle_send_start_message(username: str, message: types.Message) -> None: +async def handle_send_start_message( + message: types.Message, + response: dict[Any, Any] +) -> None: + username = response.get("username") if response.get("username") is not None else message.from_user.username await message.answer( text=welcoming_message(username=username, message_type="greet_auth_user"), reply_markup=reply.main_menu() From 149d2628b8a52c66756ae01bece5343346bcbb62 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 29 Apr 2024 09:56:55 +0300 Subject: [PATCH 068/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20mypy=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index d957698..437cc18 100644 --- a/bot.py +++ b/bot.py @@ -1,5 +1,8 @@ import asyncio import logging +from typing import ( + Sequence, +) from aiogram import ( Bot, @@ -31,6 +34,8 @@ routers_list, ) from src.tgbot.middlewares import ( + CheckActivateMiddleware, + IsAuthMiddleware, MiscMiddleware, ) from src.tgbot.services import ( @@ -38,8 +43,8 @@ ) -async def on_startup(bot: Bot, admin_ids: list[int]) -> None: - await broadcaster.broadcast(bot, admin_ids, "Бот запущен") +async def on_startup(bot: Bot, admin_ids: Sequence[int]) -> None: + await broadcaster.broadcast(bot, list(admin_ids), "Бот запущен") def setup_logging() -> None: @@ -76,6 +81,8 @@ def register_global_middlewares( logging.info("Setup middlewares...") middleware_types = [ MiscMiddleware(config, client), + CheckActivateMiddleware(client), + IsAuthMiddleware(client) ] for middleware_type in middleware_types: dp.message.outer_middleware(middleware_type) @@ -91,7 +98,6 @@ def get_storage(config: Config) -> MemoryStorage | RedisStorage: Returns: Storage: The storage object based on the configuration. - """ if config.tg_bot.use_redis: return RedisStorage.from_url( @@ -108,7 +114,7 @@ async def main() -> None: config = load_config() storage = get_storage(config) client = QueClient() - bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) + bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN)) dp = Dispatcher(storage=storage) dp.include_routers(*routers_list) From 7111ae6d039b29bf1ae056a2401b0621aea6a3c7 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 6 May 2024 20:59:15 +0300 Subject: [PATCH 069/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20unused=20?= =?UTF-8?q?services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 08805b2..514716a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,6 @@ version: '3.1' services: - db: - container_name: database - image: postgres:14.1-alpine - env_file: - - ".env" - restart: always - ports: - - "5432:5432" - networks: - - botnet - volumes: - - ./postgres:/var/lib/postgresql - tgbot: container_name: bot build: @@ -27,21 +14,6 @@ services: - ".env" volumes: - .:/src - depends_on: - - db - django_migration: - container_name: migrations - build: - context: . - working_dir: "/src/" - command: sh -c "python3 django_app.py makemigrations && python3 django_app.py migrate" - networks: - - botnet - restart: "no" - depends_on: - - db - volumes: - - .:/src networks: botnet: From 0276f8aa3306843029401f1da712f2eb5264af70 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 6 May 2024 20:59:41 +0300 Subject: [PATCH 070/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 55 ++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index b7458eb..7ba9701 100644 --- a/poetry.lock +++ b/poetry.lock @@ -523,19 +523,19 @@ cron = ["capturer (>=2.4)"] [[package]] name = "cyclonedx-python-lib" -version = "6.4.4" +version = "7.3.2" description = "Python library for CycloneDX" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.8" files = [ - {file = "cyclonedx_python_lib-6.4.4-py3-none-any.whl", hash = "sha256:c366619cc4effd528675f1f7a7a00be30b6695ff03f49c64880ad15acbebc341"}, - {file = "cyclonedx_python_lib-6.4.4.tar.gz", hash = "sha256:1b6f9109b6b9e91636dff822c2de90a05c0c8af120317713c1b879dbfdebdff8"}, + {file = "cyclonedx_python_lib-7.3.2-py3-none-any.whl", hash = "sha256:fcbcbe4c99f87910a239de84f2bc0db4a429c208b4ef0436c0cbc3677ef1b790"}, + {file = "cyclonedx_python_lib-7.3.2.tar.gz", hash = "sha256:e2b12702e98da7bce89c28496c73964c2a0b128276078c1d0b398a8ba359e000"}, ] [package.dependencies] license-expression = ">=30,<31" packageurl-python = ">=0.11,<2" -py-serializable = ">=0.16,<2" +py-serializable = ">=1.0.3,<2" sortedcontainers = ">=2.4.0,<3.0.0" [package.extras] @@ -605,13 +605,13 @@ tests = ["environs[django]", "pytest"] [[package]] name = "filelock" -version = "3.13.4" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, - {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] @@ -931,13 +931,13 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "marshmallow" -version = "3.21.1" +version = "3.21.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.21.1-py3-none-any.whl", hash = "sha256:f085493f79efb0644f270a9bf2892843142d80d7174bbbd2f3713f2a589dc633"}, - {file = "marshmallow-3.21.1.tar.gz", hash = "sha256:4e65e9e0d80fc9e609574b9983cf32579f305c718afb30d7233ab818571768c3"}, + {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, + {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, ] [package.dependencies] @@ -945,7 +945,7 @@ packaging = ">=17.0" [package.extras] dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==4.0.0)", "sphinx-version-warning (==1.1.2)"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] @@ -1413,18 +1413,18 @@ pip = "*" [[package]] name = "pip-audit" -version = "2.7.2" +version = "2.7.3" description = "A tool for scanning Python environments for known vulnerabilities" optional = false python-versions = ">=3.8" files = [ - {file = "pip_audit-2.7.2-py3-none-any.whl", hash = "sha256:49907430115baacb8bb7ffc1a2b689acfeac9d8534a43bffad3c73f8d8b32d52"}, - {file = "pip_audit-2.7.2.tar.gz", hash = "sha256:a12905e42dd452f43a2dbf895606d59c35348deed27b8cbaff8516423576fdfb"}, + {file = "pip_audit-2.7.3-py3-none-any.whl", hash = "sha256:46a11faee3323f76adf7987de8171daeb660e8f57d8088cc27fb1c1e5c7747b0"}, + {file = "pip_audit-2.7.3.tar.gz", hash = "sha256:08891bbf179bffe478521f150818112bae998424f58bf9285c0078965aef38bc"}, ] [package.dependencies] CacheControl = {version = ">=0.13.0", extras = ["filecache"]} -cyclonedx-python-lib = ">=5,<7" +cyclonedx-python-lib = ">=5,<8" html5lib = ">=1.1" packaging = ">=23.0.0" pip-api = ">=0.0.28" @@ -1436,7 +1436,7 @@ toml = ">=0.10" [package.extras] dev = ["build", "bump (>=1.3.2)", "pip-audit[doc,lint,test]"] doc = ["pdoc"] -lint = ["interrogate", "mypy", "ruff (<0.2.3)", "types-html5lib", "types-requests", "types-toml"] +lint = ["interrogate", "mypy", "ruff (<0.4.3)", "setuptools", "types-html5lib", "types-requests", "types-toml"] test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"] [[package]] @@ -1675,17 +1675,16 @@ files = [ [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] @@ -1819,13 +1818,13 @@ files = [ [[package]] name = "que-sdk" -version = "0.1.3" +version = "0.1.5" description = "" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "que_sdk-0.1.3-py3-none-any.whl", hash = "sha256:d5070e80f66ce07cf426e1884e935bec97df86c2637301d4048b18acb43f8971"}, - {file = "que_sdk-0.1.3.tar.gz", hash = "sha256:9729df92a341f565a91a6c468a5e78804608ee2848c1faf81a3e493c72803d52"}, + {file = "que_sdk-0.1.5-py3-none-any.whl", hash = "sha256:c15c1faead80108812db6b6d377c65fd3e773a857ae3bc14b0d8d736574554f5"}, + {file = "que_sdk-0.1.5.tar.gz", hash = "sha256:b6789e4d6f2b8a5fce96650c8471b1f262f1b48cd006ef340d88945a7f34c09b"}, ] [package.dependencies] @@ -2047,13 +2046,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.0" +version = "20.26.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.0-py3-none-any.whl", hash = "sha256:0846377ea76e818daaa3e00a4365c018bc3ac9760cbb3544de542885aad61fb3"}, - {file = "virtualenv-20.26.0.tar.gz", hash = "sha256:ec25a9671a5102c8d2657f62792a27b48f016664c6873f6beed3800008577210"}, + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, ] [package.dependencies] From 5a0f49c484b0f06685afc789674789e48325fc95 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 6 May 2024 21:00:09 +0300 Subject: [PATCH 071/148] =?UTF-8?q?=F0=9F=92=84=20update=20login=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/LoginForm/LoginForm.jsx | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/LoginForm/LoginForm.jsx b/frontend/src/components/LoginForm/LoginForm.jsx index 913ddd9..148771b 100644 --- a/frontend/src/components/LoginForm/LoginForm.jsx +++ b/frontend/src/components/LoginForm/LoginForm.jsx @@ -1,29 +1,39 @@ import "./LoginForm.css" -import React from "react"; -import {appJSON} from "../../configs/AxiosConfig"; +import React, {useCallback, useEffect} from "react"; +import {useTelegram} from "../../hooks/UseTelegram.js"; + const LoginForm = () => { const [data, setData] = React.useState({login: "", password: ""}); - + const {tg} = useTelegram(); const handleData = (e, name) => { setData({...data, [name]: e.target.value}); } - async function sendData() { - - try { - const response = await appJSON.post("http://127.0.0.1:8080/api/v1/auth/login/", data); - - if (response.status === 200) { - console.log("SUCCESS"); - } - - return response.data; - } catch { - console.error("LOH"); + const onSendData = useCallback(() => { + tg.sendData(JSON.stringify(data)) + }, [tg, data]) + useEffect(() => { + tg.onEvent("mainButtonClicked", onSendData) + return () => { + tg.offEvent("mainButtonClicked", onSendData) } - } + }, [tg, onSendData]) + + useEffect(() => { + tg.MainButton.setParams( + {text: 'Войти'} + ) + }, [tg]) + + useEffect(() => { + if (data.login === "" && data.password === "") { + tg.MainButton.hide(); + } else { + tg.MainButton.show(); + } + }) return (
@@ -38,7 +48,6 @@ const LoginForm = () => { handleData(e, "password")}/>
- ) From 2943b316d0694cf0c3c9827b093e5d52735dab97 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 6 May 2024 21:04:14 +0300 Subject: [PATCH 072/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 2 +- src/tgbot/middlewares/is_auth.py | 14 +++++++------- src/tgbot/services/app/broadcaster.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 542899a..ddbbf05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: 'docs|frontend|deprecated|README.md' +exclude: 'docs|frontend|deprecated|README.md|CODE_OF_CONDUCT.md|LICENSE|.github' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 diff --git a/src/tgbot/middlewares/is_auth.py b/src/tgbot/middlewares/is_auth.py index 4d82fb3..14426a6 100644 --- a/src/tgbot/middlewares/is_auth.py +++ b/src/tgbot/middlewares/is_auth.py @@ -40,15 +40,15 @@ async def __call__( state = data.get("state") storage = await state.get_data() - if command != "/start": - try: - await self.on_process_event(storage=storage) - return await handler(event, data) - except CancelHandler: - await event.answer(text=self.text) - else: + if event.web_app_data is not None or command == "/start": return await handler(event, data) + try: + await self.on_process_event(storage=storage) + return await handler(event, data) + except CancelHandler: + await event.answer(text=self.text) + async def on_process_event(self, storage: dict[str, Any]) -> Any: status_code, response = await self.client.get_user_me( access_token=storage.get("access_token", "") diff --git a/src/tgbot/services/app/broadcaster.py b/src/tgbot/services/app/broadcaster.py index db25a99..42a6a35 100644 --- a/src/tgbot/services/app/broadcaster.py +++ b/src/tgbot/services/app/broadcaster.py @@ -38,7 +38,7 @@ async def send_message( reply_markup=reply_markup, ) except exceptions.TelegramBadRequest as e: - logging.error("Telegram server says - Bad Request: chat not found {e}".format(e=e)) + logging.error(f"Telegram server says - Bad Request: chat not found {e}") except exceptions.TelegramForbiddenError: logging.error(f"Target [ID:{user_id}]: got TelegramForbiddenError") except exceptions.TelegramRetryAfter as e: From fb13c9a78e0a27ab0bad1da0dae3c86d9e24a7a3 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 6 May 2024 21:04:48 +0300 Subject: [PATCH 073/148] =?UTF-8?q?=E2=9C=A8=20add=20handler=20for=20login?= =?UTF-8?q?=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/start.py | 11 ++++++++ src/tgbot/services/app/user_operations.py | 32 ++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 57b6e7d..ca75c8f 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -1,4 +1,5 @@ import http +import json from typing import ( Any, ) @@ -34,6 +35,9 @@ from src.tgbot.services.app import ( handle_send_start_message, ) +from src.tgbot.services.app.user_operations import ( + handle_login, +) start_router = Router() @@ -66,6 +70,13 @@ async def signup_handler(message: types.Message, state: FSMContext, **middleware await handle_signup(client=client, message=message, state=state, config=config) +@start_router.message(F.web_app_data) +async def web_app_login_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + client: QueClient = middleware_data.get("que-client") + data = json.loads(message.web_app_data.data) + await handle_login(client=client, message=message, state=state, data=data) + + @start_router.message(F.text == "❔ О проекте") async def about_project_handler(message: types.Message) -> None: text = ( diff --git a/src/tgbot/services/app/user_operations.py b/src/tgbot/services/app/user_operations.py index 7dc0219..85fd1bd 100644 --- a/src/tgbot/services/app/user_operations.py +++ b/src/tgbot/services/app/user_operations.py @@ -12,8 +12,7 @@ ) from que_sdk import ( QueClient, - SignUpSchema, - TMELoginSchema, + schemas, ) from src.tgbot.config import ( @@ -64,7 +63,7 @@ async def handle_login_t_me( state: FSMContext, ) -> tuple[http.HTTPStatus, dict[str, Any]] | None: auth_data = security.generate_signature(telegram_id=message.from_user.id, secret_key=config.misc.secret_key) - status_code, response = await client.login_t_me(data_in=TMELoginSchema(**auth_data)) + status_code, response = await client.login_t_me(data_in=schemas.TMELoginSchema(**auth_data)) if status_code == http.HTTPStatus.OK: access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') @@ -81,7 +80,7 @@ async def handle_signup( ) -> tuple[http.HTTPStatus, dict[str, Any]]: username = message.from_user.username status_code, response = await client.signup( - data_in=SignUpSchema( + data_in=schemas.SignUpSchema( username=username, telegram_id=message.from_user.id, ) @@ -101,3 +100,28 @@ async def handle_not_founded_user(message: types.Message) -> None: text="Мы не смогли найти ваш в аккаунт. Создайте новый или войдите с помощью логина и пароля", reply_markup=reply.login_signup_menu() ) + + +async def handle_login( + client: QueClient, + state: FSMContext, + message: types.Message, + data: dict[str, Any] +) -> tuple[http.HTTPStatus, dict[str, Any]]: + status_code, response = await client.login( + data_in=schemas.LoginSchema( + username=data.get("login"), + password=data.get("password"), + telegram_id=message.from_user.id + ) + ) + if status_code == http.HTTPStatus.OK: + access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') + await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) + await message.answer( + text="С возвращением, {username}".format(username=data.get("login")), + reply_markup=reply.main_menu() + ) + if status_code == http.HTTPStatus.UNAUTHORIZED: + await message.answer(text="Неправильный login или password") + return status_code, response From fd13de7ba3036e49d0abf92ea5db0dfc0ebc69e4 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 6 May 2024 23:17:17 +0300 Subject: [PATCH 074/148] improve performance --- bot.py | 15 ++-- src/tgbot/handlers/start.py | 30 +++----- src/tgbot/handlers/user.py | 18 ++++- src/tgbot/keyboards/inline.py | 11 ++- src/tgbot/keyboards/reply.py | 6 +- src/tgbot/middlewares/__init__.py | 10 +-- src/tgbot/middlewares/access_control.py | 74 +++++++++++++++++++ src/tgbot/middlewares/check_activate.py | 61 --------------- src/tgbot/middlewares/check_ban.py | 61 --------------- src/tgbot/middlewares/exceptions.py | 6 +- src/tgbot/middlewares/is_auth.py | 57 -------------- src/tgbot/services/app/__init__.py | 10 ++- .../app/{user_operations.py => user.py} | 26 +++++-- 13 files changed, 150 insertions(+), 235 deletions(-) create mode 100644 src/tgbot/middlewares/access_control.py delete mode 100644 src/tgbot/middlewares/check_activate.py delete mode 100644 src/tgbot/middlewares/check_ban.py delete mode 100644 src/tgbot/middlewares/is_auth.py rename src/tgbot/services/app/{user_operations.py => user.py} (69%) diff --git a/bot.py b/bot.py index 437cc18..2929036 100644 --- a/bot.py +++ b/bot.py @@ -26,6 +26,9 @@ QueClient, ) +from src.tgbot import ( + services, +) from src.tgbot.config import ( Config, load_config, @@ -34,17 +37,13 @@ routers_list, ) from src.tgbot.middlewares import ( - CheckActivateMiddleware, - IsAuthMiddleware, + AccessControlMiddleware, MiscMiddleware, ) -from src.tgbot.services import ( - broadcaster, -) async def on_startup(bot: Bot, admin_ids: Sequence[int]) -> None: - await broadcaster.broadcast(bot, list(admin_ids), "Бот запущен") + await services.broadcaster.broadcast(bot, list(admin_ids), "Бот запущен") def setup_logging() -> None: @@ -81,8 +80,7 @@ def register_global_middlewares( logging.info("Setup middlewares...") middleware_types = [ MiscMiddleware(config, client), - CheckActivateMiddleware(client), - IsAuthMiddleware(client) + AccessControlMiddleware(client=client) ] for middleware_type in middleware_types: dp.message.outer_middleware(middleware_type) @@ -121,6 +119,7 @@ async def main() -> None: register_global_middlewares(dp, config, client) + await services.set_default_commands(bot, config) await on_startup(bot, config.tg_bot.admin_ids) await dp.start_polling(bot) diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index ca75c8f..296e485 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -19,25 +19,15 @@ QueClient, ) +from src.tgbot import ( + services, +) from src.tgbot.config import ( Config, ) from src.tgbot.keyboards import ( inline, ) -from src.tgbot.services import ( - get_user_data, - handle_login_t_me, - handle_not_founded_user, - handle_signup, - welcoming_message, -) -from src.tgbot.services.app import ( - handle_send_start_message, -) -from src.tgbot.services.app.user_operations import ( - handle_login, -) start_router = Router() @@ -49,32 +39,32 @@ async def start_handler(message: types.Message, state: FSMContext, **middleware_ storage = await state.get_data() if not storage: - status_code, response = await handle_login_t_me(que_client, config, message, state) + status_code, response = await services.handle_login_t_me(que_client, config, message, state) if status_code == http.HTTPStatus.NOT_FOUND: - await handle_not_founded_user(message=message) + await services.handle_not_founded_user(message=message) storage = await state.get_data() - status_code, response = await get_user_data(que_client, storage) + status_code, response = await services.get_user_data(que_client, storage) if status_code == http.HTTPStatus.BAD_REQUEST: code = response.get("detail").get("code") if code == 3002: - await message.answer(text=welcoming_message(message_type="deactivate_user")) + await message.answer(text=services.welcoming_message(message_type="deactivate_user")) else: if status_code != http.HTTPStatus.UNAUTHORIZED: - await handle_send_start_message(message=message, response=response) + await services.handle_send_start_message(message=message, response=response) @start_router.message(F.text == "✏️ Создать аккаунт") async def signup_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: client: QueClient = middleware_data.get("que-client") config: Config = middleware_data.get("config") - await handle_signup(client=client, message=message, state=state, config=config) + await services.handle_signup(client=client, message=message, state=state, config=config) @start_router.message(F.web_app_data) async def web_app_login_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: client: QueClient = middleware_data.get("que-client") data = json.loads(message.web_app_data.data) - await handle_login(client=client, message=message, state=state, data=data) + await services.handle_login(client=client, message=message, state=state, data=data) @start_router.message(F.text == "❔ О проекте") diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py index 40ac048..0dcc335 100644 --- a/src/tgbot/handlers/user.py +++ b/src/tgbot/handlers/user.py @@ -18,6 +18,7 @@ ) from src.tgbot.keyboards import ( + inline, reply, ) @@ -31,15 +32,15 @@ async def user_handler(message: types.Message, state: FSMContext, **middleware_d status_code, response = await que_client.get_user_me(access_token=storage.get("access_token")) days = response.get("days_since_created") text = ( - "Username: *{username}*\n" - "TelegramID: `{telegram_id}`\n\n" + "Имя пользователя: *{username}*\n" + "Уникальный ID: `{telegram_id}`\n\n" "Вы с нами {days} дней" ).format( username=response.get("username"), telegram_id=response.get("telegram_id"), days=days ) - await message.answer(text=text) + await message.answer(text=text, reply_markup=inline.user_menu()) @user_router.message(F.text, Command("reactivate")) @@ -50,3 +51,14 @@ async def user_activate_handler(message: types.Message, state: FSMContext, **mid await que_client.reactivate_user(access_token=access_token) await message.delete() await message.answer(text="Поздравляем! Вы восстановили аккаунт", reply_markup=reply.main_menu()) + + +@user_router.callback_query(F.data == "user:signout") +async def user_signout_handler(call: types.CallbackQuery, state: FSMContext) -> None: + text = ( + "Вы вышли из текущей сессии, чтобы войти используйте команду /start" + ) + + await state.clear() + await call.message.delete() + await call.message.answer(text=text, reply_markup=types.ReplyKeyboardRemove()) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index 95d5b58..9f0ccf7 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -12,15 +12,18 @@ def about_project_menu() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.row( - types.InlineKeyboardButton(text="Github", web_app=WebAppInfo(url="https://github.com/QueGroup")) + types.InlineKeyboardButton(text="🔗 Github", web_app=WebAppInfo(url="https://github.com/QueGroup")) ) return builder.as_markup() -def user_menu(is_active: bool) -> types.InlineKeyboardMarkup: +def user_menu() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.row( - types.InlineKeyboardButton(text="🔁 Изменить", callback_data="user:update"), + types.InlineKeyboardButton(text="👤 Мой профиль", callback_data="user:profile") + ) + builder.row( + types.InlineKeyboardButton(text="🔙 Выйти из аккаунта", callback_data="user:signout") ) return builder.as_markup() @@ -29,6 +32,6 @@ def user_menu(is_active: bool) -> types.InlineKeyboardMarkup: def user_activation_menu() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.row( - types.InlineKeyboardButton(text="🔛 Активировать", callback_data="user:activate") + types.InlineKeyboardButton(text="🚀 Активировать", callback_data="user:activate") ) return builder.as_markup() diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index 8d33510..84ea535 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -19,7 +19,7 @@ def main_menu() -> types.ReplyKeyboardMarkup: types.KeyboardButton(text="🎭 Мероприятия"), ) builder.row( - types.KeyboardButton(text="❔ О проекте") + types.KeyboardButton(text="ℹ️ О проекте") ) return builder.as_markup(resize_keyboard=True) @@ -27,7 +27,7 @@ def main_menu() -> types.ReplyKeyboardMarkup: def login_signup_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() builder.row( - types.KeyboardButton(text="✏️ Создать аккаунт"), - types.KeyboardButton(text="🛂 Войти в аккаунт", web_app=WebAppInfo(url="")), + types.KeyboardButton(text="📝 Создать аккаунт"), + types.KeyboardButton(text="🔑 Войти в аккаунт", web_app=WebAppInfo(url="https://tiny-memes-glow.loca.lt")), ) return builder.as_markup(resize_keyboard=True) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index 44f466d..247c058 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -19,15 +19,12 @@ # from .Throttling import ( # ThrottlingMiddleware, # TODO: https://ru.stackoverflow.com/questions/1540655/ # ) -from .check_activate import ( - CheckActivateMiddleware, +from .access_control import ( + AccessControlMiddleware, ) from .config import ( MiscMiddleware, ) -from .is_auth import ( - IsAuthMiddleware, -) __all__ = ( # "AgentSupport", @@ -40,6 +37,5 @@ # "SupportMiddleware", # "LinkCheckMiddleware", "MiscMiddleware", - "CheckActivateMiddleware", - "IsAuthMiddleware", + "AccessControlMiddleware", ) diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py new file mode 100644 index 0000000..b9d541f --- /dev/null +++ b/src/tgbot/middlewares/access_control.py @@ -0,0 +1,74 @@ +import http +from typing import ( + Any, +) + +from aiogram import ( + BaseMiddleware, +) +from aiogram.types import ( + TelegramObject, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot.services import ( + welcoming_message, +) +from src.tgbot.types import ( + Handler, +) + +from .exceptions import ( + CancelHandler, +) + + +class AccessControlMiddleware(BaseMiddleware): + def __init__(self, client: QueClient) -> None: + self.client = client + self.text_deactivate = welcoming_message(message_type="deactivate_user") + self.text_unauthorized = "Вы не вошли в аккаунт" + + # FIXME: REFACTOR ME + async def __call__( # type: ignore + self, + handler: Handler, + event: TelegramObject, + data: dict[str, Any] + ) -> Handler | None: + update = data.get("event_update") + try: + command = update.message.text + except AttributeError: + command = event.data + state = data.get("state") + storage = await state.get_data() + + try: + if event.web_app_data is not None or command == "/start" or command == "/reactivate": + return await handler(event, data) + except AttributeError: + if command == "/start" or command == "/reactivate": + return await handler(event, data) + + try: + await self.on_process_event(storage=storage) + return await handler(event, data) + except CancelHandler as e: + if e.title == "deactivate": + await event.answer(text=self.text_deactivate) + else: + await event.answer(text=self.text_unauthorized) + + async def on_process_event(self, storage: dict[str, Any]) -> None: + status_code, response = await self.client.get_user_me( + access_token=storage.get("access_token", "") + ) + if status_code == http.HTTPStatus.UNAUTHORIZED: + raise CancelHandler("unauthorized") + elif status_code == http.HTTPStatus.BAD_REQUEST: + code = response.get("detail").get("code") + if code == 3002: + raise CancelHandler("deactivate") diff --git a/src/tgbot/middlewares/check_activate.py b/src/tgbot/middlewares/check_activate.py deleted file mode 100644 index e306d48..0000000 --- a/src/tgbot/middlewares/check_activate.py +++ /dev/null @@ -1,61 +0,0 @@ -import http -from typing import ( - Any, -) - -from aiogram import ( - BaseMiddleware, -) -from aiogram.types import ( - TelegramObject, -) -from que_sdk import ( - QueClient, -) - -from src.tgbot.services import ( - welcoming_message, -) -from src.tgbot.types import ( - Handler, -) - -from .exceptions import ( - CancelHandler, -) - - -class CheckActivateMiddleware(BaseMiddleware): - def __init__(self, client: QueClient) -> None: - self.client = client - self.text = welcoming_message(message_type="deactivate_user") - - async def __call__( - self, - handler: Handler, - event: TelegramObject, - data: dict[str, Any] - ) -> Any: - update = data.get("event_update") - command = update.message.text - state = data.get("state") - storage = await state.get_data() - if storage and command != "/reactivate": - try: - await self.on_process_event(storage=storage) - return await handler(event, data) - except CancelHandler: - await event.answer(text=self.text) - else: - return await handler(event, data) - - async def on_process_event(self, storage: dict[str, Any]) -> Any: - status_code, response = await self.client.get_user_me( - access_token=storage.get("access_token", "") - ) - try: - code = response.get("detail").get("code") - if status_code == http.HTTPStatus.BAD_REQUEST and code == 3002: - raise CancelHandler() - except AttributeError: - pass diff --git a/src/tgbot/middlewares/check_ban.py b/src/tgbot/middlewares/check_ban.py deleted file mode 100644 index baca88e..0000000 --- a/src/tgbot/middlewares/check_ban.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import ( - Any, -) - -from aiogram import ( - BaseMiddleware, -) -from aiogram.types import ( - TelegramObject, -) - -from src.tgbot.types import ( - Handler, -) - - -class BanMiddleware(BaseMiddleware): - async def __call__( - self, - handler: Handler, - event: TelegramObject, - data: dict[str, Any] - ) -> Any: - pass - - # @staticmethod - # async def is_banned(user): - # try: - # return user.is_banned - # except AttributeError: - # return False - - # async def on_process_message(self, message: types.Message, data: dict) -> None: - # await self.check_ban_user(obj=message) - # - # async def on_process_callback_query( - # self, call: types.CallbackQuery, data: dict - # ) -> None: - # user = await db_commands.select_user(telegram_id=call.from_user.id) - # if (user is not None and await self.is_banned(user=user)) and ( - # call.data != "unban" - # and call.data != "unban_menu" - # and call.data != "Yoomoney:check_payment" - # and call.data != "cancel_payment" - # and call.data != "Yoomoney" - # ): - # await self.check_ban_user(obj=call) - # - # async def check_ban_user( - # self, obj: types.CallbackQuery | types.Message - # ) -> NoReturn: - # user = await db_commands.select_user(telegram_id=obj.from_user.id) - # - # text = _("😢 Вы заблокированы!") - # markup = await unban_user_keyboard() - # if await self.is_banned(user=user): - # try: - # await obj.answer(text=text, reply_markup=markup) - # except TypeError: - # await obj.message.answer(text=text, reply_markup=markup) - # raise CancelHandler() diff --git a/src/tgbot/middlewares/exceptions.py b/src/tgbot/middlewares/exceptions.py index 6794518..38f204a 100644 --- a/src/tgbot/middlewares/exceptions.py +++ b/src/tgbot/middlewares/exceptions.py @@ -1,2 +1,6 @@ +import dataclasses + + +@dataclasses.dataclass(eq=False) class CancelHandler(Exception): - pass + title: str | None = None diff --git a/src/tgbot/middlewares/is_auth.py b/src/tgbot/middlewares/is_auth.py deleted file mode 100644 index 14426a6..0000000 --- a/src/tgbot/middlewares/is_auth.py +++ /dev/null @@ -1,57 +0,0 @@ -import http -from typing import ( - Any, -) - -from aiogram import ( - BaseMiddleware, -) -from aiogram.types import ( - TelegramObject, -) -from que_sdk import ( - QueClient, -) - -from src.tgbot.types import ( - Handler, -) - -from .exceptions import ( - CancelHandler, -) - - -class IsAuthMiddleware(BaseMiddleware): - def __init__(self, client: QueClient) -> None: - self.client = client - self.text = ( - "Вы не вошли в аккаунт" - ) - - async def __call__( - self, - handler: Handler, - event: TelegramObject, - data: dict[str, Any] - ) -> Any: - update = data.get("event_update") - command = update.message.text - state = data.get("state") - storage = await state.get_data() - - if event.web_app_data is not None or command == "/start": - return await handler(event, data) - - try: - await self.on_process_event(storage=storage) - return await handler(event, data) - except CancelHandler: - await event.answer(text=self.text) - - async def on_process_event(self, storage: dict[str, Any]) -> Any: - status_code, response = await self.client.get_user_me( - access_token=storage.get("access_token", "") - ) - if status_code == http.HTTPStatus.UNAUTHORIZED: - raise CancelHandler() diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py index 5d74ab7..5f2fa4c 100644 --- a/src/tgbot/services/app/__init__.py +++ b/src/tgbot/services/app/__init__.py @@ -1,13 +1,17 @@ from . import ( broadcaster, ) -from .user_operations import ( +from .set_bot_commands import ( + set_default_commands, +) +from .user import ( get_user_data, handle_login_t_me, handle_not_founded_user, handle_send_start_message, handle_signup, welcoming_message, + handle_login, ) __all__ = ( @@ -17,5 +21,7 @@ "handle_login_t_me", "handle_signup", "handle_not_founded_user", - "handle_send_start_message" + "handle_send_start_message", + "set_default_commands", + "handle_login", ) diff --git a/src/tgbot/services/app/user_operations.py b/src/tgbot/services/app/user.py similarity index 69% rename from src/tgbot/services/app/user_operations.py rename to src/tgbot/services/app/user.py index 85fd1bd..03da68e 100644 --- a/src/tgbot/services/app/user_operations.py +++ b/src/tgbot/services/app/user.py @@ -26,13 +26,16 @@ ) -def welcoming_message(message_type: Literal["welcome", "greet_auth_user", "deactivate_user"], **kwargs: Any) -> str: +def welcoming_message( + message_type: Literal["welcome", "greet_auth_user", "deactivate_user"], + **kwargs: Any, +) -> str: messages = { - "welcome": "Добро пожаловать, {username}! Вы создали новый аккаунт", - "greet_auth_user": "Привет {username} вы вошли в аккаунт", - "deactivate_user": "Из-за отключения вашего аккаунта, доступ к приложению ограничен.\n" - "Для возобновления работы с нашим приложением, пожалуйста, активируйте ваш аккаунт.\n" - "Чтобы активировать аккаунт, используйте /reactivate", + "welcome": "🎉 Добро пожаловать, {username}! Вы создали новый аккаунт", + "greet_auth_user": "👋 Привет {username} вы вошли в аккаунт", + "deactivate_user": "🛑 К сожалению, ваш аккаунт был деактивирован, и доступ к приложению ограничен.\n" + "Чтобы возобновить работу с нашим приложением, пожалуйста, активируйте аккаунт.\n" + "Чтобы активировать аккаунт, используйте команду /reactivate.", } return messages[message_type].format(**kwargs) @@ -96,8 +99,14 @@ async def handle_signup( async def handle_not_founded_user(message: types.Message) -> None: + text = ( + "🔍 Извините, мы не смогли найти ваш аккаунт.\nЕсли вы еще не зарегистрированы, вы можете создать новый " + "аккаунт, нажав на кнопку 'Создать аккаунт' ниже.\nЕсли вы уже зарегистрированы, пожалуйста, войдите в свой " + "аккаунт, используя ваш логин и пароль." + + ) await message.answer( - text="Мы не смогли найти ваш в аккаунт. Создайте новый или войдите с помощью логина и пароля", + text=text, reply_markup=reply.login_signup_menu() ) @@ -123,5 +132,6 @@ async def handle_login( reply_markup=reply.main_menu() ) if status_code == http.HTTPStatus.UNAUTHORIZED: - await message.answer(text="Неправильный login или password") + await message.answer(text="🔐 Ой, кажется, вы ввели неправильный логин или пароль. Пожалуйста, проверьте " + "введенные данные и попробуйте снова.") return status_code, response From d41fd182f948656203d374c1f25a23f5558e07be Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:22:06 +0300 Subject: [PATCH 075/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 7ba9701..bb2fc7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -238,6 +238,20 @@ tests = ["attrs[tests-no-zope]", "zope-interface"] tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] +[[package]] +name = "babel" +version = "2.15.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + [[package]] name = "backoff" version = "2.2.1" @@ -2181,4 +2195,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "a346fda0befa4fb6392617585e7b1017279edc57b1fe022e4c68c979a5edd525" +content-hash = "250e0a5bd6ebae7e6018c5326fb7e27b7868f9831736cafc9f54ad0c7895bbcc" diff --git a/pyproject.toml b/pyproject.toml index f935b57..9e605b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ deptry = "^0.12.0" ruff = "^0.1.14" pip-audit = "^2.7.0" que-sdk = "^0.1.0" +babel = "^2.15.0" [tool.black] line-length = 99 From b6eb93a828602b47678dbce2cd3688af03bd6ca6 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:23:46 +0300 Subject: [PATCH 076/148] =?UTF-8?q?=F0=9F=8C=90=20add=20localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ru/LC_MESSAGES/messages.po | 100 +++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 locales/ru/LC_MESSAGES/messages.po diff --git a/locales/ru/LC_MESSAGES/messages.po b/locales/ru/LC_MESSAGES/messages.po new file mode 100644 index 0000000..e6de646 --- /dev/null +++ b/locales/ru/LC_MESSAGES/messages.po @@ -0,0 +1,100 @@ +# Russian translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-05-07 10:01+0300\n" +"PO-Revision-Date: 2024-05-07 10:11+0300\n" +"Last-Translator: FULL NAME \n" +"Language: ru\n" +"Language-Team: ru \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.15.0\n" + +#: src/tgbot/handlers/start.py:74 +msgid "Наша система полностью open-source" +msgstr "" + +#: src/tgbot/keyboards/inline.py:24 +msgid "👤 Мой профиль" +msgstr "" + +#: src/tgbot/keyboards/inline.py:27 +msgid "🔙 Выйти из аккаунта" +msgstr "" + +#: src/tgbot/keyboards/inline.py:36 +msgid "🚀 Активировать" +msgstr "" + +#: src/tgbot/keyboards/reply.py:16 +msgid "👤 Аккаунт" +msgstr "" + +#: src/tgbot/keyboards/reply.py:19 +msgid "💜 Знакомства" +msgstr "" + +#: src/tgbot/keyboards/reply.py:20 +msgid "🎭 Мероприятия" +msgstr "" + +#: src/tgbot/keyboards/reply.py:23 +msgid "ℹ️ О проекте" +msgstr "" + +#: src/tgbot/keyboards/reply.py:31 +msgid "📝 Создать аккаунт" +msgstr "" + +#: src/tgbot/keyboards/reply.py:32 +msgid "🔑 Войти в аккаунт" +msgstr "" + +#: src/tgbot/services/app/set_bot_commands.py:27 +msgid "🟢 Запустить бота" +msgstr "" + +#: src/tgbot/services/app/user.py:35 +msgid "🎉 Добро пожаловать, {username}! Вы создали новый аккаунт" +msgstr "" + +#: src/tgbot/services/app/user.py:36 +msgid "👋 Привет {username} вы вошли в аккаунт" +msgstr "" + +#: src/tgbot/services/app/user.py:37 +msgid "" +"🛑 К сожалению, ваш аккаунт был деактивирован, и доступ к приложению " +"ограничен.\n" +"Чтобы возобновить работу с нашим приложением, пожалуйста, активируйте " +"аккаунт.\n" +"Чтобы активировать аккаунт, используйте команду /reactivate." +msgstr "" + +#: src/tgbot/services/app/user.py:103 +msgid "" +"🔍 Извините, мы не смогли найти ваш аккаунт.\n" +"Если вы еще не зарегистрированы, вы можете создать новый аккаунт, нажав " +"на кнопку 'Создать аккаунт' ниже.\n" +"Если вы уже зарегистрированы, пожалуйста, войдите в свой аккаунт, " +"используя ваш логин и пароль." +msgstr "" + +#: src/tgbot/services/app/user.py:132 +msgid "С возвращением, {username}" +msgstr "" + +#: src/tgbot/services/app/user.py:136 +msgid "" +"🔐 Ой, кажется, вы ввели неправильный логин или пароль. Пожалуйста, " +"проверьте введенные данные и попробуйте снова." +msgstr "" From 10db2e8726c12839069d3711ea79a1bdce054fdf Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:24:09 +0300 Subject: [PATCH 077/148] =?UTF-8?q?=F0=9F=93=9D=20update=20DEVELOPMENT.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DEVELOPMENT.md | 56 ++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index a5947e8..0faa6b5 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -7,7 +7,7 @@ - [Manually](#wheelchair-manually) - [requirements](#with-requirements) - [poetry](#with-poetry) - - [Localization](#globe_with_meridians-i18n) + - [Localization](#globe_with_meridians-i18n) - [Tests](#test_tube-tests) ## :construction_worker: Getting Started @@ -17,14 +17,15 @@ First, rename the file `.env.dist` to `.env`.\ Afterward, fill it with the required data. -| Variable | Type | Importance | Description | -|-------------------|------|------------|-----------------------------------------------------------------------------------------| -| BOT_TOKEN | str | True | Bot token | -| ADMINS | list | True | list of admins id | -| SUPPORTS | list | True | list of supports id | -| IP | str | True | ip for other services | -| TIMEZONE | str | True | your time zone for working with the scheduler | -| MODERATE_CHAT | str | True | telegram chat where the event will be moderated | +| Variable | Type | Importance | Description | +|----------------------|------|------------|-------------------------------------------------| +| BOT_TOKEN | str | True | Bot token | +| ADMINS | list | True | list of admins id | +| SUPPORTS | list | True | list of supports id | +| TIMEZONE | str | True | your time zone for working with the scheduler | +| MODERATE_CHAT | str | True | telegram chat where the event will be moderated | +| SIGNATURE_SECRET_KEY | str | True | signature for login | +| USE_REDIS | bool | True | Use redis or default storage | Once done, run the following command: @@ -87,29 +88,6 @@ Then install Poetry dependencies: poetry install ``` -## :rocket: Usage - -Follow the same steps as described in the Docker section for setting up the .env file. But use `localhost` for `DB_HOST` - -### :green_book: Django - -To create a `SECRET_KEY`, use this site - [generate secret keys](https://djecrety.ir/) -and paste it into the `.env` file - -```dotenv -SECRET_KEY=jjv@^0qv^=aydunfjo$qpd_66j+)egm1#-c1iwt%mtjinm)ftj -``` - -Run the following commands to set up the Django application: -```sh -$ python django_app.py makemigrations -$ python django_app.py migrate -$ python django_app.py createsuperuser -$ python django_app.py makemigrations usersmanage -$ python django_app.py migrate usersmanage -$ python django_app.py runserver -``` - ## :globe_with_meridians: i18n ### Title - dating @@ -118,31 +96,31 @@ $ python django_app.py runserver 1. Extract texts from files (he finds it himself) ```sh - $ pybabel extract -F babel.cfg -o locales/dating.pot . + $ pybabel extract -F babel.cfg --input-dirs=. -o locales/messages.pot ``` 2. Create a folder for English translation ```sh - $ pybabel init -i locales/dating.pot -d locales -D dating -l en + $ pybabel init -i locales/messages.pot -d locales -D messages -l en ``` 3. For Russian translation ```sh - $ pybabel init -i locales/dating.pot -d locales -D dating -l ru + $ pybabel init -i locales/messages.pot -d locales -D messages -l ru ``` 4. Translate and compile ```sh - $ pybabel compile -d locales -D dating + $ pybabel compile -d locales -D messages ``` #### Updating translations 1. Extract texts from files, add text to translated versions ```sh - $ pybabel extract -F babel.cfg -o locales/dating.pot . - $ pybabel update -d locales -D dating -i locales/dating.pot + $ pybabel extract -F babel.cfg -o locales/messages.pot . + $ pybabel update -d locales -D dating -i locales/messages.pot ``` 2. Manually translate, then compile ```sh - $ pybabel compile -d locales -D dating + $ pybabel compile -d locales -D messages ``` ## :test_tube: Tests \ No newline at end of file From 4036ce18c4825ba7f9c1c00b496df7fda776c321 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:26:30 +0300 Subject: [PATCH 078/148] =?UTF-8?q?=E2=9C=A8=20add=20i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 17 +++++++++++------ src/tgbot/config.py | 2 ++ src/tgbot/handlers/start.py | 17 +++++++++++++---- src/tgbot/keyboards/reply.py | 15 +++++++++------ src/tgbot/services/app/user.py | 29 ++++++++++++++++++----------- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/bot.py b/bot.py index 2929036..cdaad9b 100644 --- a/bot.py +++ b/bot.py @@ -21,6 +21,10 @@ DefaultKeyBuilder, RedisStorage, ) +from aiogram.utils.i18n import ( + ConstI18nMiddleware, + I18n, +) import betterlogging as bl from que_sdk import ( QueClient, @@ -75,13 +79,15 @@ def setup_logging() -> None: def register_global_middlewares( dp: Dispatcher, config: Config, - client: QueClient + client: QueClient, + i18n: I18n ) -> None: logging.info("Setup middlewares...") middleware_types = [ MiscMiddleware(config, client), - AccessControlMiddleware(client=client) + AccessControlMiddleware(client=client), ] + for middleware_type in middleware_types: dp.message.outer_middleware(middleware_type) dp.callback_query.outer_middleware(middleware_type) @@ -110,15 +116,14 @@ async def main() -> None: setup_logging() config = load_config() + i18n = I18n(path=config.tg_bot.LOCALES_DIR, default_locale="ru", domain="messages") storage = get_storage(config) client = QueClient() bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN)) dp = Dispatcher(storage=storage) - + ConstI18nMiddleware(locale="ru", i18n=i18n).setup(router=dp) + register_global_middlewares(dp, config, client, i18n) dp.include_routers(*routers_list) - - register_global_middlewares(dp, config, client) - await services.set_default_commands(bot, config) await on_startup(bot, config.tg_bot.admin_ids) await dp.start_polling(bot) diff --git a/src/tgbot/config.py b/src/tgbot/config.py index 84d7c2e..bacbcd8 100644 --- a/src/tgbot/config.py +++ b/src/tgbot/config.py @@ -63,6 +63,8 @@ class TgBot: ip: str moderate_chat: int use_redis: bool + _BASE_DIR = Path(__file__).parent.parent.parent + LOCALES_DIR = _BASE_DIR / "locales" @staticmethod def from_env(env: Env) -> "TgBot": diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 296e485..e4a7d86 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -15,6 +15,10 @@ from aiogram.fsm.context import ( FSMContext, ) +from aiogram.utils.i18n import ( + gettext as _, + lazy_gettext as __, +) from que_sdk import ( QueClient, ) @@ -25,11 +29,17 @@ from src.tgbot.config import ( Config, ) +from src.tgbot.filters import ( + ChatTypeFilter, +) from src.tgbot.keyboards import ( inline, ) start_router = Router() +start_router.message.filter( + ChatTypeFilter(chat_type=["private"]) +) @start_router.message(CommandStart()) @@ -37,7 +47,6 @@ async def start_handler(message: types.Message, state: FSMContext, **middleware_ config: Config = middleware_data.get("config") que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() - if not storage: status_code, response = await services.handle_login_t_me(que_client, config, message, state) if status_code == http.HTTPStatus.NOT_FOUND: @@ -53,7 +62,7 @@ async def start_handler(message: types.Message, state: FSMContext, **middleware_ await services.handle_send_start_message(message=message, response=response) -@start_router.message(F.text == "✏️ Создать аккаунт") +@start_router.message(F.text == __("📝 Создать аккаунт")) async def signup_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: client: QueClient = middleware_data.get("que-client") config: Config = middleware_data.get("config") @@ -67,9 +76,9 @@ async def web_app_login_handler(message: types.Message, state: FSMContext, **mid await services.handle_login(client=client, message=message, state=state, data=data) -@start_router.message(F.text == "❔ О проекте") +@start_router.message(F.text == __("ℹ️ О проекте")) async def about_project_handler(message: types.Message) -> None: - text = ( + text = _( "Наша система полностью open-source" ) await message.answer(text=text, reply_markup=inline.about_project_menu()) diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index 84ea535..aec0cb0 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -4,6 +4,9 @@ from aiogram.types import ( WebAppInfo, ) +from aiogram.utils.i18n import ( + gettext as _, +) from aiogram.utils.keyboard import ( ReplyKeyboardBuilder, ) @@ -12,14 +15,14 @@ def main_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() builder.add( - types.KeyboardButton(text="👤 Аккаунт") + types.KeyboardButton(text=_("👤 Аккаунт")) ) builder.row( - types.KeyboardButton(text="💜 Знакомства"), - types.KeyboardButton(text="🎭 Мероприятия"), + types.KeyboardButton(text=_("💜 Знакомства")), + types.KeyboardButton(text=_("🎭 Мероприятия")), ) builder.row( - types.KeyboardButton(text="ℹ️ О проекте") + types.KeyboardButton(text=_("ℹ️ О проекте")) ) return builder.as_markup(resize_keyboard=True) @@ -27,7 +30,7 @@ def main_menu() -> types.ReplyKeyboardMarkup: def login_signup_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() builder.row( - types.KeyboardButton(text="📝 Создать аккаунт"), - types.KeyboardButton(text="🔑 Войти в аккаунт", web_app=WebAppInfo(url="https://tiny-memes-glow.loca.lt")), + types.KeyboardButton(text=_("📝 Создать аккаунт")), + types.KeyboardButton(text=_("🔑 Войти в аккаунт"), web_app=WebAppInfo(url="https://floppy-phones-camp.loca.lt")), ) return builder.as_markup(resize_keyboard=True) diff --git a/src/tgbot/services/app/user.py b/src/tgbot/services/app/user.py index 03da68e..195efcb 100644 --- a/src/tgbot/services/app/user.py +++ b/src/tgbot/services/app/user.py @@ -10,6 +10,9 @@ from aiogram.fsm.context import ( FSMContext, ) +from aiogram.utils.i18n import ( + gettext as _, +) from que_sdk import ( QueClient, schemas, @@ -33,9 +36,9 @@ def welcoming_message( messages = { "welcome": "🎉 Добро пожаловать, {username}! Вы создали новый аккаунт", "greet_auth_user": "👋 Привет {username} вы вошли в аккаунт", - "deactivate_user": "🛑 К сожалению, ваш аккаунт был деактивирован, и доступ к приложению ограничен.\n" - "Чтобы возобновить работу с нашим приложением, пожалуйста, активируйте аккаунт.\n" - "Чтобы активировать аккаунт, используйте команду /reactivate.", + "deactivate_user": ("🛑 К сожалению, ваш аккаунт был деактивирован, и доступ к приложению ограничен.\n" + "Чтобы возобновить работу с нашим приложением, пожалуйста, активируйте аккаунт.\n" + "Чтобы активировать аккаунт, используйте команду /reactivate."), } return messages[message_type].format(**kwargs) @@ -54,7 +57,7 @@ async def handle_send_start_message( ) -> None: username = response.get("username") if response.get("username") is not None else message.from_user.username await message.answer( - text=welcoming_message(username=username, message_type="greet_auth_user"), + text=welcoming_message(message_type="greet_auth_user", username=username), reply_markup=reply.main_menu() ) @@ -90,7 +93,7 @@ async def handle_signup( ) await message.answer( - text=welcoming_message(username=username, message_type="welcome"), + text=welcoming_message(message_type="welcome", username=username), reply_markup=reply.main_menu() ) await handle_login_t_me(client=client, state=state, config=config, message=message) @@ -99,9 +102,9 @@ async def handle_signup( async def handle_not_founded_user(message: types.Message) -> None: - text = ( - "🔍 Извините, мы не смогли найти ваш аккаунт.\nЕсли вы еще не зарегистрированы, вы можете создать новый " - "аккаунт, нажав на кнопку 'Создать аккаунт' ниже.\nЕсли вы уже зарегистрированы, пожалуйста, войдите в свой " + text = _( + "🔍 Извините, мы не смогли найти ваш аккаунт.\nВы можете создать новый " + "аккаунт, нажав на кнопку 'Создать аккаунт' ниже.\nили войти в свой " "аккаунт, используя ваш логин и пароль." ) @@ -128,10 +131,14 @@ async def handle_login( access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) await message.answer( - text="С возвращением, {username}".format(username=data.get("login")), + text=_("С возвращением, {username}").format(username=data.get("login")), reply_markup=reply.main_menu() ) if status_code == http.HTTPStatus.UNAUTHORIZED: - await message.answer(text="🔐 Ой, кажется, вы ввели неправильный логин или пароль. Пожалуйста, проверьте " - "введенные данные и попробуйте снова.") + await message.answer( + text=_( + "🔐 Ой, кажется, вы ввели неправильный логин или пароль. Пожалуйста, проверьте " + "введенные данные и попробуйте снова." + ) + ) return status_code, response From e5c9be757783d8a3f0495d45c2a5000e7cb7ef8a Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:26:59 +0300 Subject: [PATCH 079/148] =?UTF-8?q?=F0=9F=9A=A7=20developing=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/__init__.py | 4 ++++ src/tgbot/handlers/profile.py | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/tgbot/handlers/profile.py diff --git a/src/tgbot/handlers/__init__.py b/src/tgbot/handlers/__init__.py index a9c4dcd..389cec4 100644 --- a/src/tgbot/handlers/__init__.py +++ b/src/tgbot/handlers/__init__.py @@ -1,3 +1,6 @@ +from .profile import ( + profile_router, +) from .start import ( start_router, ) @@ -8,6 +11,7 @@ routers_list = [ start_router, user_router, + profile_router, # echo_router, # echo_router must be last ] diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py new file mode 100644 index 0000000..96d23f2 --- /dev/null +++ b/src/tgbot/handlers/profile.py @@ -0,0 +1,35 @@ +from typing import ( + Any, +) + +from aiogram import ( + F, + Router, +) +from aiogram.fsm.context import ( + FSMContext, +) +from aiogram.types import ( + CallbackQuery, +) + +from src.tgbot.filters import ( + ChatTypeFilter, +) +from src.tgbot.keyboards import ( + inline, +) + +profile_router = Router() +profile_router.message.filter( + ChatTypeFilter(chat_type=["private"]) +) + + +@profile_router.callback_query(F.data == "user:profile") +async def profile_handler(call: CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: + # que_client: QueClient = middleware_data.get("que-client") + # storage = await state.get_data() + # await call.message.delete() + # await call.message.answer_photo() + await call.message.edit_text(text="Ваш профиль: ", reply_markup=inline.profile_menu()) From 4dae2f6e465d983906f0c8aeb04268ee332402c1 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:27:39 +0300 Subject: [PATCH 080/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/__init__.py | 1 + src/tgbot/filters/__init__.py | 1 + src/tgbot/handlers/user.py | 8 ++++++++ src/tgbot/middlewares/access_control.py | 13 +++++++++---- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/infrastructure/__init__.py b/src/infrastructure/__init__.py index e69de29..ac8c604 100644 --- a/src/infrastructure/__init__.py +++ b/src/infrastructure/__init__.py @@ -0,0 +1 @@ +# TODO: Вынести модуль infra на серверную часть diff --git a/src/tgbot/filters/__init__.py b/src/tgbot/filters/__init__.py index 128f965..244bf63 100644 --- a/src/tgbot/filters/__init__.py +++ b/src/tgbot/filters/__init__.py @@ -7,4 +7,5 @@ __all__ = ( "IsAdmin", + "ChatTypeFilter" ) diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py index 0dcc335..2cb42de 100644 --- a/src/tgbot/handlers/user.py +++ b/src/tgbot/handlers/user.py @@ -17,12 +17,18 @@ QueClient, ) +from src.tgbot.filters import ( + ChatTypeFilter, +) from src.tgbot.keyboards import ( inline, reply, ) user_router = Router() +user_router.message.filter( + ChatTypeFilter(chat_type=["private"]) +) @user_router.message(F.text == "👤 Аккаунт") @@ -53,6 +59,8 @@ async def user_activate_handler(message: types.Message, state: FSMContext, **mid await message.answer(text="Поздравляем! Вы восстановили аккаунт", reply_markup=reply.main_menu()) +# TODO: Чтобы у пользователя был выбор менять аккаунты, то мы должны сделать клавиатуру, в которой +# будут две кнопки: Войти и Войти по паролю. @user_router.callback_query(F.data == "user:signout") async def user_signout_handler(call: types.CallbackQuery, state: FSMContext) -> None: text = ( diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index b9d541f..d018feb 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -25,6 +25,7 @@ ) +# TODO: Проверить ещё забанен ли пользователь или нет class AccessControlMiddleware(BaseMiddleware): def __init__(self, client: QueClient) -> None: self.client = client @@ -32,7 +33,7 @@ def __init__(self, client: QueClient) -> None: self.text_unauthorized = "Вы не вошли в аккаунт" # FIXME: REFACTOR ME - async def __call__( # type: ignore + async def __call__( # type: ignore self, handler: Handler, event: TelegramObject, @@ -47,11 +48,15 @@ async def __call__( # type: ignore storage = await state.get_data() try: - if event.web_app_data is not None or command == "/start" or command == "/reactivate": + if ( + event.web_app_data is not None or + command == "/start" or + command == "/reactivate" or + command == "📝 Создать аккаунт" + ): return await handler(event, data) except AttributeError: - if command == "/start" or command == "/reactivate": - return await handler(event, data) + return await handler(event, data) try: await self.on_process_event(storage=storage) From 50dc944624ea11e81f7fa844aad352b339f75370 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 9 May 2024 23:27:54 +0300 Subject: [PATCH 081/148] =?UTF-8?q?=F0=9F=92=84=20add=20profile=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/inline.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index 9f0ccf7..47f5ab3 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -4,6 +4,9 @@ from aiogram.types import ( WebAppInfo, ) +from aiogram.utils.i18n import ( + gettext as _, +) from aiogram.utils.keyboard import ( InlineKeyboardBuilder, ) @@ -20,10 +23,14 @@ def about_project_menu() -> types.InlineKeyboardMarkup: def user_menu() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.row( - types.InlineKeyboardButton(text="👤 Мой профиль", callback_data="user:profile") + types.InlineKeyboardButton(text=_("👤 Мой профиль"), callback_data="user:profile"), + ) + builder.row( + types.InlineKeyboardButton(text=_("Изменить"), callback_data="user:edit"), + types.InlineKeyboardButton(text=_("Устройства"), callback_data="user:session") ) builder.row( - types.InlineKeyboardButton(text="🔙 Выйти из аккаунта", callback_data="user:signout") + types.InlineKeyboardButton(text=_("🔙 Выйти из аккаунта"), callback_data="user:signout") ) return builder.as_markup() @@ -32,6 +39,18 @@ def user_menu() -> types.InlineKeyboardMarkup: def user_activation_menu() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() builder.row( - types.InlineKeyboardButton(text="🚀 Активировать", callback_data="user:activate") + types.InlineKeyboardButton(text=_("🚀 Активировать"), callback_data="user:activate") + ) + return builder.as_markup() + + +def profile_menu() -> types.InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.row( + types.InlineKeyboardButton(text="Изменить", callback_data="profile:edit"), + types.InlineKeyboardButton(text="Удалить", callback_data="profile:delete") + ) + builder.row( + types.InlineKeyboardButton(text="<< Вернуться назад", callback_data="back_to_user_menu") ) return builder.as_markup() From c7d682b8ae03f52f0fffe3634e3a4152dc28fad3 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 10 May 2024 12:18:22 +0300 Subject: [PATCH 082/148] =?UTF-8?q?=E2=9C=A8=20add=20rate=20limiter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 18 ++++- src/tgbot/middlewares/__init__.py | 8 +- src/tgbot/middlewares/exceptions.py | 18 +++++ src/tgbot/middlewares/throttling.py | 113 ++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 src/tgbot/middlewares/throttling.py diff --git a/bot.py b/bot.py index cdaad9b..54a7007 100644 --- a/bot.py +++ b/bot.py @@ -29,6 +29,9 @@ from que_sdk import ( QueClient, ) +from redis.asyncio import ( # type: ignore + Redis, +) from src.tgbot import ( services, @@ -43,6 +46,7 @@ from src.tgbot.middlewares import ( AccessControlMiddleware, MiscMiddleware, + ThrottlingMiddleware, ) @@ -80,14 +84,14 @@ def register_global_middlewares( dp: Dispatcher, config: Config, client: QueClient, - i18n: I18n + redis: Redis, ) -> None: logging.info("Setup middlewares...") middleware_types = [ MiscMiddleware(config, client), AccessControlMiddleware(client=client), ] - + dp.message.middleware(ThrottlingMiddleware(redis)) for middleware_type in middleware_types: dp.message.outer_middleware(middleware_type) dp.callback_query.outer_middleware(middleware_type) @@ -119,10 +123,18 @@ async def main() -> None: i18n = I18n(path=config.tg_bot.LOCALES_DIR, default_locale="ru", domain="messages") storage = get_storage(config) client = QueClient() + + redis = Redis( + host=config.redis.host, # type: ignore + port=config.redis.port, # type: ignore + decode_responses=True, + # max_connections=10, + # auto_close_connection_pool=True + ) bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN)) dp = Dispatcher(storage=storage) ConstI18nMiddleware(locale="ru", i18n=i18n).setup(router=dp) - register_global_middlewares(dp, config, client, i18n) + register_global_middlewares(dp, config, client, redis) dp.include_routers(*routers_list) await services.set_default_commands(bot, config) await on_startup(bot, config.tg_bot.admin_ids) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index 247c058..6283969 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -16,9 +16,9 @@ # from .SchedulerWare import ( # SchedulerMiddleware, # ) -# from .Throttling import ( -# ThrottlingMiddleware, # TODO: https://ru.stackoverflow.com/questions/1540655/ -# ) +from .throttling import ( + ThrottlingMiddleware, +) from .access_control import ( AccessControlMiddleware, ) @@ -33,7 +33,7 @@ # "LinkCheck", # "LogMiddleware", # "SchedulerMiddleware", - # "ThrottlingMiddleware", + "ThrottlingMiddleware", # "SupportMiddleware", # "LinkCheckMiddleware", "MiscMiddleware", diff --git a/src/tgbot/middlewares/exceptions.py b/src/tgbot/middlewares/exceptions.py index 38f204a..69a6da4 100644 --- a/src/tgbot/middlewares/exceptions.py +++ b/src/tgbot/middlewares/exceptions.py @@ -1,6 +1,24 @@ import dataclasses +import time @dataclasses.dataclass(eq=False) class CancelHandler(Exception): title: str | None = None + + +@dataclasses.dataclass(eq=False) +class Throttled(Exception): + title: str | None = None + key: str = '' + called_at: float = dataclasses.field(default_factory=time.time) + rate: float | None = None + exceeded_count: int = 0 + delta: float = 0.0 + user: int | None = None + chat: int | None = None + + def __str__(self) -> str: + return f"Rate limit exceeded! (Limit: {self.rate} s, " \ + f"exceeded: {self.exceeded_count}, " \ + f"time delta: {round(self.delta, 3)} s)" diff --git a/src/tgbot/middlewares/throttling.py b/src/tgbot/middlewares/throttling.py new file mode 100644 index 0000000..f3bd210 --- /dev/null +++ b/src/tgbot/middlewares/throttling.py @@ -0,0 +1,113 @@ +import logging +import time +from typing import ( + Any, +) + +from aiogram import ( + BaseMiddleware, + types, +) +import redis # type: ignore + +from src.tgbot.middlewares.exceptions import ( + CancelHandler, + Throttled, +) +from src.tgbot.types import ( + Handler, +) + + +class ThrottlingMiddleware(BaseMiddleware): + def __init__(self, r: redis.asyncio.Redis, limit: int = 2, key_prefix: str = 'antiflood_') -> None: + self.rate_limit = limit + self.prefix = key_prefix + self.throttle_manager = ThrottleManager(r=r) + + super().__init__() + + async def __call__( + self, + handler: Handler, + event: types.Message, + data: dict[str, Any] + ) -> Any: + + try: + await self.on_process_event(event) + except CancelHandler: + # Cancel current handler + return + + try: + result = await handler(event, data) + except Exception as e: + logging.info(e) + return + + return result + + async def on_process_event(self, event: types.Message) -> Any: + limit = self.rate_limit + key = f"{self.prefix}_message" + + try: + await self.throttle_manager.throttle(key, rate=limit, user_id=event.from_user.id, chat_id=event.chat.id) + except Throttled as t: + await self.event_throttled(event, t) + raise CancelHandler() + + @staticmethod + async def event_throttled(event: types.Message, throttled: Throttled) -> None: + delta = throttled.rate - throttled.delta + if throttled.exceeded_count <= 2: + await event.answer(f'Too many requests.\nTry again in {delta:.2f} seconds.') + + +class ThrottleManager: + bucket_keys = [ + "RATE_LIMIT", "DELTA", + "LAST_CALL", "EXCEEDED_COUNT" + ] + + def __init__(self, r: redis.asyncio.Redis): + self.redis = r + + async def throttle(self, key: str, rate: float, user_id: int, chat_id: int) -> bool: + now = time.time() + bucket_name = f'throttle_{key}_{user_id}_{chat_id}' + + data = await self.redis.hmget(bucket_name, self.bucket_keys) + data = { + k: float(v.decode()) + if isinstance(v, bytes) + else v + for k, v in zip(self.bucket_keys, data) + if v is not None + } + called = data.get("LAST_CALL", now) + delta = now - float(called) + result = delta >= rate or delta <= 0 + data["RATE_LIMIT"] = rate + data["LAST_CALL"] = now + data["DELTA"] = delta + if not result: + data["EXCEEDED_COUNT"] = int(data["EXCEEDED_COUNT"]) + data["EXCEEDED_COUNT"] += 1 + else: + data["EXCEEDED_COUNT"] = 1 + + await self.redis.hset(bucket_name, mapping=data) + + if not result: + raise Throttled( + key=key, + chat=chat_id, + user=user_id, + rate=rate, + delta=delta, + called_at=now, + exceeded_count=data.get("EXCEEDED_COUNT", 0) + ) + return result From fb3e147361c956456358b1fcae363a3e49caa83e Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 10 May 2024 12:19:49 +0300 Subject: [PATCH 083/148] =?UTF-8?q?=F0=9F=94=A7=20add=20env=20var=20for=20?= =?UTF-8?q?redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.dist b/.env.dist index af7f594..20977cf 100644 --- a/.env.dist +++ b/.env.dist @@ -7,3 +7,6 @@ MODERATE_CHAT= SIGNATURE_SECRET_KEY= USE_REDIS= + +REDIS_PORT= +REDIS_HOST= From 9ce87be5b5805018dcc789c777f2e7745467fd52 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 10 May 2024 12:20:00 +0300 Subject: [PATCH 084/148] =?UTF-8?q?=F0=9F=94=A7=20update=20RedisConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/config.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/tgbot/config.py b/src/tgbot/config.py index bacbcd8..e52cf27 100644 --- a/src/tgbot/config.py +++ b/src/tgbot/config.py @@ -21,37 +21,37 @@ class RedisConfig: Attributes ---------- - redis_pass : Optional(str) + password : Optional(str) The password used to authenticate with Redis. - redis_port : Optional(int) + port : Optional(int) The port where Redis server is listening. - redis_host : Optional(str) + host : Optional(str) The host where Redis server is located. """ - redis_pass: str | None - redis_port: int | None - redis_host: str | None + password: str | None + port: int | None + host: str | None def dsn(self) -> str: """ Constructs and returns a Redis DSN (Data Source Name) for this database configuration. """ - if self.redis_pass: - return f"redis://:{self.redis_pass}@{self.redis_host}:{self.redis_port}/0" + if self.password: + return f"redis://:{self.password}@{self.host}:{self.port}/0" else: - return f"redis://{self.redis_host}:{self.redis_port}/0" + return f"redis://{self.host}:{self.port}/0" @staticmethod def from_env(env: Env) -> "RedisConfig": """ Creates the RedisConfig object from environment variables. """ - redis_pass = env.str("REDIS_PASSWORD") - redis_port = env.int("REDIS_PORT") - redis_host = env.str("REDIS_HOST") + password = env.str("REDIS_PASSWORD", None) + port = env.int("REDIS_PORT") + host = env.str("REDIS_HOST") - return RedisConfig(redis_pass=redis_pass, redis_port=redis_port, redis_host=redis_host) + return RedisConfig(password=password, port=port, host=host) @dataclass(frozen=True, slots=True) @@ -150,4 +150,5 @@ def load_config() -> Config: return Config( tg_bot=TgBot.from_env(env), misc=Miscellaneous.from_env(env), + redis=RedisConfig.from_env(env) ) From fc3d95221bc1d591a5d4c715f55035a340cf0504 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 10 May 2024 12:50:26 +0300 Subject: [PATCH 085/148] =?UTF-8?q?=F0=9F=93=9D=20update=20badges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- frontend/README.md | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 70e6412..8b4e375 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,10 @@ enhancing its capabilities. ![AIOHTTP](https://img.shields.io/badge/aiohttp-%232C5bb4.svg?style=for-the-badge&logo=aiohttp&logoColor=white)\ ![Poetry](https://img.shields.io/badge/Poetry-%233B82F6.svg?style=for-the-badge&logo=poetry&logoColor=0B3D8D)\ ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)\ -![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white) +![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)\ +![Aiogram](https://img.shields.io/badge/aiogram--V3-blue?style=for-the-badge&logo=telegram&logoColor=white)\ +![Redis](https://img.shields.io/badge/redis-%23DD0031.svg?style=for-the-badge&logo=redis&logoColor=white)\ +![GitHub Actions](https://img.shields.io/badge/github%20actions-%232671E5.svg?style=for-the-badge&logo=githubactions&logoColor=white) ### :hammer: Development diff --git a/frontend/README.md b/frontend/README.md index f768e33..63f8295 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,8 +1,11 @@ -# React + Vite +## Frontend -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +### :alembic: Built With -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) +![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) +![Vite](https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white) +![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white) +![ESLint](https://img.shields.io/badge/ESLint-4B3263?style=for-the-badge&logo=eslint&logoColor=white) +![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white) +![CSS3](https://img.shields.io/badge/css3-%231572B6.svg?style=for-the-badge&logo=css3&logoColor=white) From 0308bbcdf02604a403dc5a8b97b8259e171d0fed Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 16 May 2024 20:46:53 +0300 Subject: [PATCH 086/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 102 +++++++++++++++++++++++++++++++++++++++++-------- pyproject.toml | 14 +++++-- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index bb2fc7d..6e9fefa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "aiogram" -version = "3.5.0" +version = "3.6.0" description = "Modern and fully asynchronous framework for Telegram Bot API" optional = false python-versions = ">=3.8" files = [ - {file = "aiogram-3.5.0-py3-none-any.whl", hash = "sha256:70b5804671b87214768a2a63f19f1457684bd0c6cb6abd23e73bb16207fd7e58"}, - {file = "aiogram-3.5.0.tar.gz", hash = "sha256:1793deb24f36a6fc7b678c31d9a831cef7972765710a47a3e139645a99facba4"}, + {file = "aiogram-3.6.0-py3-none-any.whl", hash = "sha256:f04cc42151f8198cdff038af2f62e9a481ce136c37e0cf9e3f2fe5a7e80bc4bb"}, + {file = "aiogram-3.6.0.tar.gz", hash = "sha256:66bfbdbef2ad5f4bd1a695aee84bbc3146ef7c1cc72d1bdacd3d5f8334d5934f"}, ] [package.dependencies] @@ -537,13 +537,13 @@ cron = ["capturer (>=2.4)"] [[package]] name = "cyclonedx-python-lib" -version = "7.3.2" +version = "7.3.4" description = "Python library for CycloneDX" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "cyclonedx_python_lib-7.3.2-py3-none-any.whl", hash = "sha256:fcbcbe4c99f87910a239de84f2bc0db4a429c208b4ef0436c0cbc3677ef1b790"}, - {file = "cyclonedx_python_lib-7.3.2.tar.gz", hash = "sha256:e2b12702e98da7bce89c28496c73964c2a0b128276078c1d0b398a8ba359e000"}, + {file = "cyclonedx_python_lib-7.3.4-py3-none-any.whl", hash = "sha256:8b6dc39f2281feb7fbf9b174fa5d8d3f8f7b51fd6f1d83e9b4c9bbd60ec2ab91"}, + {file = "cyclonedx_python_lib-7.3.4.tar.gz", hash = "sha256:f374855bd6b736b3a6be4eec93b5ca7f160c8282fe4ba5486518da11dbe83f1b"}, ] [package.dependencies] @@ -1474,13 +1474,13 @@ testing = ["aboutcode-toolkit (>=6.0.0)", "black", "pytest (>=6,!=7.0.0)", "pyte [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -1505,13 +1505,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.7.0" +version = "3.7.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, - {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, ] [package.dependencies] @@ -1555,6 +1555,27 @@ files = [ [package.dependencies] defusedxml = ">=0.7.1,<0.8.0" +[[package]] +name = "pyaes" +version = "1.6.1" +description = "Pure-Python Implementation of the AES block-cipher and common modes of operation" +optional = false +python-versions = "*" +files = [ + {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, +] + +[[package]] +name = "pyasn1" +version = "0.6.0" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, +] + [[package]] name = "pycodestyle" version = "2.11.1" @@ -1746,6 +1767,24 @@ pluggy = ">=0.12,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.23.6" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, + {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, +] + +[package.dependencies] +pytest = ">=7.0.0,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -1902,6 +1941,20 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "ruff" version = "0.1.15" @@ -1991,6 +2044,23 @@ files = [ [package.dependencies] mpmath = ">=0.19" +[[package]] +name = "telethon" +version = "1.35.0" +description = "Full-featured Telegram client library for Python 3" +optional = false +python-versions = ">=3.5" +files = [ + {file = "Telethon-1.35.0.tar.gz", hash = "sha256:99d7a2e161e9af1cdf03feef7a3fea6eef304a9caf620fe13aefc53099845555"}, +] + +[package.dependencies] +pyaes = "*" +rsa = "*" + +[package.extras] +cryptg = ["cryptg"] + [[package]] name = "toml" version = "0.10.2" @@ -2060,13 +2130,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.1" +version = "20.26.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, - {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, + {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, + {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, ] [package.dependencies] @@ -2195,4 +2265,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "250e0a5bd6ebae7e6018c5326fb7e27b7868f9831736cafc9f54ad0c7895bbcc" +content-hash = "a203a354e8d034917c2768aa48deddb317e6515ffb7f2e90391c65b01e25d039" diff --git a/pyproject.toml b/pyproject.toml index 9e605b5..1f8a7b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,14 +11,20 @@ python = "^3.11" aiogram = "^3.4.1" environs = "^10.2.0" better-profanity = ">=0.7.0,<0.8.0" -pytest = "^7.4.4" aiohttp = "<3.9.3" apscheduler = "^3.10.4" nudenet = "^3.0.8" -pre-commit = "^3.6.0" pydantic = "^2.4.1" redis = "^5.0.1" betterlogging = ">=0.2.1,<0.3.0" +que-sdk = "^0.1.0" +babel = "^2.15.0" + + +[tool.poetry.group.dev.dependencies] +telethon = "^1.35.0" +pytest-asyncio = "^0.23.6" +anyio = "^4.3.0" flake8 = "^7.0.0" black = "^23.12.1" mypy = "^1.8.0" @@ -26,8 +32,8 @@ isort = "^5.13.2" deptry = "^0.12.0" ruff = "^0.1.14" pip-audit = "^2.7.0" -que-sdk = "^0.1.0" -babel = "^2.15.0" +pre-commit = "^3.6.0" +pytest = "^7.4.4" [tool.black] line-length = 99 From 4f205261d27e0a6ecf7f9c6734398e834a4a5e24 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 16 May 2024 21:13:34 +0300 Subject: [PATCH 087/148] =?UTF-8?q?=E2=9C=85=20add=20e2e=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {test => tests/e2e}/__init__.py | 0 tests/e2e/conftest.py | 38 +++++++++++++++++++++++++++++++++ tests/e2e/test_help.py | 22 +++++++++++++++++++ 3 files changed, 60 insertions(+) rename {test => tests/e2e}/__init__.py (100%) create mode 100644 tests/e2e/conftest.py create mode 100644 tests/e2e/test_help.py diff --git a/test/__init__.py b/tests/e2e/__init__.py similarity index 100% rename from test/__init__.py rename to tests/e2e/__init__.py diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py new file mode 100644 index 0000000..1e52613 --- /dev/null +++ b/tests/e2e/conftest.py @@ -0,0 +1,38 @@ +import pytest +from telethon import ( + TelegramClient, +) +from telethon.sessions import ( + StringSession, +) +from telethon.tl.custom import ( + Conversation, +) + +from src.tgbot.config import ( + Miscellaneous, + load_config, +) + + +@pytest.fixture(scope="session") +def config() -> Miscellaneous: + return load_config().misc + + +@pytest.fixture(scope="session") +def anyio_backend() -> str: + return "asyncio" + + +@pytest.fixture(scope="session") +async def conv(config: Miscellaneous) -> Conversation: + client = TelegramClient( + StringSession(config.session_str), + config.api_id, + config.api_hash, + sequential_updates=True + ) + await client.connect() + async with client.conversation("@TestingDatingbottestbot", timeout=5) as conv: + yield conv diff --git a/tests/e2e/test_help.py b/tests/e2e/test_help.py new file mode 100644 index 0000000..30c2670 --- /dev/null +++ b/tests/e2e/test_help.py @@ -0,0 +1,22 @@ +from aiogram import ( + types, +) +import pytest +from telethon.tl.custom import ( + Conversation, +) + + +@pytest.mark.anyio +async def test_help(conv: Conversation) -> None: + await conv.send_message("/help") + resp: types.Message = await conv.get_response() + assert "👍 👎" in resp.raw_text + + +@pytest.mark.anyio +async def test_start_handler(conv: Conversation) -> None: + await conv.send_message('/start') + resp: types.Message = await conv.get_response() + resp.raw_text = resp.raw_text.replace(resp.raw_text[8:20], "") + assert resp.raw_text == "👋 Привет вы вошли в аккаунт" From 0a60f0d9ef15ce6a31dffcf5d6f23a5c17e2cee2 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 16 May 2024 21:14:18 +0300 Subject: [PATCH 088/148] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20add?= =?UTF-8?q?=20helpers=20func=20for=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/__init__.py | 1 + tests/utils/__init__.py | 5 +++++ tests/utils/_get_session_string.py | 12 ++++++++++++ tests/utils/helpers.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/_get_session_string.py create mode 100644 tests/utils/helpers.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e7a75d1 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# https://github.com/ehForwarderBot/efb-telegram-master/tree/master/tests diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..2159df7 --- /dev/null +++ b/tests/utils/__init__.py @@ -0,0 +1,5 @@ +from .helpers import get_button_with_text + +__all__ = ( + "get_button_with_text", +) diff --git a/tests/utils/_get_session_string.py b/tests/utils/_get_session_string.py new file mode 100644 index 0000000..c42abf9 --- /dev/null +++ b/tests/utils/_get_session_string.py @@ -0,0 +1,12 @@ +from telethon import ( + TelegramClient, +) +from telethon.sessions import ( + StringSession, +) + +api_id = 28394664 +api_hash = "59e0397dd64ba527b2761ed10f4d92de" + +with TelegramClient(StringSession(), api_id, api_hash) as client: + print("Session string:", client.session.save()) diff --git a/tests/utils/helpers.py b/tests/utils/helpers.py new file mode 100644 index 0000000..7807883 --- /dev/null +++ b/tests/utils/helpers.py @@ -0,0 +1,29 @@ +# tests/helpers.py + +from typing import ( + Optional, +) + +from telethon.tl.custom.message import ( + Message, + MessageButton, +) + + +def get_button_with_text( + message: Message, text: str, strict: bool = False +) -> Optional[MessageButton]: + """Return MessageButton from Message with text or None.""" + if message.buttons is None: + return None + + for row in message.buttons: + for button in row: + if strict: + is_match = text == button.text + else: + is_match = text in button.text + if is_match: + return button + + return None From 566a84064e76fc0ae86f85d0edd3abe918deaec9 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 16 May 2024 21:22:50 +0300 Subject: [PATCH 089/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 16 ++++++++-------- src/tgbot/handlers/start.py | 6 ++++++ src/tgbot/middlewares/access_control.py | 7 ++++--- src/tgbot/middlewares/throttling.py | 6 +++--- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/bot.py b/bot.py index 54a7007..4b3a705 100644 --- a/bot.py +++ b/bot.py @@ -29,9 +29,7 @@ from que_sdk import ( QueClient, ) -from redis.asyncio import ( # type: ignore - Redis, -) +from redis.asyncio.client import Redis # type: ignore from src.tgbot import ( services, @@ -125,12 +123,13 @@ async def main() -> None: client = QueClient() redis = Redis( - host=config.redis.host, # type: ignore - port=config.redis.port, # type: ignore + host=config.redis.host, + port=config.redis.port, decode_responses=True, - # max_connections=10, - # auto_close_connection_pool=True + max_connections=10, + auto_close_connection_pool=True ) + bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN)) dp = Dispatcher(storage=storage) ConstI18nMiddleware(locale="ru", i18n=i18n).setup(router=dp) @@ -143,6 +142,7 @@ async def main() -> None: if __name__ == "__main__": try: - asyncio.run(main()) + # FIXME: Почему-то с редисом asyncio.run(main()) не работает + asyncio.get_event_loop().run_until_complete(main()) except (KeyboardInterrupt, SystemExit): logging.error("Бот был выключен!") diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index e4a7d86..17f8e8b 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -10,6 +10,7 @@ types, ) from aiogram.filters import ( + Command, CommandStart, ) from aiogram.fsm.context import ( @@ -82,3 +83,8 @@ async def about_project_handler(message: types.Message) -> None: "Наша система полностью open-source" ) await message.answer(text=text, reply_markup=inline.about_project_menu()) + + +@start_router.message(F.text, Command("help")) +async def help_handler(message: types.Message) -> None: + await message.answer("👍 👎") diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index d018feb..bae0ea8 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -46,18 +46,19 @@ async def __call__( # type: ignore command = event.data state = data.get("state") storage = await state.get_data() - + # TODO: Добавить проверку, что это может быть админ или агент тех поддержки try: if ( event.web_app_data is not None or command == "/start" or command == "/reactivate" or - command == "📝 Создать аккаунт" + command == "📝 Создать аккаунт" or + command == "/help" ): return await handler(event, data) except AttributeError: return await handler(event, data) - + # TODO: Проверить как это работает, потому что мне кажется, что до сюда не доходит try: await self.on_process_event(storage=storage) return await handler(event, data) diff --git a/src/tgbot/middlewares/throttling.py b/src/tgbot/middlewares/throttling.py index f3bd210..c287351 100644 --- a/src/tgbot/middlewares/throttling.py +++ b/src/tgbot/middlewares/throttling.py @@ -8,7 +8,7 @@ BaseMiddleware, types, ) -import redis # type: ignore +from redis.asyncio.client import Redis # type: ignore from src.tgbot.middlewares.exceptions import ( CancelHandler, @@ -20,7 +20,7 @@ class ThrottlingMiddleware(BaseMiddleware): - def __init__(self, r: redis.asyncio.Redis, limit: int = 2, key_prefix: str = 'antiflood_') -> None: + def __init__(self, r: Redis, limit: int = 2, key_prefix: str = 'antiflood_') -> None: self.rate_limit = limit self.prefix = key_prefix self.throttle_manager = ThrottleManager(r=r) @@ -71,7 +71,7 @@ class ThrottleManager: "LAST_CALL", "EXCEEDED_COUNT" ] - def __init__(self, r: redis.asyncio.Redis): + def __init__(self, r: Redis): self.redis = r async def throttle(self, key: str, rate: float, user_id: int, chat_id: int) -> bool: From 3474dad5821905ecc7a3024be7f53c94b37f9d3f Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 16 May 2024 21:24:08 +0300 Subject: [PATCH 090/148] =?UTF-8?q?=F0=9F=94=90=20add=20env=20vars=20for?= =?UTF-8?q?=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dist | 4 ++++ src/tgbot/config.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/.env.dist b/.env.dist index 20977cf..822e4e4 100644 --- a/.env.dist +++ b/.env.dist @@ -10,3 +10,7 @@ USE_REDIS= REDIS_PORT= REDIS_HOST= + +API_ID= +API_HASH= +SESSION_STR= diff --git a/src/tgbot/config.py b/src/tgbot/config.py index e52cf27..239d4b6 100644 --- a/src/tgbot/config.py +++ b/src/tgbot/config.py @@ -91,6 +91,9 @@ def from_env(env: Env) -> "TgBot": @dataclass(frozen=True, slots=True) class Miscellaneous: secret_key: str + api_id: int | None = None + api_hash: str | None = None + session_str: str | None = None @staticmethod def from_env(env: Env) -> "Miscellaneous": @@ -98,8 +101,14 @@ def from_env(env: Env) -> "Miscellaneous": Creates the Miscellaneous object from environment variables. """ secret_key = env.str("SIGNATURE_SECRET_KEY") + api_id = env.int("API_ID") + api_hash = env.str("API_HASH") + session_str = env.str("SESSION_STR") return Miscellaneous( secret_key=secret_key, + api_id=api_id, + api_hash=api_hash, + session_str=session_str ) From 6c26256686e2b9cdb1067c618dfeaed9096f63d2 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:20:23 +0300 Subject: [PATCH 091/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20dead=20ex?= =?UTF-8?q?ternal=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/YandexMap/__init__.py | 17 ------ src/infrastructure/YandexMap/api.py | 70 ---------------------- src/infrastructure/YandexMap/exceptions.py | 14 ----- 3 files changed, 101 deletions(-) delete mode 100644 src/infrastructure/YandexMap/__init__.py delete mode 100644 src/infrastructure/YandexMap/api.py delete mode 100644 src/infrastructure/YandexMap/exceptions.py diff --git a/src/infrastructure/YandexMap/__init__.py b/src/infrastructure/YandexMap/__init__.py deleted file mode 100644 index 18fadac..0000000 --- a/src/infrastructure/YandexMap/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from .api import ( - YaClient, -) -from .exceptions import ( - InvalidKey, - NothingFound, - UnexpectedResponse, - YandexGeocoderException, -) - -__all__ = ( - "YaClient", - "InvalidKey", - "NothingFound", - "YandexGeocoderException", - "UnexpectedResponse", -) diff --git a/src/infrastructure/YandexMap/api.py b/src/infrastructure/YandexMap/api.py deleted file mode 100644 index 0b32875..0000000 --- a/src/infrastructure/YandexMap/api.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import ( - Any, -) - -import aiohttp - -from .exceptions import ( - InvalidKey, - NothingFound, - UnexpectedResponse, -) - - -class YaClient: - __slots__ = ("api_key",) - api_key: str - - def __init__(self, api_key: str) -> None: - self.api_key = api_key - - async def _request(self, address: str) -> dict[str, Any] | None: - async with aiohttp.ClientSession() as session: - async with session.get( - url="https://geocode-maps.yandex.ru/1.x/", - params=dict(format="json", apikey=self.api_key, geocode=address), - ) as response: - if response.status == 200: - a = await response.json() - return a["response"] - elif response.status == 403: - raise InvalidKey() - else: - raise UnexpectedResponse( - f"status_code={response.status}, body={response.content}" - ) - - async def coordinates(self, address: str) -> tuple[float, float]: - d = await self._request(address) - data = d["GeoObjectCollection"]["featureMember"] - - if not data: - raise NothingFound(f'Nothing found for "{address}" not found') - - coordinates = data[0]["GeoObject"]["Point"]["pos"] - longitude, latitude = tuple(coordinates.split(" ")) - return longitude, latitude - - async def address(self, longitude: float, latitude: float) -> dict[str, Any] | None: - response = await self._request(f"{longitude},{latitude}") - data = response.get("GeoObjectCollection", {}).get("featureMember", []) - - if not data: - raise NothingFound(f'Nothing found for "{longitude} {latitude}"') - - try: - address_details = data[0]["GeoObject"]["metaDataProperty"][ - "GeocoderMetaData" - ]["AddressDetails"]["Country"] - try: - locality = address_details["AdministrativeArea"]["Locality"][ - "LocalityName" - ] - except KeyError: - locality = address_details["AdministrativeArea"][ - "SubAdministrativeArea" - ]["Locality"]["LocalityName"] - - return locality - except KeyError: - return None diff --git a/src/infrastructure/YandexMap/exceptions.py b/src/infrastructure/YandexMap/exceptions.py deleted file mode 100644 index 8a50ff2..0000000 --- a/src/infrastructure/YandexMap/exceptions.py +++ /dev/null @@ -1,14 +0,0 @@ -class YandexGeocoderException(Exception): - pass - - -class UnexpectedResponse(YandexGeocoderException): - pass - - -class NothingFound(YandexGeocoderException): - pass - - -class InvalidKey(YandexGeocoderException): - pass From d85bd3b19bbbade8127e0b83c34e3fc39687175c Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:20:46 +0300 Subject: [PATCH 092/148] =?UTF-8?q?=E2=9E=95=20add=20yandex-geo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1f8a7b2..f448f89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,8 +17,9 @@ nudenet = "^3.0.8" pydantic = "^2.4.1" redis = "^5.0.1" betterlogging = ">=0.2.1,<0.3.0" -que-sdk = "^0.1.0" +que-sdk = "^0.1.7" babel = "^2.15.0" +yandex-geo = "^2.1.1" [tool.poetry.group.dev.dependencies] From 14f1cb52120915116fe818f566b1ed6d9a8cee14 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:20:59 +0300 Subject: [PATCH 093/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 382 ++++++++++++++++++++++++++-------------------------- 1 file changed, 189 insertions(+), 193 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6e9fefa..50be96b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "aiogram" -version = "3.6.0" +version = "3.7.0" description = "Modern and fully asynchronous framework for Telegram Bot API" optional = false python-versions = ">=3.8" files = [ - {file = "aiogram-3.6.0-py3-none-any.whl", hash = "sha256:f04cc42151f8198cdff038af2f62e9a481ce136c37e0cf9e3f2fe5a7e80bc4bb"}, - {file = "aiogram-3.6.0.tar.gz", hash = "sha256:66bfbdbef2ad5f4bd1a695aee84bbc3146ef7c1cc72d1bdacd3d5f8334d5934f"}, + {file = "aiogram-3.7.0-py3-none-any.whl", hash = "sha256:3b5dca84e73403e89c15ef2ecaedf2c16c53eaf97068eb74fcfd4effc1ade593"}, + {file = "aiogram-3.7.0.tar.gz", hash = "sha256:beadfc9f3ea4b65c04b58b278fab66fb08424142d56eeff3a3b6f19ff6a69038"}, ] [package.dependencies] @@ -32,10 +32,11 @@ typing-extensions = ">=4.7.0,<=5.0" [package.extras] cli = ["aiogram-cli (>=1.0.3,<1.1.0)"] -dev = ["black (>=23.10.0,<23.11.0)", "isort (>=5.12.0,<5.13.0)", "mypy (>=1.6.1,<1.7.0)", "packaging (>=23.1,<24.0)", "pre-commit (>=3.5.0,<3.6.0)", "ruff (>=0.1.1,<0.2.0)", "toml (>=0.10.2,<0.11.0)"] +dev = ["black (>=23.10.0,<23.11.0)", "isort (>=5.12.0,<5.13.0)", "motor-types (>=1.0.0b4,<1.1.0)", "mypy (>=1.6.1,<1.7.0)", "packaging (>=23.1,<24.0)", "pre-commit (>=3.5.0,<3.6.0)", "ruff (>=0.1.1,<0.2.0)", "toml (>=0.10.2,<0.11.0)"] docs = ["furo (>=2023.9.10,<2023.10.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.16.1,<2.17.0)", "pymdown-extensions (>=10.3,<11.0)", "sphinx (>=7.2.6,<7.3.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.1.0,<2.2.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.3.2a0,<0.4.0)", "towncrier (>=23.6.0,<23.7.0)"] fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)"] i18n = ["babel (>=2.13.0,<2.14.0)"] +mongo = ["motor (>=3.3.2,<3.4.0)"] proxy = ["aiohttp-socks (>=0.8.3,<0.9.0)"] redis = ["redis[hiredis] (>=5.0.1,<5.1.0)"] test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.19.0,<3.20.0)", "pytest (>=7.4.2,<7.5.0)", "pytest-aiohttp (>=1.0.5,<1.1.0)", "pytest-asyncio (>=0.21.1,<0.22.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-html (>=4.0.2,<4.1.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.12.0,<3.13.0)", "pytest-mypy (>=0.10.3,<0.11.0)", "pytz (>=2023.3,<2024.0)"] @@ -151,24 +152,24 @@ frozenlist = ">=1.1.0" [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] name = "anyio" -version = "4.3.0" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, - {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] @@ -363,13 +364,13 @@ redis = ["redis (>=2.10.5)"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -537,13 +538,13 @@ cron = ["capturer (>=2.4)"] [[package]] name = "cyclonedx-python-lib" -version = "7.3.4" +version = "7.4.0" description = "Python library for CycloneDX" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "cyclonedx_python_lib-7.3.4-py3-none-any.whl", hash = "sha256:8b6dc39f2281feb7fbf9b174fa5d8d3f8f7b51fd6f1d83e9b4c9bbd60ec2ab91"}, - {file = "cyclonedx_python_lib-7.3.4.tar.gz", hash = "sha256:f374855bd6b736b3a6be4eec93b5ca7f160c8282fe4ba5486518da11dbe83f1b"}, + {file = "cyclonedx_python_lib-7.4.0-py3-none-any.whl", hash = "sha256:fc423e7f46d772e5ded29a48cb0743233e692e5853c49b829efc0f59014efde1"}, + {file = "cyclonedx_python_lib-7.4.0.tar.gz", hash = "sha256:09b10736a7f440262578fa40f470b448de1ebf3c7a71e2ff0a4af0781d3a3b42"}, ] [package.dependencies] @@ -945,13 +946,13 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "marshmallow" -version = "3.21.2" +version = "3.21.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, - {file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, ] [package.dependencies] @@ -1224,18 +1225,15 @@ files = [ [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.1" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "nudenet" version = "3.0.8" @@ -1299,36 +1297,36 @@ files = [ [[package]] name = "onnxruntime" -version = "1.17.3" +version = "1.18.0" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.17.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d86dde9c0bb435d709e51bd25991c9fe5b9a5b168df45ce119769edc4d198b15"}, - {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d87b68bf931ac527b2d3c094ead66bb4381bac4298b65f46c54fe4d1e255865"}, - {file = "onnxruntime-1.17.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e950cf0333cf114a155f9142e71da344d2b08dfe202763a403ae81cc02ebd1"}, - {file = "onnxruntime-1.17.3-cp310-cp310-win32.whl", hash = "sha256:0962a4d0f5acebf62e1f0bf69b6e0adf16649115d8de854c1460e79972324d68"}, - {file = "onnxruntime-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:468ccb8a0faa25c681a41787b1594bf4448b0252d3efc8b62fd8b2411754340f"}, - {file = "onnxruntime-1.17.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e8cd90c1c17d13d47b89ab076471e07fb85467c01dcd87a8b8b5cdfbcb40aa51"}, - {file = "onnxruntime-1.17.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a058b39801baefe454eeb8acf3ada298c55a06a4896fafc224c02d79e9037f60"}, - {file = "onnxruntime-1.17.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f823d5eb4807007f3da7b27ca972263df6a1836e6f327384eb266274c53d05d"}, - {file = "onnxruntime-1.17.3-cp311-cp311-win32.whl", hash = "sha256:b66b23f9109e78ff2791628627a26f65cd335dcc5fbd67ff60162733a2f7aded"}, - {file = "onnxruntime-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:570760ca53a74cdd751ee49f13de70d1384dcf73d9888b8deac0917023ccda6d"}, - {file = "onnxruntime-1.17.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:77c318178d9c16e9beadd9a4070d8aaa9f57382c3f509b01709f0f010e583b99"}, - {file = "onnxruntime-1.17.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23da8469049b9759082e22c41a444f44a520a9c874b084711b6343672879f50b"}, - {file = "onnxruntime-1.17.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2949730215af3f9289008b2e31e9bbef952012a77035b911c4977edea06f3f9e"}, - {file = "onnxruntime-1.17.3-cp312-cp312-win32.whl", hash = "sha256:6c7555a49008f403fb3b19204671efb94187c5085976ae526cb625f6ede317bc"}, - {file = "onnxruntime-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:58672cf20293a1b8a277a5c6c55383359fcdf6119b2f14df6ce3b140f5001c39"}, - {file = "onnxruntime-1.17.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4395ba86e3c1e93c794a00619ef1aec597ab78f5a5039f3c6d2e9d0695c0a734"}, - {file = "onnxruntime-1.17.3-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf354c04344ec38564fc22394e1fe08aa6d70d790df00159205a0055c4a4d3f"}, - {file = "onnxruntime-1.17.3-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a94b600b7af50e922d44b95a57981e3e35103c6e3693241a03d3ca204740bbda"}, - {file = "onnxruntime-1.17.3-cp38-cp38-win32.whl", hash = "sha256:5a335c76f9c002a8586c7f38bc20fe4b3725ced21f8ead835c3e4e507e42b2ab"}, - {file = "onnxruntime-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f56a86fbd0ddc8f22696ddeda0677b041381f4168a2ca06f712ef6ec6050d6d"}, - {file = "onnxruntime-1.17.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:e0ae39f5452278cd349520c296e7de3e90d62dc5b0157c6868e2748d7f28b871"}, - {file = "onnxruntime-1.17.3-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ff2dc012bd930578aff5232afd2905bf16620815f36783a941aafabf94b3702"}, - {file = "onnxruntime-1.17.3-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf6c37483782e4785019b56e26224a25e9b9a35b849d0169ce69189867a22bb1"}, - {file = "onnxruntime-1.17.3-cp39-cp39-win32.whl", hash = "sha256:351bf5a1140dcc43bfb8d3d1a230928ee61fcd54b0ea664c8e9a889a8e3aa515"}, - {file = "onnxruntime-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:57a3de15778da8d6cc43fbf6cf038e1e746146300b5f0b1fbf01f6f795dc6440"}, + {file = "onnxruntime-1.18.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:5a3b7993a5ecf4a90f35542a4757e29b2d653da3efe06cdd3164b91167bbe10d"}, + {file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15b944623b2cdfe7f7945690bfb71c10a4531b51997c8320b84e7b0bb59af902"}, + {file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e61ce5005118064b1a0ed73ebe936bc773a102f067db34108ea6c64dd62a179"}, + {file = "onnxruntime-1.18.0-cp310-cp310-win32.whl", hash = "sha256:a4fc8a2a526eb442317d280610936a9f73deece06c7d5a91e51570860802b93f"}, + {file = "onnxruntime-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:71ed219b768cab004e5cd83e702590734f968679bf93aa488c1a7ffbe6e220c3"}, + {file = "onnxruntime-1.18.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:3d24bd623872a72a7fe2f51c103e20fcca2acfa35d48f2accd6be1ec8633d960"}, + {file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f15e41ca9b307a12550bfd2ec93f88905d9fba12bab7e578f05138ad0ae10d7b"}, + {file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f45ca2887f62a7b847d526965686b2923efa72538c89b7703c7b3fe970afd59"}, + {file = "onnxruntime-1.18.0-cp311-cp311-win32.whl", hash = "sha256:9e24d9ecc8781323d9e2eeda019b4b24babc4d624e7d53f61b1fe1a929b0511a"}, + {file = "onnxruntime-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:f8608398976ed18aef450d83777ff6f77d0b64eced1ed07a985e1a7db8ea3771"}, + {file = "onnxruntime-1.18.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f1d79941f15fc40b1ee67738b2ca26b23e0181bf0070b5fb2984f0988734698f"}, + {file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e8caf3a8565c853a22d323a3eebc2a81e3de7591981f085a4f74f7a60aab2d"}, + {file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:498d2b8380635f5e6ebc50ec1b45f181588927280f32390fb910301d234f97b8"}, + {file = "onnxruntime-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ba7cc0ce2798a386c082aaa6289ff7e9bedc3dee622eef10e74830cff200a72e"}, + {file = "onnxruntime-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:1fa175bd43f610465d5787ae06050c81f7ce09da2bf3e914eb282cb8eab363ef"}, + {file = "onnxruntime-1.18.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0284c579c20ec8b1b472dd190290a040cc68b6caec790edb960f065d15cf164a"}, + {file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d47353d036d8c380558a5643ea5f7964d9d259d31c86865bad9162c3e916d1f6"}, + {file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:885509d2b9ba4b01f08f7fa28d31ee54b6477953451c7ccf124a84625f07c803"}, + {file = "onnxruntime-1.18.0-cp38-cp38-win32.whl", hash = "sha256:8614733de3695656411d71fc2f39333170df5da6c7efd6072a59962c0bc7055c"}, + {file = "onnxruntime-1.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:47af3f803752fce23ea790fd8d130a47b2b940629f03193f780818622e856e7a"}, + {file = "onnxruntime-1.18.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:9153eb2b4d5bbab764d0aea17adadffcfc18d89b957ad191b1c3650b9930c59f"}, + {file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c7fd86eca727c989bb8d9c5104f3c45f7ee45f445cc75579ebe55d6b99dfd7c"}, + {file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac67a4de9c1326c4d87bcbfb652c923039b8a2446bb28516219236bec3b494f5"}, + {file = "onnxruntime-1.18.0-cp39-cp39-win32.whl", hash = "sha256:6ffb445816d06497df7a6dd424b20e0b2c39639e01e7fe210e247b82d15a23b9"}, + {file = "onnxruntime-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:46de6031cb6745f33f7eca9e51ab73e8c66037fb7a3b6b4560887c5b55ab5d5d"}, ] [package.dependencies] @@ -1341,18 +1339,18 @@ sympy = "*" [[package]] name = "opencv-python-headless" -version = "4.9.0.80" +version = "4.10.0.82" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"}, - {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"}, + {file = "opencv-python-headless-4.10.0.82.tar.gz", hash = "sha256:de9e742c1b9540816fbd115b0b03841d41ed0c65566b0d7a5371f98b131b7e6d"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a09ed50ba21cc5bf5d436cb0e784ad09c692d6b1d1454252772f6c8f2c7b4088"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:977a5fd21e1fe0d3d2134887db4441f8725abeae95150126302f31fcd9f548fa"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4ec6755838b0be12510bfc9ffb014779c612418f11f4f7e6f505c36124a3aa"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a37fa5276967ecf6eb297295b16b28b7a2eb3b568ca0ee469fb1a5954de298"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:94736e9b322d13db4768fd35588ad5e8995e78e207263076bfbee18aac835ad5"}, + {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:c1822fa23d1641c0249ed5eb906f4c385f7959ff1bd601a776d56b0c18914af4"}, ] [package.dependencies] @@ -1523,22 +1521,22 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "5.26.1" +version = "5.27.1" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.26.1-cp310-abi3-win32.whl", hash = "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23"}, - {file = "protobuf-5.26.1-cp310-abi3-win_amd64.whl", hash = "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33"}, - {file = "protobuf-5.26.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d"}, - {file = "protobuf-5.26.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca"}, - {file = "protobuf-5.26.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7"}, - {file = "protobuf-5.26.1-cp38-cp38-win32.whl", hash = "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc"}, - {file = "protobuf-5.26.1-cp38-cp38-win_amd64.whl", hash = "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e"}, - {file = "protobuf-5.26.1-cp39-cp39-win32.whl", hash = "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c"}, - {file = "protobuf-5.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c"}, - {file = "protobuf-5.26.1-py3-none-any.whl", hash = "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932"}, - {file = "protobuf-5.26.1.tar.gz", hash = "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51"}, + {file = "protobuf-5.27.1-cp310-abi3-win32.whl", hash = "sha256:3adc15ec0ff35c5b2d0992f9345b04a540c1e73bfee3ff1643db43cc1d734333"}, + {file = "protobuf-5.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:25236b69ab4ce1bec413fd4b68a15ef8141794427e0b4dc173e9d5d9dffc3bcd"}, + {file = "protobuf-5.27.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e38fc29d7df32e01a41cf118b5a968b1efd46b9c41ff515234e794011c78b17"}, + {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:917ed03c3eb8a2d51c3496359f5b53b4e4b7e40edfbdd3d3f34336e0eef6825a"}, + {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:ee52874a9e69a30271649be88ecbe69d374232e8fd0b4e4b0aaaa87f429f1631"}, + {file = "protobuf-5.27.1-cp38-cp38-win32.whl", hash = "sha256:7a97b9c5aed86b9ca289eb5148df6c208ab5bb6906930590961e08f097258107"}, + {file = "protobuf-5.27.1-cp38-cp38-win_amd64.whl", hash = "sha256:f6abd0f69968792da7460d3c2cfa7d94fd74e1c21df321eb6345b963f9ec3d8d"}, + {file = "protobuf-5.27.1-cp39-cp39-win32.whl", hash = "sha256:dfddb7537f789002cc4eb00752c92e67885badcc7005566f2c5de9d969d3282d"}, + {file = "protobuf-5.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:39309898b912ca6febb0084ea912e976482834f401be35840a008da12d189340"}, + {file = "protobuf-5.27.1-py3-none-any.whl", hash = "sha256:4ac7249a1530a2ed50e24201d6630125ced04b30619262f06224616e0030b6cf"}, + {file = "protobuf-5.27.1.tar.gz", hash = "sha256:df5e5b8e39b7d1c25b186ffdf9f44f40f810bbcc9d2b71d9d3156fee5a9adf15"}, ] [[package]] @@ -1589,18 +1587,18 @@ files = [ [[package]] name = "pydantic" -version = "2.7.1" +version = "2.7.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, - {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, + {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, + {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.18.2" +pydantic-core = "2.18.4" typing-extensions = ">=4.6.1" [package.extras] @@ -1608,90 +1606,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.18.2" +version = "2.18.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, - {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, - {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, - {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, - {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, - {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, - {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, - {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, - {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, - {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, - {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, - {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, - {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, - {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, - {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, - {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, - {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, - {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, - {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, - {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, - {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, - {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, - {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, - {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, - {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, - {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, - {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, - {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, - {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, - {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, - {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, + {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, + {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, + {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, + {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, + {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, + {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, + {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, + {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, + {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, + {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, + {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, + {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, + {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, + {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, + {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, + {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, + {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, + {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, + {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, + {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, + {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, + {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, + {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, + {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, + {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, + {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, + {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, + {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, + {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, ] [package.dependencies] @@ -1769,13 +1767,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.7" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] @@ -1871,13 +1869,13 @@ files = [ [[package]] name = "que-sdk" -version = "0.1.5" +version = "0.1.7" description = "" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "que_sdk-0.1.5-py3-none-any.whl", hash = "sha256:c15c1faead80108812db6b6d377c65fd3e773a857ae3bc14b0d8d736574554f5"}, - {file = "que_sdk-0.1.5.tar.gz", hash = "sha256:b6789e4d6f2b8a5fce96650c8471b1f262f1b48cd006ef340d88945a7f34c09b"}, + {file = "que_sdk-0.1.7-py3-none-any.whl", hash = "sha256:38026d9e2cd1913a5daf63ec0bc12ef347d6c25678b1ca215c5e1446134d6607"}, + {file = "que_sdk-0.1.7.tar.gz", hash = "sha256:b17dec5b118150af8c593227a060bff84176ccd664393c2aae71f30fd8811950"}, ] [package.dependencies] @@ -1886,13 +1884,13 @@ httpx = ">=0.27.0,<0.28.0" [[package]] name = "redis" -version = "5.0.4" +version = "5.0.5" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, - {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, + {file = "redis-5.0.5-py3-none-any.whl", hash = "sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada"}, + {file = "redis-5.0.5.tar.gz", hash = "sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae"}, ] [package.dependencies] @@ -1904,13 +1902,13 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1981,22 +1979,6 @@ files = [ {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] -[[package]] -name = "setuptools" -version = "69.5.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -2032,17 +2014,17 @@ files = [ [[package]] name = "sympy" -version = "1.12" +version = "1.12.1" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" files = [ - {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, - {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, + {file = "sympy-1.12.1-py3-none-any.whl", hash = "sha256:9b2cbc7f1a640289430e13d2a56f02f867a1da0190f2f99d8968c2f74da0e515"}, + {file = "sympy-1.12.1.tar.gz", hash = "sha256:2877b03f998cd8c08f07cd0de5b767119cd3ef40d09f41c30d722f6686b0fb88"}, ] [package.dependencies] -mpmath = ">=0.19" +mpmath = ">=1.1.0,<1.4.0" [[package]] name = "telethon" @@ -2074,13 +2056,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.1" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, + {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, ] [[package]] @@ -2159,6 +2141,20 @@ files = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] +[[package]] +name = "yandex-geo" +version = "2.1.1" +description = "Simple library for getting address or coordinates via Yandex geocoder" +optional = false +python-versions = "<4.0,>=3.11" +files = [ + {file = "yandex_geo-2.1.1-py3-none-any.whl", hash = "sha256:655bbc6d3d1622caf42155685d49b29150689a47cf13c96f41ff122712c245c8"}, + {file = "yandex_geo-2.1.1.tar.gz", hash = "sha256:03cc1125ee7d687b104a5e2fbe08abc636d5599d19a5cb42eaffffeeefc01e2c"}, +] + +[package.dependencies] +httpx = ">=0.27.0,<0.28.0" + [[package]] name = "yarl" version = "1.9.4" @@ -2265,4 +2261,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "a203a354e8d034917c2768aa48deddb317e6515ffb7f2e90391c65b01e25d039" +content-hash = "3f5d7090024796f99967e61ee6b4586280e0f63a359feedc6ad747f0e6f39e81" From 28ae7e75c1f5213ead8bb8e7ea8fb3322773ff05 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:23:07 +0300 Subject: [PATCH 094/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/app/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tgbot/services/app/user.py b/src/tgbot/services/app/user.py index 195efcb..c9d052d 100644 --- a/src/tgbot/services/app/user.py +++ b/src/tgbot/services/app/user.py @@ -46,7 +46,7 @@ def welcoming_message( async def get_user_data(client: QueClient, storage: dict[str, Any]) -> tuple[http.HTTPStatus, dict[str, Any]]: access_token = storage.get("access_token") - status_code, response = await client.get_user_me(access_token) + status_code, response = await client.get_user_me(access_token=access_token) return status_code, response From e2f96b3a43522bbcca9b489af48793950cdff7eb Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:26:37 +0300 Subject: [PATCH 095/148] =?UTF-8?q?=F0=9F=92=84=20update=20user=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/inline.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index 47f5ab3..83dab3c 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -20,11 +20,16 @@ def about_project_menu() -> types.InlineKeyboardMarkup: return builder.as_markup() -def user_menu() -> types.InlineKeyboardMarkup: +def user_menu(is_profile: bool) -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() - builder.row( - types.InlineKeyboardButton(text=_("👤 Мой профиль"), callback_data="user:profile"), - ) + if is_profile: + builder.row( + types.InlineKeyboardButton(text=_("👤 Мой профиль"), callback_data="user:profile"), + ) + else: + builder.row( + types.InlineKeyboardButton(text=_("Создать профиль"), callback_data="user:profile-create"), + ) builder.row( types.InlineKeyboardButton(text=_("Изменить"), callback_data="user:edit"), types.InlineKeyboardButton(text=_("Устройства"), callback_data="user:session") From 2c9bbcd62ba8f8991610c3e6e64ecc62767342f3 Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:31:29 +0300 Subject: [PATCH 096/148] =?UTF-8?q?=F0=9F=9A=A7=20add=20base=20implementat?= =?UTF-8?q?ion=20for=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index 96d23f2..3b7ce5c 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -12,6 +12,9 @@ from aiogram.types import ( CallbackQuery, ) +from que_sdk import ( + QueClient, +) from src.tgbot.filters import ( ChatTypeFilter, @@ -28,8 +31,16 @@ @profile_router.callback_query(F.data == "user:profile") async def profile_handler(call: CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: - # que_client: QueClient = middleware_data.get("que-client") - # storage = await state.get_data() + que_client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + _, profile = await que_client.get_profile(user_id=storage.get("id"), access_token=storage.get("access_token")) # await call.message.delete() # await call.message.answer_photo() - await call.message.edit_text(text="Ваш профиль: ", reply_markup=inline.profile_menu()) + await call.message.edit_text( + text="Ваш профиль: {profile_id}".format(profile_id=profile.get("id")), reply_markup=inline.profile_menu() + ) + + +@profile_router.callback_query(F.data == "user:profile-create") +async def profile_create_handler(call: CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: + await call.message.edit_text("Вы должны заполнить анкету") From e98dbb0c763effa348ad7d160e32bfdeb200a96f Mon Sep 17 00:00:00 2001 From: dromanov Date: Thu, 6 Jun 2024 22:32:31 +0300 Subject: [PATCH 097/148] =?UTF-8?q?=F0=9F=9A=B8=20diff=20buttons=20in=20de?= =?UTF-8?q?pendence=20on=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py index 2cb42de..609c74e 100644 --- a/src/tgbot/handlers/user.py +++ b/src/tgbot/handlers/user.py @@ -46,7 +46,9 @@ async def user_handler(message: types.Message, state: FSMContext, **middleware_d telegram_id=response.get("telegram_id"), days=days ) - await message.answer(text=text, reply_markup=inline.user_menu()) + profile_created = bool(response.get("profile")) + await state.update_data({"id": response.get("id")}) + await message.answer(text=text, reply_markup=inline.user_menu(is_profile=profile_created)) @user_router.message(F.text, Command("reactivate")) From 512ae1202799311f6c8b6cc4310eeced108b110e Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:17:25 +0300 Subject: [PATCH 098/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20import=20with=20is?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/utils/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 2159df7..6e4dd7b 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,4 +1,6 @@ -from .helpers import get_button_with_text +from .helpers import ( + get_button_with_text, +) __all__ = ( "get_button_with_text", From b0e2ac216103c6ac7a3e542a4952ee357b32f7c1 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:18:33 +0300 Subject: [PATCH 099/148] =?UTF-8?q?=F0=9F=9A=9A=20move=20states.py=20to=20?= =?UTF-8?q?deprecated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deprecated/states.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 deprecated/states.py diff --git a/deprecated/states.py b/deprecated/states.py new file mode 100644 index 0000000..7bfb86d --- /dev/null +++ b/deprecated/states.py @@ -0,0 +1,44 @@ +from aiogram.fsm.state import ( + State, + StatesGroup, +) + + +class AdminsActions(StatesGroup): + add = State() + delete = State() + + +class NewData(StatesGroup): + sex = State() + commentary = State() + name = State() + need_partner_sex = State() + age = State() + city = State() + nationality = State() + education = State() + town = State() + car = State() + own_home = State() + hobbies = State() + child = State() + marital = State() + photo = State() + + +class RegData(StatesGroup): + sex = State() + commentary = State() + name = State() + need_partner_sex = State() + age = State() + nationality = State() + education = State() + town = State() + car = State() + own_home = State() + hobbies = State() + child = State() + marital = State() + photo = State() From 1af0e87f5cdea78da0d32fe1e287edc43093c33a Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:22:21 +0300 Subject: [PATCH 100/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20import=20with=20is?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/__init__.py | 10 +++++++--- src/tgbot/services/app/__init__.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index 6283969..9adaf31 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -16,15 +16,18 @@ # from .SchedulerWare import ( # SchedulerMiddleware, # ) -from .throttling import ( - ThrottlingMiddleware, -) from .access_control import ( AccessControlMiddleware, ) +from .album import ( + AlbumMiddleware, +) from .config import ( MiscMiddleware, ) +from .throttling import ( + ThrottlingMiddleware, +) __all__ = ( # "AgentSupport", @@ -38,4 +41,5 @@ # "LinkCheckMiddleware", "MiscMiddleware", "AccessControlMiddleware", + "AlbumMiddleware", ) diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py index 5f2fa4c..3168c53 100644 --- a/src/tgbot/services/app/__init__.py +++ b/src/tgbot/services/app/__init__.py @@ -6,12 +6,12 @@ ) from .user import ( get_user_data, + handle_login, handle_login_t_me, handle_not_founded_user, handle_send_start_message, handle_signup, welcoming_message, - handle_login, ) __all__ = ( From b6c9ee3969ee67b4237a8eecfc9fe54352198828 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:22:27 +0300 Subject: [PATCH 101/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20import=20with=20is?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/misc/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tgbot/misc/__init__.py b/src/tgbot/misc/__init__.py index 75463fb..0f7cac6 100644 --- a/src/tgbot/misc/__init__.py +++ b/src/tgbot/misc/__init__.py @@ -1,5 +1,9 @@ from . import ( security, + states, ) -__all__ = ("security",) +__all__ = ( + "security", + "states", +) From 5e314e42e1a8f2afe3293bbfb7b5a19135ac23b8 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:24:35 +0300 Subject: [PATCH 102/148] =?UTF-8?q?=F0=9F=92=A1=20add=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/access_control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index bae0ea8..b2439ad 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -26,6 +26,7 @@ # TODO: Проверить ещё забанен ли пользователь или нет +# TODO: Надо делать один запрос и сохранять пользователя в состояние, чтобы на каждый запрос не пинговать сервер class AccessControlMiddleware(BaseMiddleware): def __init__(self, client: QueClient) -> None: self.client = client From 0f80812d88c4a2c8b3eaccccec9512fe021e40eb Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:26:15 +0300 Subject: [PATCH 103/148] =?UTF-8?q?=F0=9F=92=84=20add=20new=20keyboards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/reply.py | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index aec0cb0..9aafab5 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -34,3 +34,47 @@ def login_signup_menu() -> types.ReplyKeyboardMarkup: types.KeyboardButton(text=_("🔑 Войти в аккаунт"), web_app=WebAppInfo(url="https://floppy-phones-camp.loca.lt")), ) return builder.as_markup(resize_keyboard=True) + + +def gender_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + + builder.row( + types.KeyboardButton(text="♂ Мужской"), + types.KeyboardButton(text="♀ Женский"), + ) + + return builder.as_markup(resize_keyboard=True) + + +def interested_in_gender_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + + builder.row( + types.KeyboardButton(text="♂ Парня"), + types.KeyboardButton(text="♀ Девушку"), + ) + + return builder.as_markup(resize_keyboard=True) + + +def hobbies_menu() -> types.ReplyKeyboardMarkup: + hobbies = [ + ("Спорт", "sports"), + ("Музыка", "music"), + ("Путешествия", "travelling"), + ("Готовка", "cooking"), + ("Компьютерные игры", "gaming") + ] + builder = ReplyKeyboardBuilder() + + for hobby_name, hobby_callback in hobbies: + button_text = f"{hobby_name}" + builder.row( + types.KeyboardButton(text=button_text) + ) + builder.adjust(1, 2) + builder.row( + types.KeyboardButton(text="Подтвердить выбор"), + ) + return builder.as_markup(resize_keyboard=True) From 055633f411c16042188ab1f28bb573deab29eba1 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:27:41 +0300 Subject: [PATCH 104/148] =?UTF-8?q?=F0=9F=92=A1=20add=20todo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/inline.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index 83dab3c..2da60ad 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -59,3 +59,31 @@ def profile_menu() -> types.InlineKeyboardMarkup: types.InlineKeyboardButton(text="<< Вернуться назад", callback_data="back_to_user_menu") ) return builder.as_markup() + +# TODO: Может понадобится когда-нибудь +# class HobbiesCallbackFactory(CallbackData): +# action: str +# hobby_name: str | None = None +# +# +# def hobbies_menu() -> types.InlineKeyboardMarkup: +# hobbies = [ +# ("Спорт", "sports"), +# ("Музыка", "music"), +# ("Путешествия", "travelling"), +# ("Готовка", "cooking"), +# ("Компьютерные игры", "gaming") +# ] +# builder = InlineKeyboardBuilder() +# +# for hobby_name, hobby_callback in hobbies: +# button_text = f"⚪️ {hobby_name}" +# builder.button( +# text=button_text, +# callback_data=HobbiesCallbackFactory(action="toggle", hobby_name=hobby_callback) +# ) +# builder.adjust(2) +# builder.button( +# text="Подтвердить выбор", callback_data=HobbiesCallbackFactory(action="confirm") +# ) +# return builder.as_markup() From b9bb49c5dd4980ee72f21e923137b44c319c3394 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:28:57 +0300 Subject: [PATCH 105/148] =?UTF-8?q?=F0=9F=9A=A7=20add=20base=20form=20for?= =?UTF-8?q?=20fill=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 186 ++++++++++++++++++++++++++++++++-- 1 file changed, 180 insertions(+), 6 deletions(-) diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index 3b7ce5c..0bb159f 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -3,15 +3,20 @@ ) from aiogram import ( + Bot, F, Router, + types, +) +from aiogram.enums import ( + ContentType, +) +from aiogram.filters import ( + StateFilter, ) from aiogram.fsm.context import ( FSMContext, ) -from aiogram.types import ( - CallbackQuery, -) from que_sdk import ( QueClient, ) @@ -21,6 +26,10 @@ ) from src.tgbot.keyboards import ( inline, + reply, +) +from src.tgbot.misc import ( + states, ) profile_router = Router() @@ -30,7 +39,7 @@ @profile_router.callback_query(F.data == "user:profile") -async def profile_handler(call: CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: +async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() _, profile = await que_client.get_profile(user_id=storage.get("id"), access_token=storage.get("access_token")) @@ -42,5 +51,170 @@ async def profile_handler(call: CallbackQuery, state: FSMContext, **middleware_d @profile_router.callback_query(F.data == "user:profile-create") -async def profile_create_handler(call: CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: - await call.message.edit_text("Вы должны заполнить анкету") +async def profile_create_handler(call: types.CallbackQuery, state: FSMContext) -> None: + text = ( + "Вам нужно пройти опрос, чтобы создать профиль:\n\n" + "Напишите мне ваше имя, которое будут все видеть в анкете" + ) + # TODO: Добавить кнопку назад + использовать имя из телеграмма + await call.message.edit_text(text=text) + await state.set_state(states.RegistrationSG.first_name) + + +@profile_router.message(F.text, StateFilter(states.RegistrationSG.first_name)) +async def input_first_name_handler(message: types.Message, state: FSMContext) -> None: + await state.update_data({"first_name": message.text}) + text = "Принято! Выберите ваш гендер" + await message.answer(text=text, reply_markup=reply.gender_menu()) + await state.set_state(states.RegistrationSG.gender) + + +@profile_router.message((F.text == "♂ Мужской" or F.text == "♀ Женский"), StateFilter(states.RegistrationSG.gender)) +async def input_gender_handler(message: types.Message, state: FSMContext) -> None: + genders = { + "♂ Мужской": "male", + "♀ Женский": "female" + } + await state.update_data({"gender": genders[message.text]}) + text = ( + "Нажмите на кнопку ниже, чтобы определить ваше местоположение! Или напишите текстом" + ) + # TODO: Добавить кнопки: назад + определения местоположения + await message.answer(text=text) + await state.set_state(states.RegistrationSG.city) + + +@profile_router.message(F.text, StateFilter(states.RegistrationSG.city)) +async def input_city_handler(message: types.Message, state: FSMContext) -> None: + await state.update_data({"city": message.text}) + text = ( + "Отлично! Теперь напишите о себе" + ) + # TODO: Добавить кнопки отмены и назад + await message.answer(text=text) + await state.set_state(states.RegistrationSG.about_me) + + +@profile_router.message(F.text, StateFilter(states.RegistrationSG.about_me)) +async def input_about_me_handler(message: types.Message, state: FSMContext) -> None: + await state.update_data({"description": message.text}) + text = "Принято! Теперь выберите кого вы бы хотели найти" + await message.answer(text=text, reply_markup=reply.interested_in_gender_menu()) + await state.set_state(states.RegistrationSG.interested_in) + + +# TODO: Можно добавить в magic filter ограничение, F.text == парня и девушку +@profile_router.message(F.text, StateFilter(states.RegistrationSG.interested_in)) +async def input_interested_in_handler(message: types.Message, state: FSMContext) -> None: + genders = { + "♂ Парня": "male", + "♀ Девушку": "female", + } + await state.update_data({"interested_in": genders[message.text]}) + text = ( + "Отлично! Выберите интересные для вас занятия" + ) + await message.answer(text=text, reply_markup=reply.hobbies_menu()) + await state.set_state(states.RegistrationSG.hobbies) + + +# TODO: Добавить возможность очищать список интересов, если неправильно выбрал +@profile_router.message(F.text, StateFilter(states.RegistrationSG.hobbies)) +async def input_hobbies_handler(message: types, state: FSMContext) -> None: + storage = await state.get_data() + selected_interests = storage.get("hobbies", []) + hobby_name = message.text + text = ( + "И напоследок, пришлите одну или несколько своих фотографий" + ) + if hobby_name == "Подтвердить выбор": + await message.answer(text=text, reply_markup=types.ReplyKeyboardRemove()) + await state.set_state(states.RegistrationSG.photos) + else: + selected_interests.append(hobby_name) + await state.update_data({"hobbies": selected_interests}) + + +# TODO: Дописать +@profile_router.message( + states.RegistrationSG.photos, + F.content_type.in_([ContentType.PHOTO]) +) +async def user_handle_album( + message: types.Message, + state: FSMContext, + bot: Bot +) -> None: + print(await state.get_data()) + # tg_id = message.from_user.id + # root = "environ.Path(__file__) - 3" + # user_folder = rf"{root}/photos/{tg_id}/" + # if not os.path.exists(user_folder): + # os.mkdir(user_folder) + # file_id = message.photo[-1].file_id + + # file = await bot.get_file(file_id=file_id) + # file_name = f"photo_{file_id}.jpg" + # file_destination = os.path.join(user_folder, file_name) + # await bot.download_file(file_path=file.file_path, destination=file_destination) + # logging.info("Фотографии сохранены") + # data = await state.get_data() + # await message.answer_photo( + # photo=file_id, caption=render_template(name="profile.html", user=data), + # reply_markup=await confirm_keyboard() + # ) + # await state.update_data( + # { + # "file_id1": file_id, + # "folder": user_folder, + # "destination": file_destination + # } + # ) + + # await state.set_state(UserFormState.confirm_registration) + +# @profile_router.callback_query( +# inline.HobbiesCallbackFactory.filter(F.action == "toggle" or F.action == "confirm"), +# StateFilter(states.RegistrationSG.hobbies) +# ) +# async def choose_hobbies_handler( +# call: types.CallbackQuery, +# state: FSMContext, +# callback_data: inline.HobbiesCallbackFactory, +# bot: Bot, +# ) -> None: +# action = callback_data.action +# hobby_name = callback_data.hobby_name +# user_data = await state.get_data() +# selected_interests = user_data.get("selected_interests", []) +# if action == "toggle": +# keyboard = inline.hobbies_menu() +# +# for row_button in keyboard.inline_keyboard: +# for button in row_button: +# if hobby_name in selected_interests +# and hobby_name in button.callback_data and button.text.startswith("🔘"): +# button_text = button.text.replace("🔘", "⚪️") +# button.text = button_text +# selected_interests.remove(hobby_name) +# print(selected_interests) +# await state.update_data(selected_interests=selected_interests) +# if hobby_name in button.callback_data and hobby_name not in selected_interests: +# selected_interests.append(hobby_name) +# button_text = button.text.replace("⚪️", "🔘") +# button.text = button_text +# +# await state.update_data(selected_interests=selected_interests) +# # print(selected_interests) +# await bot.edit_message_reply_markup( +# chat_id=call.from_user.id, +# message_id=call.message.message_id, +# reply_markup=keyboard, +# ) +# elif action == "confirm": +# text = ( +# "И напоследок, пришлите мне ваши фотографии, которые будут отображаться в анкете (от 1 до 5)" +# ) +# await call.message.edit_text(text=text) +# await state.set_state(states.RegistrationSG.photos) +# print(user_data) From 5c1b6530b3404e611b627b82702da9a46b385d44 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:29:30 +0300 Subject: [PATCH 106/148] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20add?= =?UTF-8?q?=20middleware=20for=20working=20with=20media=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/album.py | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/tgbot/middlewares/album.py diff --git a/src/tgbot/middlewares/album.py b/src/tgbot/middlewares/album.py new file mode 100644 index 0000000..f9d4b32 --- /dev/null +++ b/src/tgbot/middlewares/album.py @@ -0,0 +1,47 @@ +import asyncio +from typing import ( + Any, +) + +from aiogram import ( + BaseMiddleware, + types, +) + +from src.tgbot.types import ( + Handler, +) + + +class AlbumMiddleware(BaseMiddleware): + """ + Album middleware for capturing media groups + """ + album_data: dict[Any, Any] = dict() + + def __init__(self, latency: int | float = 1): + self.latency = latency + super().__init__() + + async def __call__( + self, + handler: Handler, + message: types.Message, + data: dict[str, Any] + ) -> None: + if not message.media_group_id: + await handler(message, data) + return + try: + self.album_data[message.media_group_id].append(message) + except KeyError: + self.album_data[message.media_group_id] = [message] + await asyncio.sleep(self.latency) + + data["_is_last"] = True + data["album"] = self.album_data[message.media_group_id] + await handler(message, data) + + if message.media_group_id and data.get("_is_last"): + del self.album_data[message.media_group_id] + del data["_is_last"] From 501b83dd712a5ebc867e728d783f84a239a741cb Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:29:56 +0300 Subject: [PATCH 107/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/throttling.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tgbot/middlewares/throttling.py b/src/tgbot/middlewares/throttling.py index c287351..b381b2d 100644 --- a/src/tgbot/middlewares/throttling.py +++ b/src/tgbot/middlewares/throttling.py @@ -20,7 +20,12 @@ class ThrottlingMiddleware(BaseMiddleware): - def __init__(self, r: Redis, limit: int = 2, key_prefix: str = 'antiflood_') -> None: + def __init__( + self, + r: Redis, + limit: int = 1, + key_prefix: str = 'antiflood_', + ) -> None: self.rate_limit = limit self.prefix = key_prefix self.throttle_manager = ThrottleManager(r=r) @@ -61,7 +66,7 @@ async def on_process_event(self, event: types.Message) -> Any: @staticmethod async def event_throttled(event: types.Message, throttled: Throttled) -> None: delta = throttled.rate - throttled.delta - if throttled.exceeded_count <= 2: + if throttled.exceeded_count <= 3: await event.answer(f'Too many requests.\nTry again in {delta:.2f} seconds.') From 9f536974fdd33b9f7a275045a1cd139e06d34b26 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:31:35 +0300 Subject: [PATCH 108/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20dead=20st?= =?UTF-8?q?ates=20and=20add=20new?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/misc/states.py | 52 +++++++++------------------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/src/tgbot/misc/states.py b/src/tgbot/misc/states.py index 7bfb86d..d9b54db 100644 --- a/src/tgbot/misc/states.py +++ b/src/tgbot/misc/states.py @@ -1,44 +1,14 @@ -from aiogram.fsm.state import ( - State, - StatesGroup, +from aiogram.fsm import ( + state, ) -class AdminsActions(StatesGroup): - add = State() - delete = State() - - -class NewData(StatesGroup): - sex = State() - commentary = State() - name = State() - need_partner_sex = State() - age = State() - city = State() - nationality = State() - education = State() - town = State() - car = State() - own_home = State() - hobbies = State() - child = State() - marital = State() - photo = State() - - -class RegData(StatesGroup): - sex = State() - commentary = State() - name = State() - need_partner_sex = State() - age = State() - nationality = State() - education = State() - town = State() - car = State() - own_home = State() - hobbies = State() - child = State() - marital = State() - photo = State() +class RegistrationSG(state.StatesGroup): + first_name = state.State() + gender = state.State() + city = state.State() + birthday = state.State() + about_me = state.State() + interested_in = state.State() + hobbies = state.State() + photos = state.State() From bf053b952f56e11bef4f4cf74a5012fe503e0935 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 14 Jun 2024 14:32:33 +0300 Subject: [PATCH 109/148] =?UTF-8?q?=F0=9F=92=A1=20comment=20the=20registra?= =?UTF-8?q?tion=20of=20ThrottlingMiddleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 4b3a705..27b0f7d 100644 --- a/bot.py +++ b/bot.py @@ -41,10 +41,10 @@ from src.tgbot.handlers import ( routers_list, ) -from src.tgbot.middlewares import ( +from src.tgbot.middlewares import ( # ThrottlingMiddleware, AccessControlMiddleware, + AlbumMiddleware, MiscMiddleware, - ThrottlingMiddleware, ) @@ -89,7 +89,8 @@ def register_global_middlewares( MiscMiddleware(config, client), AccessControlMiddleware(client=client), ] - dp.message.middleware(ThrottlingMiddleware(redis)) + # dp.message.middleware(ThrottlingMiddleware(redis)) + dp.message.middleware(AlbumMiddleware()) for middleware_type in middleware_types: dp.message.outer_middleware(middleware_type) dp.callback_query.outer_middleware(middleware_type) From 4f0e7e21bcba1294776bcc42443431a0cf94b561 Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 11:22:49 +0300 Subject: [PATCH 110/148] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20deprecate=20cla?= =?UTF-8?q?ss=20that=20needs=20to=20be=20cleaned=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {src/tgbot/misc => deprecated}/async_obj.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src/tgbot/misc => deprecated}/async_obj.py (100%) diff --git a/src/tgbot/misc/async_obj.py b/deprecated/async_obj.py similarity index 100% rename from src/tgbot/misc/async_obj.py rename to deprecated/async_obj.py From be547d949f82eda2396da0ae89f64c7a93372713 Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 11:23:40 +0300 Subject: [PATCH 111/148] =?UTF-8?q?=F0=9F=9A=9A=20move=20exceptions=20from?= =?UTF-8?q?=20middlewares=20to=20misc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/{middlewares => misc}/exceptions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/tgbot/{middlewares => misc}/exceptions.py (100%) diff --git a/src/tgbot/middlewares/exceptions.py b/src/tgbot/misc/exceptions.py similarity index 100% rename from src/tgbot/middlewares/exceptions.py rename to src/tgbot/misc/exceptions.py From 9ede94bd2de0dc743291097ebb4c377d51e59e2a Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 11:24:11 +0300 Subject: [PATCH 112/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/access_control.py | 9 ++++----- src/tgbot/middlewares/throttling.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index b2439ad..038e326 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -13,6 +13,9 @@ QueClient, ) +from src.tgbot.misc.exceptions import ( + CancelHandler, +) from src.tgbot.services import ( welcoming_message, ) @@ -20,10 +23,6 @@ Handler, ) -from .exceptions import ( - CancelHandler, -) - # TODO: Проверить ещё забанен ли пользователь или нет # TODO: Надо делать один запрос и сохранять пользователя в состояние, чтобы на каждый запрос не пинговать сервер @@ -38,7 +37,7 @@ async def __call__( # type: ignore self, handler: Handler, event: TelegramObject, - data: dict[str, Any] + data: dict[str, Any], ) -> Handler | None: update = data.get("event_update") try: diff --git a/src/tgbot/middlewares/throttling.py b/src/tgbot/middlewares/throttling.py index b381b2d..a1c8528 100644 --- a/src/tgbot/middlewares/throttling.py +++ b/src/tgbot/middlewares/throttling.py @@ -10,7 +10,7 @@ ) from redis.asyncio.client import Redis # type: ignore -from src.tgbot.middlewares.exceptions import ( +from src.tgbot.misc.exceptions import ( CancelHandler, Throttled, ) From 04103547162b1ab484e7c7bd3f5e3e9c46ed9ec1 Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 16:24:46 +0300 Subject: [PATCH 113/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/access_control.py | 79 ++++++++++++++++--------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index 038e326..e0689b4 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -1,3 +1,4 @@ +import datetime import http from typing import ( Any, @@ -6,7 +7,11 @@ from aiogram import ( BaseMiddleware, ) +from aiogram.fsm.context import ( + FSMContext, +) from aiogram.types import ( + CallbackQuery, TelegramObject, ) from que_sdk import ( @@ -24,57 +29,77 @@ ) -# TODO: Проверить ещё забанен ли пользователь или нет -# TODO: Надо делать один запрос и сохранять пользователя в состояние, чтобы на каждый запрос не пинговать сервер class AccessControlMiddleware(BaseMiddleware): def __init__(self, client: QueClient) -> None: self.client = client self.text_deactivate = welcoming_message(message_type="deactivate_user") self.text_unauthorized = "Вы не вошли в аккаунт" - # FIXME: REFACTOR ME - async def __call__( # type: ignore + async def __call__( self, handler: Handler, event: TelegramObject, data: dict[str, Any], - ) -> Handler | None: + ) -> Any: update = data.get("event_update") try: command = update.message.text except AttributeError: command = event.data state = data.get("state") - storage = await state.get_data() - # TODO: Добавить проверку, что это может быть админ или агент тех поддержки - try: - if ( - event.web_app_data is not None or - command == "/start" or - command == "/reactivate" or - command == "📝 Создать аккаунт" or - command == "/help" - ): - return await handler(event, data) - except AttributeError: + + if await self._is_allowed_command(event, command): return await handler(event, data) - # TODO: Проверить как это работает, потому что мне кажется, что до сюда не доходит + try: - await self.on_process_event(storage=storage) + await self._on_process_event(state=state) return await handler(event, data) except CancelHandler as e: - if e.title == "deactivate": - await event.answer(text=self.text_deactivate) - else: - await event.answer(text=self.text_unauthorized) + await self._handle_cancel_event(event=event, e=e) - async def on_process_event(self, storage: dict[str, Any]) -> None: - status_code, response = await self.client.get_user_me( - access_token=storage.get("access_token", "") - ) + @staticmethod + async def _is_allowed_command(event: TelegramObject, command: str) -> bool: + allowed_commands = {"/start", "/reactivate", "📝 Создать аккаунт", "/help"} + if isinstance(event, CallbackQuery): + return command in allowed_commands + else: + return event.web_app_data is not None or command in allowed_commands + + async def _on_process_event(self, state: FSMContext) -> None: + storage = await state.get_data() + + if ( + storage and + isinstance(storage.get("timestamp"), datetime.datetime) and + datetime.datetime.now() - storage.get("timestamp") < datetime.timedelta(minutes=15) + ): + + status_code, response = storage.get("status_code"), storage.get("response") + else: + status_code, response = await self.client.get_user_me( + access_token=storage.get("access_token", "") + ) + if status_code != http.HTTPStatus.UNAUTHORIZED or status_code == http.HTTPStatus.BAD_REQUEST: + await state.update_data( + { + "status_code": status_code, + "response": response, + "timestamp": datetime.datetime.now() + } + ) if status_code == http.HTTPStatus.UNAUTHORIZED: raise CancelHandler("unauthorized") elif status_code == http.HTTPStatus.BAD_REQUEST: code = response.get("detail").get("code") if code == 3002: raise CancelHandler("deactivate") + + async def _handle_cancel_event(self, event: TelegramObject, e: CancelHandler) -> None: + response_texts = { + "deactivate": self.text_deactivate, + "unauthorized": self.text_unauthorized, + } + response_text = response_texts.get(e.title) + + if response_text: + await event.answer(text=response_text) From e80800de3688094ebd47f75b9a6a6f3e52590652 Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 16:25:22 +0300 Subject: [PATCH 114/148] =?UTF-8?q?=F0=9F=92=84=20add=20new=20menus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/reply.py | 65 +++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index 9aafab5..cc125f7 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -31,30 +31,44 @@ def login_signup_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() builder.row( types.KeyboardButton(text=_("📝 Создать аккаунт")), - types.KeyboardButton(text=_("🔑 Войти в аккаунт"), web_app=WebAppInfo(url="https://floppy-phones-camp.loca.lt")), + types.KeyboardButton( + text=_("🔑 Войти в аккаунт"), web_app=WebAppInfo(url="https://floppy-phones-camp.loca.lt") + ), ) return builder.as_markup(resize_keyboard=True) -def gender_menu() -> types.ReplyKeyboardMarkup: +def login_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton( + text=_("🔑 Войти в аккаунт"), web_app=WebAppInfo(url="https://floppy-phones-camp.loca.lt") + ), + ) + return builder.as_markup(resize_keyboard=True) + +def gender_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() builder.row( types.KeyboardButton(text="♂ Мужской"), types.KeyboardButton(text="♀ Женский"), ) - + builder.row( + types.KeyboardButton(text="<< Вернуться назад") + ) return builder.as_markup(resize_keyboard=True) def interested_in_gender_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() - builder.row( types.KeyboardButton(text="♂ Парня"), types.KeyboardButton(text="♀ Девушку"), ) - + builder.row( + types.KeyboardButton(text="<< Вернуться назад") + ) return builder.as_markup(resize_keyboard=True) @@ -76,5 +90,46 @@ def hobbies_menu() -> types.ReplyKeyboardMarkup: builder.adjust(1, 2) builder.row( types.KeyboardButton(text="Подтвердить выбор"), + types.KeyboardButton(text="Очистить список"), + types.KeyboardButton(text="<< Вернуться назад") ) return builder.as_markup(resize_keyboard=True) + + +def get_location_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.add( + types.KeyboardButton( + text=_("🗺 Определить автоматически"), request_location=True + )) + return builder.as_markup(resize_keyboard=True) + + +def get_photo_from_user_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton(text="Взять из профиля") + ) + builder.row( + types.KeyboardButton(text="<< Вернуться назад") + ) + return builder.as_markup(resize_keyboard=True) + + +def get_user_first_name() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton(text="Взять из телеграмма") + ) + builder.row( + types.KeyboardButton(text="<< Вернуться назад") + ) + return builder.as_markup() + + +def back_to_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton(text="<< Вернуться назад") + ) + return builder.as_markup() From 49143b0a64ba3801d77a8c72e5da683ad35209a9 Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 16:27:28 +0300 Subject: [PATCH 115/148] =?UTF-8?q?=F0=9F=9A=B8=20handling=20back=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 76 ++++++++++++++++++++++++++--------- src/tgbot/handlers/user.py | 23 +++++++---- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index 0bb159f..7722ced 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -51,51 +51,70 @@ async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middle @profile_router.callback_query(F.data == "user:profile-create") -async def profile_create_handler(call: types.CallbackQuery, state: FSMContext) -> None: +@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.gender)) +async def profile_create_handler(obj: types.CallbackQuery | types.Message, state: FSMContext) -> None: text = ( "Вам нужно пройти опрос, чтобы создать профиль:\n\n" "Напишите мне ваше имя, которое будут все видеть в анкете" ) - # TODO: Добавить кнопку назад + использовать имя из телеграмма - await call.message.edit_text(text=text) + if isinstance(obj, types.Message): + await obj.answer(text=text, reply_markup=reply.get_user_first_name()) + if isinstance(obj, types.CallbackQuery) and state is not None: + await obj.message.delete() + await obj.message.answer(text=text, reply_markup=reply.get_user_first_name()) await state.set_state(states.RegistrationSG.first_name) @profile_router.message(F.text, StateFilter(states.RegistrationSG.first_name)) -async def input_first_name_handler(message: types.Message, state: FSMContext) -> None: - await state.update_data({"first_name": message.text}) +@profile_router.callback_query(F.data == "get_name_from_tg", StateFilter(states.RegistrationSG.first_name)) +async def input_first_name_handler(obj: types.Message | types.CallbackQuery, state: FSMContext) -> None: text = "Принято! Выберите ваш гендер" - await message.answer(text=text, reply_markup=reply.gender_menu()) + first_name: str + + if isinstance(obj, types.Message): + first_name = obj.text + await state.update_data({"first_name": first_name}) + await obj.answer(text=text, reply_markup=reply.gender_menu()) + + if isinstance(obj, types.CallbackQuery): + first_name = obj.from_user.first_name + await state.update_data({"first_name": first_name}) + await obj.message.delete() + await obj.message.answer(text=text, reply_markup=reply.gender_menu()) await state.set_state(states.RegistrationSG.gender) -@profile_router.message((F.text == "♂ Мужской" or F.text == "♀ Женский"), StateFilter(states.RegistrationSG.gender)) +@profile_router.message(F.text == "♂ Мужской", StateFilter(states.RegistrationSG.gender)) +@profile_router.message(F.text == "♀ Женский", StateFilter(states.RegistrationSG.gender)) +@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.about_me)) async def input_gender_handler(message: types.Message, state: FSMContext) -> None: genders = { "♂ Мужской": "male", "♀ Женский": "female" } - await state.update_data({"gender": genders[message.text]}) + if genders.get(message.text): + await state.update_data({"gender": genders[message.text]}) text = ( "Нажмите на кнопку ниже, чтобы определить ваше местоположение! Или напишите текстом" ) - # TODO: Добавить кнопки: назад + определения местоположения - await message.answer(text=text) + # TODO: Добавить кнопку назад + await message.answer(text=text, reply_markup=reply.get_location_menu()) await state.set_state(states.RegistrationSG.city) @profile_router.message(F.text, StateFilter(states.RegistrationSG.city)) +@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.interested_in)) async def input_city_handler(message: types.Message, state: FSMContext) -> None: await state.update_data({"city": message.text}) text = ( "Отлично! Теперь напишите о себе" ) - # TODO: Добавить кнопки отмены и назад - await message.answer(text=text) + await message.answer(text=text, reply_markup=reply.back_to_menu()) await state.set_state(states.RegistrationSG.about_me) @profile_router.message(F.text, StateFilter(states.RegistrationSG.about_me)) +@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.hobbies)) async def input_about_me_handler(message: types.Message, state: FSMContext) -> None: await state.update_data({"description": message.text}) text = "Принято! Теперь выберите кого вы бы хотели найти" @@ -103,14 +122,26 @@ async def input_about_me_handler(message: types.Message, state: FSMContext) -> N await state.set_state(states.RegistrationSG.interested_in) -# TODO: Можно добавить в magic filter ограничение, F.text == парня и девушку -@profile_router.message(F.text, StateFilter(states.RegistrationSG.interested_in)) +@profile_router.message( + F.text == "♂ Парня", + StateFilter(states.RegistrationSG.interested_in), +) +@profile_router.message( + F.text == "♀ Девушку", + StateFilter(states.RegistrationSG.interested_in), +) +@profile_router.message( + F.text == "<< Вернуться назад", + StateFilter(states.RegistrationSG.photos) +) async def input_interested_in_handler(message: types.Message, state: FSMContext) -> None: genders = { "♂ Парня": "male", "♀ Девушку": "female", } - await state.update_data({"interested_in": genders[message.text]}) + gender = message.text + if genders.get(gender): + await state.update_data({"interested_in": genders[gender]}) text = ( "Отлично! Выберите интересные для вас занятия" ) @@ -118,7 +149,6 @@ async def input_interested_in_handler(message: types.Message, state: FSMContext) await state.set_state(states.RegistrationSG.hobbies) -# TODO: Добавить возможность очищать список интересов, если неправильно выбрал @profile_router.message(F.text, StateFilter(states.RegistrationSG.hobbies)) async def input_hobbies_handler(message: types, state: FSMContext) -> None: storage = await state.get_data() @@ -128,8 +158,14 @@ async def input_hobbies_handler(message: types, state: FSMContext) -> None: "И напоследок, пришлите одну или несколько своих фотографий" ) if hobby_name == "Подтвердить выбор": - await message.answer(text=text, reply_markup=types.ReplyKeyboardRemove()) - await state.set_state(states.RegistrationSG.photos) + if len(selected_interests) == 0: + await message.answer(text="Вы должны выбрать как минимум один интерес") + else: + await message.answer(text=text, reply_markup=reply.get_photo_from_user_menu()) + await state.set_state(states.RegistrationSG.photos) + elif hobby_name == "Очистить список": + selected_interests = [] + await state.update_data({"hobbies": selected_interests}) else: selected_interests.append(hobby_name) await state.update_data({"hobbies": selected_interests}) @@ -137,8 +173,8 @@ async def input_hobbies_handler(message: types, state: FSMContext) -> None: # TODO: Дописать @profile_router.message( - states.RegistrationSG.photos, - F.content_type.in_([ContentType.PHOTO]) + F.content_type.in_([ContentType.PHOTO]), + StateFilter(states.RegistrationSG.photos), ) async def user_handle_album( message: types.Message, diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py index 609c74e..7accb02 100644 --- a/src/tgbot/handlers/user.py +++ b/src/tgbot/handlers/user.py @@ -9,6 +9,7 @@ ) from aiogram.filters import ( Command, + StateFilter, ) from aiogram.fsm.context import ( FSMContext, @@ -24,6 +25,9 @@ inline, reply, ) +from src.tgbot.misc import ( + states, +) user_router = Router() user_router.message.filter( @@ -32,7 +36,9 @@ @user_router.message(F.text == "👤 Аккаунт") -async def user_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: +@user_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.first_name)) +@user_router.callback_query(F.data == "back_to_user_menu") +async def user_handler(obj: types.Message | types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() status_code, response = await que_client.get_user_me(access_token=storage.get("access_token")) @@ -48,7 +54,12 @@ async def user_handler(message: types.Message, state: FSMContext, **middleware_d ) profile_created = bool(response.get("profile")) await state.update_data({"id": response.get("id")}) - await message.answer(text=text, reply_markup=inline.user_menu(is_profile=profile_created)) + if isinstance(obj, types.Message): + if obj.text == "<< Вернуться назад": + await obj.answer(text="👤", reply_markup=reply.main_menu()) + await obj.answer(text=text, reply_markup=inline.user_menu(is_profile=profile_created)) + elif isinstance(obj, types.CallbackQuery): + await obj.message.edit_text(text=text, reply_markup=inline.user_menu(is_profile=profile_created)) @user_router.message(F.text, Command("reactivate")) @@ -61,14 +72,12 @@ async def user_activate_handler(message: types.Message, state: FSMContext, **mid await message.answer(text="Поздравляем! Вы восстановили аккаунт", reply_markup=reply.main_menu()) -# TODO: Чтобы у пользователя был выбор менять аккаунты, то мы должны сделать клавиатуру, в которой -# будут две кнопки: Войти и Войти по паролю. @user_router.callback_query(F.data == "user:signout") async def user_signout_handler(call: types.CallbackQuery, state: FSMContext) -> None: text = ( - "Вы вышли из текущей сессии, чтобы войти используйте команду /start" + "Вы вышли из текущей сессии, чтобы войти в аккаунт используйте команду /start или кнопку ниже" ) - + # TODO: Проверить работоспособность await state.clear() await call.message.delete() - await call.message.answer(text=text, reply_markup=types.ReplyKeyboardRemove()) + await call.message.answer(text=text, reply_markup=reply.login_menu()) From f1bd8cf466e976af61ed1fe021ea4a9338fbc166 Mon Sep 17 00:00:00 2001 From: dromanov Date: Sun, 16 Jun 2024 16:29:44 +0300 Subject: [PATCH 116/148] =?UTF-8?q?=F0=9F=92=AC=20update=20text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/start.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 17f8e8b..302d711 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -10,7 +10,6 @@ types, ) from aiogram.filters import ( - Command, CommandStart, ) from aiogram.fsm.context import ( @@ -80,11 +79,6 @@ async def web_app_login_handler(message: types.Message, state: FSMContext, **mid @start_router.message(F.text == __("ℹ️ О проекте")) async def about_project_handler(message: types.Message) -> None: text = _( - "Наша система полностью open-source" + "Система работает на open-source" ) await message.answer(text=text, reply_markup=inline.about_project_menu()) - - -@start_router.message(F.text, Command("help")) -async def help_handler(message: types.Message) -> None: - await message.answer("👍 👎") From 1dd60d55e70daa02d8c1d460eec7bc04baf2b960 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:17:00 +0300 Subject: [PATCH 117/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20que-sdk?= =?UTF-8?q?=20to=200.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f448f89..02c2040 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ nudenet = "^3.0.8" pydantic = "^2.4.1" redis = "^5.0.1" betterlogging = ">=0.2.1,<0.3.0" -que-sdk = "^0.1.7" +que-sdk = "^0.2.0" babel = "^2.15.0" yandex-geo = "^2.1.1" From 937052f4ff882420798cf3811326da57c84ba6fc Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:17:19 +0300 Subject: [PATCH 118/148] =?UTF-8?q?=E2=9E=95=20add=20aiogram-calendar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 02c2040..5d82ad8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ betterlogging = ">=0.2.1,<0.3.0" que-sdk = "^0.2.0" babel = "^2.15.0" yandex-geo = "^2.1.1" +aiogram-calendar = "^0.5.0" [tool.poetry.group.dev.dependencies] From f8054dfb21f4281ad62afee509db7925b962066e Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:17:36 +0300 Subject: [PATCH 119/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 302 ++++++++++++++++++++++++++++------------------------ 1 file changed, 164 insertions(+), 138 deletions(-) diff --git a/poetry.lock b/poetry.lock index 50be96b..2ceeda8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "aiogram" -version = "3.7.0" +version = "3.8.0" description = "Modern and fully asynchronous framework for Telegram Bot API" optional = false python-versions = ">=3.8" files = [ - {file = "aiogram-3.7.0-py3-none-any.whl", hash = "sha256:3b5dca84e73403e89c15ef2ecaedf2c16c53eaf97068eb74fcfd4effc1ade593"}, - {file = "aiogram-3.7.0.tar.gz", hash = "sha256:beadfc9f3ea4b65c04b58b278fab66fb08424142d56eeff3a3b6f19ff6a69038"}, + {file = "aiogram-3.8.0-py3-none-any.whl", hash = "sha256:7e30f53fb3c6420007a3a0085aeef79c00c6b788cff9f39d2198a98e599a609f"}, + {file = "aiogram-3.8.0.tar.gz", hash = "sha256:fc0737cffaf29d0f1ba1688914d42e99fc235b618185c22678a6c928e0100304"}, ] [package.dependencies] @@ -32,7 +32,7 @@ typing-extensions = ">=4.7.0,<=5.0" [package.extras] cli = ["aiogram-cli (>=1.0.3,<1.1.0)"] -dev = ["black (>=23.10.0,<23.11.0)", "isort (>=5.12.0,<5.13.0)", "motor-types (>=1.0.0b4,<1.1.0)", "mypy (>=1.6.1,<1.7.0)", "packaging (>=23.1,<24.0)", "pre-commit (>=3.5.0,<3.6.0)", "ruff (>=0.1.1,<0.2.0)", "toml (>=0.10.2,<0.11.0)"] +dev = ["black (>=24.4.2,<24.5.0)", "isort (>=5.13.2,<5.14.0)", "motor-types (>=1.0.0b4,<1.1.0)", "mypy (>=1.10.0,<1.11.0)", "packaging (>=24.1,<25.0)", "pre-commit (>=3.5,<4.0)", "ruff (>=0.4.9,<0.5.0)", "toml (>=0.10.2,<0.11.0)"] docs = ["furo (>=2023.9.10,<2023.10.0)", "markdown-include (>=0.8.1,<0.9.0)", "pygments (>=2.16.1,<2.17.0)", "pymdown-extensions (>=10.3,<11.0)", "sphinx (>=7.2.6,<7.3.0)", "sphinx-autobuild (>=2021.3.14,<2021.4.0)", "sphinx-copybutton (>=0.5.2,<0.6.0)", "sphinx-intl (>=2.1.0,<2.2.0)", "sphinx-substitution-extensions (>=2022.2.16,<2022.3.0)", "sphinxcontrib-towncrier (>=0.3.2a0,<0.4.0)", "towncrier (>=23.6.0,<23.7.0)"] fast = ["aiodns (>=3.0.0)", "uvloop (>=0.17.0)"] i18n = ["babel (>=2.13.0,<2.14.0)"] @@ -41,6 +41,23 @@ proxy = ["aiohttp-socks (>=0.8.3,<0.9.0)"] redis = ["redis[hiredis] (>=5.0.1,<5.1.0)"] test = ["aresponses (>=2.1.6,<2.2.0)", "pycryptodomex (>=3.19.0,<3.20.0)", "pytest (>=7.4.2,<7.5.0)", "pytest-aiohttp (>=1.0.5,<1.1.0)", "pytest-asyncio (>=0.21.1,<0.22.0)", "pytest-cov (>=4.1.0,<4.2.0)", "pytest-html (>=4.0.2,<4.1.0)", "pytest-lazy-fixture (>=0.6.3,<0.7.0)", "pytest-mock (>=3.12.0,<3.13.0)", "pytest-mypy (>=0.10.3,<0.11.0)", "pytz (>=2023.3,<2024.0)"] +[[package]] +name = "aiogram-calendar" +version = "0.5.0" +description = "Simple Inline Calendar & Date Selection tool for Aiogram Telegram bots" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiogram_calendar-0.5.0-py3-none-any.whl", hash = "sha256:73b1f900b946aea51e54ba0049cdd308ec775d1cb028599fcebd89fd38a0ff61"}, + {file = "aiogram_calendar-0.5.0.tar.gz", hash = "sha256:49d76bd55ccfbc5d2c1cb0866636a626d57ea09052fdf10ce62dd39162aa4225"}, +] + +[package.dependencies] +aiogram = ">=3" + +[package.extras] +dev = ["pytest", "pytest-asyncio"] + [[package]] name = "aiohttp" version = "3.9.2" @@ -538,13 +555,13 @@ cron = ["capturer (>=2.4)"] [[package]] name = "cyclonedx-python-lib" -version = "7.4.0" +version = "7.4.1" description = "Python library for CycloneDX" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "cyclonedx_python_lib-7.4.0-py3-none-any.whl", hash = "sha256:fc423e7f46d772e5ded29a48cb0743233e692e5853c49b829efc0f59014efde1"}, - {file = "cyclonedx_python_lib-7.4.0.tar.gz", hash = "sha256:09b10736a7f440262578fa40f470b448de1ebf3c7a71e2ff0a4af0781d3a3b42"}, + {file = "cyclonedx_python_lib-7.4.1-py3-none-any.whl", hash = "sha256:73bf8d5c09ad10698c75d3ce3f123c84c9aff3959d67b8b5ca9e5a7c5da43abe"}, + {file = "cyclonedx_python_lib-7.4.1.tar.gz", hash = "sha256:23bf8196e008bb8e06c1040ad2ab69492891d8a581cb2aefa36a77f199790a37"}, ] [package.dependencies] @@ -620,34 +637,34 @@ tests = ["environs[django]", "pytest"] [[package]] name = "filelock" -version = "3.14.0" +version = "3.15.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" -version = "7.0.0" +version = "7.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, - {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, + {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, + {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.11.0,<2.12.0" +pycodestyle = ">=2.12.0,<2.13.0" pyflakes = ">=3.2.0,<3.3.0" [[package]] @@ -1168,38 +1185,38 @@ files = [ [[package]] name = "mypy" -version = "1.10.0" +version = "1.10.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, - {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, - {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, - {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, - {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, - {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, - {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, - {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, - {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, - {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, - {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, - {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, - {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, - {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, - {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, - {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, - {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, - {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, - {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, ] [package.dependencies] @@ -1252,47 +1269,56 @@ opencv-python-headless = "*" [[package]] name = "numpy" -version = "1.26.4" +version = "2.0.0" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, + {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, + {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, + {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, + {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, + {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, + {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, + {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, + {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, + {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, ] [[package]] @@ -1339,18 +1365,18 @@ sympy = "*" [[package]] name = "opencv-python-headless" -version = "4.10.0.82" +version = "4.10.0.84" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.10.0.82.tar.gz", hash = "sha256:de9e742c1b9540816fbd115b0b03841d41ed0c65566b0d7a5371f98b131b7e6d"}, - {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a09ed50ba21cc5bf5d436cb0e784ad09c692d6b1d1454252772f6c8f2c7b4088"}, - {file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:977a5fd21e1fe0d3d2134887db4441f8725abeae95150126302f31fcd9f548fa"}, - {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4ec6755838b0be12510bfc9ffb014779c612418f11f4f7e6f505c36124a3aa"}, - {file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a37fa5276967ecf6eb297295b16b28b7a2eb3b568ca0ee469fb1a5954de298"}, - {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:94736e9b322d13db4768fd35588ad5e8995e78e207263076bfbee18aac835ad5"}, - {file = "opencv_python_headless-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:c1822fa23d1641c0249ed5eb906f4c385f7959ff1bd601a776d56b0c18914af4"}, + {file = "opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec"}, + {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05"}, ] [package.dependencies] @@ -1361,13 +1387,13 @@ numpy = [ [[package]] name = "packageurl-python" -version = "0.15.0" +version = "0.15.1" description = "A purl aka. Package URL parser and builder" optional = false python-versions = ">=3.7" files = [ - {file = "packageurl-python-0.15.0.tar.gz", hash = "sha256:f219b2ce6348185a27bd6a72e6fdc9f984e6c9fa157effa7cb93e341c49cdcc2"}, - {file = "packageurl_python-0.15.0-py3-none-any.whl", hash = "sha256:cdc6bd42dc30c4fc7f8f0ccb721fc31f8c33985dbffccb6e6be4c72874de48ca"}, + {file = "packageurl_python-0.15.1-py3-none-any.whl", hash = "sha256:f7a44ddb9caaf6197b3b62b890ed0be5cb15e962accab2a51db36846d5174562"}, + {file = "packageurl_python-0.15.1.tar.gz", hash = "sha256:9a37b9a7cad9a2872b4612151ba3749fd9dec90485577c14d374b6e66b7edf03"}, ] [package.extras] @@ -1378,13 +1404,13 @@ test = ["pytest"] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -1400,13 +1426,13 @@ files = [ [[package]] name = "pip" -version = "24.0" +version = "24.1.1" description = "The PyPA recommended tool for installing Python packages." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pip-24.0-py3-none-any.whl", hash = "sha256:ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc"}, - {file = "pip-24.0.tar.gz", hash = "sha256:ea9bd1a847e8c5774a5777bb398c19e80bcd4e2aa16a4b301b718fe6f593aba2"}, + {file = "pip-24.1.1-py3-none-any.whl", hash = "sha256:efca15145a95e95c00608afeab66311d40bfb73bb2266a855befd705e6bb15a0"}, + {file = "pip-24.1.1.tar.gz", hash = "sha256:5aa64f65e1952733ee0a9a9b1f52496ebdb3f3077cc46f80a16d983b58d1180a"}, ] [[package]] @@ -1521,22 +1547,22 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "5.27.1" +version = "5.27.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.27.1-cp310-abi3-win32.whl", hash = "sha256:3adc15ec0ff35c5b2d0992f9345b04a540c1e73bfee3ff1643db43cc1d734333"}, - {file = "protobuf-5.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:25236b69ab4ce1bec413fd4b68a15ef8141794427e0b4dc173e9d5d9dffc3bcd"}, - {file = "protobuf-5.27.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e38fc29d7df32e01a41cf118b5a968b1efd46b9c41ff515234e794011c78b17"}, - {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:917ed03c3eb8a2d51c3496359f5b53b4e4b7e40edfbdd3d3f34336e0eef6825a"}, - {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:ee52874a9e69a30271649be88ecbe69d374232e8fd0b4e4b0aaaa87f429f1631"}, - {file = "protobuf-5.27.1-cp38-cp38-win32.whl", hash = "sha256:7a97b9c5aed86b9ca289eb5148df6c208ab5bb6906930590961e08f097258107"}, - {file = "protobuf-5.27.1-cp38-cp38-win_amd64.whl", hash = "sha256:f6abd0f69968792da7460d3c2cfa7d94fd74e1c21df321eb6345b963f9ec3d8d"}, - {file = "protobuf-5.27.1-cp39-cp39-win32.whl", hash = "sha256:dfddb7537f789002cc4eb00752c92e67885badcc7005566f2c5de9d969d3282d"}, - {file = "protobuf-5.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:39309898b912ca6febb0084ea912e976482834f401be35840a008da12d189340"}, - {file = "protobuf-5.27.1-py3-none-any.whl", hash = "sha256:4ac7249a1530a2ed50e24201d6630125ced04b30619262f06224616e0030b6cf"}, - {file = "protobuf-5.27.1.tar.gz", hash = "sha256:df5e5b8e39b7d1c25b186ffdf9f44f40f810bbcc9d2b71d9d3156fee5a9adf15"}, + {file = "protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38"}, + {file = "protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505"}, + {file = "protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e"}, + {file = "protobuf-5.27.2-cp38-cp38-win32.whl", hash = "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863"}, + {file = "protobuf-5.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6"}, + {file = "protobuf-5.27.2-cp39-cp39-win32.whl", hash = "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca"}, + {file = "protobuf-5.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce"}, + {file = "protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470"}, + {file = "protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714"}, ] [[package]] @@ -1576,24 +1602,24 @@ files = [ [[package]] name = "pycodestyle" -version = "2.11.1" +version = "2.12.0" description = "Python style guide checker" optional = false python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, - {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, + {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, + {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, ] [[package]] name = "pydantic" -version = "2.7.3" +version = "2.7.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, - {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, + {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, + {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, ] [package.dependencies] @@ -1869,13 +1895,13 @@ files = [ [[package]] name = "que-sdk" -version = "0.1.7" +version = "0.2.0" description = "" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "que_sdk-0.1.7-py3-none-any.whl", hash = "sha256:38026d9e2cd1913a5daf63ec0bc12ef347d6c25678b1ca215c5e1446134d6607"}, - {file = "que_sdk-0.1.7.tar.gz", hash = "sha256:b17dec5b118150af8c593227a060bff84176ccd664393c2aae71f30fd8811950"}, + {file = "que_sdk-0.2.0-py3-none-any.whl", hash = "sha256:25b66792942d3952472817dee3a3f9d0f7437c85e1e1547a4af5e3abac368705"}, + {file = "que_sdk-0.2.0.tar.gz", hash = "sha256:4fc36344f5f175e668079fa8e18dbb79e9576e9289034560e647706e9605c970"}, ] [package.dependencies] @@ -1884,13 +1910,13 @@ httpx = ">=0.27.0,<0.28.0" [[package]] name = "redis" -version = "5.0.5" +version = "5.0.7" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.5-py3-none-any.whl", hash = "sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada"}, - {file = "redis-5.0.5.tar.gz", hash = "sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae"}, + {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, + {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, ] [package.dependencies] @@ -2028,12 +2054,12 @@ mpmath = ">=1.1.0,<1.4.0" [[package]] name = "telethon" -version = "1.35.0" +version = "1.36.0" description = "Full-featured Telegram client library for Python 3" optional = false python-versions = ">=3.5" files = [ - {file = "Telethon-1.35.0.tar.gz", hash = "sha256:99d7a2e161e9af1cdf03feef7a3fea6eef304a9caf620fe13aefc53099845555"}, + {file = "Telethon-1.36.0.tar.gz", hash = "sha256:11db5c7ed7e37f1272d443fb7eea0f1db580d56c6949165233946fb323aaf3a7"}, ] [package.dependencies] @@ -2056,13 +2082,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2095,13 +2121,13 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) [[package]] name = "urllib3" -version = "2.2.1" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, - {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -2112,13 +2138,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.2" +version = "20.26.3" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, - {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, ] [package.dependencies] @@ -2143,13 +2169,13 @@ files = [ [[package]] name = "yandex-geo" -version = "2.1.1" +version = "2.1.2" description = "Simple library for getting address or coordinates via Yandex geocoder" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "yandex_geo-2.1.1-py3-none-any.whl", hash = "sha256:655bbc6d3d1622caf42155685d49b29150689a47cf13c96f41ff122712c245c8"}, - {file = "yandex_geo-2.1.1.tar.gz", hash = "sha256:03cc1125ee7d687b104a5e2fbe08abc636d5599d19a5cb42eaffffeeefc01e2c"}, + {file = "yandex_geo-2.1.2-py3-none-any.whl", hash = "sha256:f3e35f67ae3dee1a3c75bf96da2ee7478b77d4329479920b680950b28e123463"}, + {file = "yandex_geo-2.1.2.tar.gz", hash = "sha256:3654fd9678b44416629dec06c54a17603a62341eaff2cb454eeb9906af871e8e"}, ] [package.dependencies] @@ -2261,4 +2287,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "3f5d7090024796f99967e61ee6b4586280e0f63a359feedc6ad747f0e6f39e81" +content-hash = "7bce95a5a55eedec89a24c5ec98f2687c9b60f2571ab606b0855a4e3ed16b7e6" From f5e7a87ed6586a53d17493aa35065cbae8255a96 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:20:35 +0300 Subject: [PATCH 120/148] =?UTF-8?q?=F0=9F=94=A7=20add=20folder=20for=20pho?= =?UTF-8?q?tos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- photos/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 photos/.gitkeep diff --git a/photos/.gitkeep b/photos/.gitkeep new file mode 100644 index 0000000..e69de29 From 49612d191d96c5ff6a033bb62eb9597c3faaf10f Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:21:22 +0300 Subject: [PATCH 121/148] =?UTF-8?q?=F0=9F=99=88=20exclude=20.gitkeep=20fro?= =?UTF-8?q?m=20photos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5efaa84..eeafb93 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,4 @@ dmypy.json # Django Settings with Secret key /django_project/django_project/settings.py photos/ +!photos/.gitkeep From 475aba2d02ce8d63dd82294e851a625bd3572463 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:24:14 +0300 Subject: [PATCH 122/148] =?UTF-8?q?=F0=9F=92=AC=20a=20more=20intuitive=20l?= =?UTF-8?q?abel=20for=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/access_control.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index e0689b4..d95c227 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -74,7 +74,7 @@ async def _on_process_event(self, state: FSMContext) -> None: datetime.datetime.now() - storage.get("timestamp") < datetime.timedelta(minutes=15) ): - status_code, response = storage.get("status_code"), storage.get("response") + status_code, response = storage.get("status_code"), storage.get("user") else: status_code, response = await self.client.get_user_me( access_token=storage.get("access_token", "") @@ -83,7 +83,7 @@ async def _on_process_event(self, state: FSMContext) -> None: await state.update_data( { "status_code": status_code, - "response": response, + "user": response, "timestamp": datetime.datetime.now() } ) From 37849b9a94a303ee298ef453167dd66612cb2690 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:24:49 +0300 Subject: [PATCH 123/148] =?UTF-8?q?=E2=9C=A8=20add=20client=20for=20workin?= =?UTF-8?q?g=20with=20geo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/middlewares/config.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tgbot/middlewares/config.py b/src/tgbot/middlewares/config.py index aa20e9c..78d1807 100644 --- a/src/tgbot/middlewares/config.py +++ b/src/tgbot/middlewares/config.py @@ -11,6 +11,9 @@ from que_sdk import ( QueClient, ) +from yandex_geocoder import ( + Client, +) from src.tgbot.config import ( Config, @@ -21,9 +24,15 @@ class MiscMiddleware(BaseMiddleware): - def __init__(self, config: Config, client: QueClient) -> None: + def __init__( + self, + config: Config, + client: QueClient, + ya_client: Client + ) -> None: self.config = config self.client = client + self.ya_client = ya_client async def __call__( self, @@ -33,4 +42,6 @@ async def __call__( ) -> Any: data["config"] = self.config data["que-client"] = self.client + data["ya_client"] = self.ya_client + return await handler(event, data) From ea9392e51ba48001fb03feddc5bb4733eb8f85b4 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:26:23 +0300 Subject: [PATCH 124/148] =?UTF-8?q?=E2=9C=A8=20update=20misc=20middleware?= =?UTF-8?q?=20for=20working=20with=20geo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bot.py b/bot.py index 27b0f7d..1d20c5e 100644 --- a/bot.py +++ b/bot.py @@ -30,6 +30,9 @@ QueClient, ) from redis.asyncio.client import Redis # type: ignore +from yandex_geocoder import ( + Client, +) from src.tgbot import ( services, @@ -83,11 +86,14 @@ def register_global_middlewares( config: Config, client: QueClient, redis: Redis, + i18n: I18n, + ya_client: Client ) -> None: logging.info("Setup middlewares...") middleware_types = [ - MiscMiddleware(config, client), + MiscMiddleware(config, client, ya_client), AccessControlMiddleware(client=client), + ConstI18nMiddleware(locale="ru", i18n=i18n) ] # dp.message.middleware(ThrottlingMiddleware(redis)) dp.message.middleware(AlbumMiddleware()) @@ -122,6 +128,7 @@ async def main() -> None: i18n = I18n(path=config.tg_bot.LOCALES_DIR, default_locale="ru", domain="messages") storage = get_storage(config) client = QueClient() + ya_client = Client(api_key=config.misc.yandex_map_api_key) # type: ignore redis = Redis( host=config.redis.host, @@ -133,8 +140,8 @@ async def main() -> None: bot = Bot(token=config.tg_bot.token, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN)) dp = Dispatcher(storage=storage) - ConstI18nMiddleware(locale="ru", i18n=i18n).setup(router=dp) - register_global_middlewares(dp, config, client, redis) + + register_global_middlewares(dp, config, client, redis, i18n, ya_client) dp.include_routers(*routers_list) await services.set_default_commands(bot, config) await on_startup(bot, config.tg_bot.admin_ids) From 77e10ea65ab8de9a0cffeb832ba80a49a92ededb Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:27:26 +0300 Subject: [PATCH 125/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 1d20c5e..6213f4e 100644 --- a/bot.py +++ b/bot.py @@ -128,7 +128,7 @@ async def main() -> None: i18n = I18n(path=config.tg_bot.LOCALES_DIR, default_locale="ru", domain="messages") storage = get_storage(config) client = QueClient() - ya_client = Client(api_key=config.misc.yandex_map_api_key) # type: ignore + ya_client = Client(api_key=config.misc.yandex_map_api_key) # type: ignore redis = Redis( host=config.redis.host, From 9bcb90d24108da13a27b705f6e1ccc90e15eaeee Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:28:10 +0300 Subject: [PATCH 126/148] =?UTF-8?q?=F0=9F=94=A7=20add=20var=20for=20workin?= =?UTF-8?q?g=20with=20geo=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/config.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/tgbot/config.py b/src/tgbot/config.py index 239d4b6..6eda80a 100644 --- a/src/tgbot/config.py +++ b/src/tgbot/config.py @@ -91,9 +91,10 @@ def from_env(env: Env) -> "TgBot": @dataclass(frozen=True, slots=True) class Miscellaneous: secret_key: str - api_id: int | None = None - api_hash: str | None = None - session_str: str | None = None + api_id: int | None + api_hash: str | None + session_str: str | None + yandex_map_api_key: str @staticmethod def from_env(env: Env) -> "Miscellaneous": @@ -104,11 +105,13 @@ def from_env(env: Env) -> "Miscellaneous": api_id = env.int("API_ID") api_hash = env.str("API_HASH") session_str = env.str("SESSION_STR") + yandex_map_api_key = env.str("YANDEX_MAP_API_KEY") return Miscellaneous( secret_key=secret_key, api_id=api_id, api_hash=api_hash, - session_str=session_str + session_str=session_str, + yandex_map_api_key=yandex_map_api_key, ) From f5d0c8e64b466ea917e213caf5792df307c59dc3 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:28:30 +0300 Subject: [PATCH 127/148] =?UTF-8?q?=E2=9C=A8=20add=20new=20field=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/misc/states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tgbot/misc/states.py b/src/tgbot/misc/states.py index d9b54db..7b12e8d 100644 --- a/src/tgbot/misc/states.py +++ b/src/tgbot/misc/states.py @@ -12,3 +12,4 @@ class RegistrationSG(state.StatesGroup): interested_in = state.State() hobbies = state.State() photos = state.State() + confirmation = state.State() From f7429eb642b6c4e201163c3aa355f34933a09031 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:28:48 +0300 Subject: [PATCH 128/148] =?UTF-8?q?=F0=9F=92=84=20add=20new=20keyboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/inline.py | 30 +----------------------------- src/tgbot/keyboards/reply.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index 2da60ad..a9f4146 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -24,7 +24,7 @@ def user_menu(is_profile: bool) -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() if is_profile: builder.row( - types.InlineKeyboardButton(text=_("👤 Мой профиль"), callback_data="user:profile"), + types.InlineKeyboardButton(text=_("👤 Профиль"), callback_data="user:profile"), ) else: builder.row( @@ -59,31 +59,3 @@ def profile_menu() -> types.InlineKeyboardMarkup: types.InlineKeyboardButton(text="<< Вернуться назад", callback_data="back_to_user_menu") ) return builder.as_markup() - -# TODO: Может понадобится когда-нибудь -# class HobbiesCallbackFactory(CallbackData): -# action: str -# hobby_name: str | None = None -# -# -# def hobbies_menu() -> types.InlineKeyboardMarkup: -# hobbies = [ -# ("Спорт", "sports"), -# ("Музыка", "music"), -# ("Путешествия", "travelling"), -# ("Готовка", "cooking"), -# ("Компьютерные игры", "gaming") -# ] -# builder = InlineKeyboardBuilder() -# -# for hobby_name, hobby_callback in hobbies: -# button_text = f"⚪️ {hobby_name}" -# builder.button( -# text=button_text, -# callback_data=HobbiesCallbackFactory(action="toggle", hobby_name=hobby_callback) -# ) -# builder.adjust(2) -# builder.button( -# text="Подтвердить выбор", callback_data=HobbiesCallbackFactory(action="confirm") -# ) -# return builder.as_markup() diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index cc125f7..08826ac 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -101,7 +101,10 @@ def get_location_menu() -> types.ReplyKeyboardMarkup: builder.add( types.KeyboardButton( text=_("🗺 Определить автоматически"), request_location=True - )) + ), + types.KeyboardButton(text="<< Вернуться назад") + ) + builder.adjust(1) return builder.as_markup(resize_keyboard=True) @@ -124,7 +127,7 @@ def get_user_first_name() -> types.ReplyKeyboardMarkup: builder.row( types.KeyboardButton(text="<< Вернуться назад") ) - return builder.as_markup() + return builder.as_markup(resize_keyboard=True) def back_to_menu() -> types.ReplyKeyboardMarkup: @@ -132,4 +135,12 @@ def back_to_menu() -> types.ReplyKeyboardMarkup: builder.row( types.KeyboardButton(text="<< Вернуться назад") ) - return builder.as_markup() + return builder.as_markup(resize_keyboard=True) + + +def confirmation_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton(text="✅ Да все хорошо!") + ) + return builder.as_markup(resize_keyboard=True) From c4bf8c47290fd5895475f1049f87dbee5d9c43b6 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 00:29:40 +0300 Subject: [PATCH 129/148] =?UTF-8?q?=F0=9F=9A=A7=20draft=20work:=20profile,?= =?UTF-8?q?=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 321 +++++++++++++++++++++++----------- src/tgbot/handlers/user.py | 9 +- 2 files changed, 226 insertions(+), 104 deletions(-) diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index 7722ced..f2474f3 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -1,3 +1,13 @@ +import asyncio +import datetime +from decimal import ( + Decimal, +) +import logging +import os +from pathlib import ( + Path, +) from typing import ( Any, ) @@ -17,9 +27,20 @@ from aiogram.fsm.context import ( FSMContext, ) +from aiogram.types import ( + ReplyKeyboardRemove, +) +from aiogram_calendar import ( + DialogCalendar, + DialogCalendarCallback, + get_user_locale, +) from que_sdk import ( QueClient, ) +from yandex_geocoder import ( + Client, +) from src.tgbot.filters import ( ChatTypeFilter, @@ -42,17 +63,18 @@ async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() + # TODO: Нужно сделать кэш на время, чтобы постоянно не отправлять запросы на сервер _, profile = await que_client.get_profile(user_id=storage.get("id"), access_token=storage.get("access_token")) # await call.message.delete() # await call.message.answer_photo() await call.message.edit_text( - text="Ваш профиль: {profile_id}".format(profile_id=profile.get("id")), reply_markup=inline.profile_menu() + text="Ваш прфиль: {profile_id}".format(profile_id=profile.get("id")), reply_markup=inline.profile_menu() ) @profile_router.callback_query(F.data == "user:profile-create") @profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.gender)) -async def profile_create_handler(obj: types.CallbackQuery | types.Message, state: FSMContext) -> None: +async def profile_create_handler(obj: types.TelegramObject, state: FSMContext) -> None: text = ( "Вам нужно пройти опрос, чтобы создать профиль:\n\n" "Напишите мне ваше имя, которое будут все видеть в анкете" @@ -66,46 +88,147 @@ async def profile_create_handler(obj: types.CallbackQuery | types.Message, state @profile_router.message(F.text, StateFilter(states.RegistrationSG.first_name)) -@profile_router.callback_query(F.data == "get_name_from_tg", StateFilter(states.RegistrationSG.first_name)) -async def input_first_name_handler(obj: types.Message | types.CallbackQuery, state: FSMContext) -> None: +async def input_first_name_handler( + obj: types.TelegramObject, + state: FSMContext, + bot: Bot +) -> None: text = "Принято! Выберите ваш гендер" first_name: str - + storage = await state.get_data() + profile = storage.get("profile", {}) if isinstance(obj, types.Message): - first_name = obj.text - await state.update_data({"first_name": first_name}) + first_name = obj.text if obj.text != "Взять из телеграмма" else obj.from_user.first_name + profile["first_name"] = first_name + await state.update_data({"profile": profile}) await obj.answer(text=text, reply_markup=reply.gender_menu()) if isinstance(obj, types.CallbackQuery): - first_name = obj.from_user.first_name - await state.update_data({"first_name": first_name}) - await obj.message.delete() + text = "Вы вернулись на шаг назад. Выберите ваш гендер" + await bot.delete_message(chat_id=obj.from_user.id, message_id=obj.message.message_id - 1) + await bot.delete_message(chat_id=obj.from_user.id, message_id=obj.message.message_id) await obj.message.answer(text=text, reply_markup=reply.gender_menu()) await state.set_state(states.RegistrationSG.gender) @profile_router.message(F.text == "♂ Мужской", StateFilter(states.RegistrationSG.gender)) @profile_router.message(F.text == "♀ Женский", StateFilter(states.RegistrationSG.gender)) -@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.about_me)) +@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.city)) async def input_gender_handler(message: types.Message, state: FSMContext) -> None: + storage = await state.get_data() + profile = storage.get("profile") genders = { "♂ Мужской": "male", "♀ Женский": "female" } + current_year = datetime.datetime.now().year + year = current_year - 18 if genders.get(message.text): - await state.update_data({"gender": genders[message.text]}) + profile["gender"] = genders[message.text] + await state.update_data({"profile": profile}) + text = ( + "Теперь выберите дату своего рождения" + ) + await message.answer(text=text, reply_markup=ReplyKeyboardRemove()) + await asyncio.sleep(0.25) + await message.answer( + text="Календарь:", + reply_markup=await DialogCalendar( + locale=await get_user_locale(message.from_user) + ).start_calendar(year=year), + ) + await state.set_state(states.RegistrationSG.birthday) + + +@profile_router.callback_query( + DialogCalendarCallback.filter(), + StateFilter(states.RegistrationSG.birthday) +) +@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.about_me)) +async def process_dialog_calendar( + obj: types.TelegramObject, + state: FSMContext, + bot: Bot, + callback_data: DialogCalendarCallback | None = None, +) -> None: text = ( "Нажмите на кнопку ниже, чтобы определить ваше местоположение! Или напишите текстом" ) - # TODO: Добавить кнопку назад - await message.answer(text=text, reply_markup=reply.get_location_menu()) - await state.set_state(states.RegistrationSG.city) + storage = await state.get_data() + profile = storage.get("profile") + if isinstance(obj, types.CallbackQuery): + call = obj + if callback_data.act == "CANCEL": + await input_first_name_handler(obj=call, state=state, bot=bot) + else: + selected, date = await DialogCalendar( + locale=await get_user_locale(call.from_user) + ).process_selection(call, callback_data) + if selected: + current_date = datetime.datetime.now().date() + if date.date() < current_date: + profile["birthday"] = date.strftime("%Y-%m-%d") + await state.update_data({"profile": profile}) + + await call.message.answer(text=text, reply_markup=reply.get_location_menu()) + await state.set_state(states.RegistrationSG.city) + else: + await call.answer(text="Выбранная вами дата превышает текущую", show_alert=True) + if isinstance(obj, types.Message): + message = obj + await message.answer(text=text, reply_markup=reply.get_location_menu()) + await state.set_state(states.RegistrationSG.city) + + +@profile_router.message( + F.content_type.in_([ContentType.LOCATION]), + StateFilter(states.RegistrationSG.city), +) +async def handle_user_location(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + ya_client: Client = middleware_data.get("ya_client") + longitude = Decimal(message.location.longitude) + latitude = Decimal(message.location.latitude) + city = await ya_client.aioaddress(longitude=longitude, latitude=latitude, level="city") + storage = await state.get_data() + profile = storage.get("profile") + profile.update({ + "city": city, + "longitude": longitude, + "latitude": latitude + }) + await state.update_data({"profile": profile}) + text = ( + "Отлично! Теперь напишите о себе" + ) + await message.answer(text=text, reply_markup=reply.back_to_menu()) + await state.set_state(states.RegistrationSG.about_me) -@profile_router.message(F.text, StateFilter(states.RegistrationSG.city)) +@profile_router.message(F.text != "✅ Да все хорошо!", StateFilter(states.RegistrationSG.city)) @profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.interested_in)) -async def input_city_handler(message: types.Message, state: FSMContext) -> None: - await state.update_data({"city": message.text}) +async def input_city_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + ya_client: Client = middleware_data.get("ya_client") + longitude, latitude = await ya_client.aiocoordinates(message.text) + city = await ya_client.aioaddress(longitude=longitude, latitude=latitude, level="city") + storage = await state.get_data() + profile = storage.get("profile") + text = ( + "Я нашел такой адрес:\n" + "*{city}*\n" + "Если все правильно, то подтвердите" + ).format(city=city) + profile.update({ + "city": city, + "longitude": longitude, + "latitude": latitude + }) + + await state.update_data({"profile": profile}) + await message.answer(text=text, reply_markup=reply.confirmation_menu()) + + +@profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.city)) +async def handle_confirmation_city(message: types.Message, state: FSMContext) -> None: text = ( "Отлично! Теперь напишите о себе" ) @@ -116,8 +239,12 @@ async def input_city_handler(message: types.Message, state: FSMContext) -> None: @profile_router.message(F.text, StateFilter(states.RegistrationSG.about_me)) @profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.hobbies)) async def input_about_me_handler(message: types.Message, state: FSMContext) -> None: - await state.update_data({"description": message.text}) + storage = await state.get_data() + profile = storage.get("profile") + profile["description"] = message.text text = "Принято! Теперь выберите кого вы бы хотели найти" + + await state.update_data({"profile": profile}) await message.answer(text=text, reply_markup=reply.interested_in_gender_menu()) await state.set_state(states.RegistrationSG.interested_in) @@ -140,8 +267,11 @@ async def input_interested_in_handler(message: types.Message, state: FSMContext) "♀ Девушку": "female", } gender = message.text + storage = await state.get_data() + profile = storage.get("profile") if genders.get(gender): - await state.update_data({"interested_in": genders[gender]}) + profile["interested_in"] = genders.get(gender) + await state.update_data({"profile": profile}) text = ( "Отлично! Выберите интересные для вас занятия" ) @@ -153,25 +283,40 @@ async def input_interested_in_handler(message: types.Message, state: FSMContext) async def input_hobbies_handler(message: types, state: FSMContext) -> None: storage = await state.get_data() selected_interests = storage.get("hobbies", []) + profile = storage.get("profile") hobby_name = message.text text = ( "И напоследок, пришлите одну или несколько своих фотографий" ) if hobby_name == "Подтвердить выбор": - if len(selected_interests) == 0: - await message.answer(text="Вы должны выбрать как минимум один интерес") - else: - await message.answer(text=text, reply_markup=reply.get_photo_from_user_menu()) - await state.set_state(states.RegistrationSG.photos) + # FIXME: + # if not selected_interests: + # await message.answer(text="Вы должны выбрать как минимум один интерес") + # else: + await message.answer(text=text, reply_markup=reply.get_photo_from_user_menu()) + await state.set_state(states.RegistrationSG.photos) elif hobby_name == "Очистить список": selected_interests = [] - await state.update_data({"hobbies": selected_interests}) + profile["hobbies"] = selected_interests + await state.update_data({"profile": profile}) else: selected_interests.append(hobby_name) - await state.update_data({"hobbies": selected_interests}) + profile["hobbies"] = selected_interests + await state.update_data({"profile": profile}) -# TODO: Дописать +# TODO: WIP +@profile_router.message( + F.text == "Взять из профиля", + StateFilter(states.RegistrationSG.photos), +) +async def get_photo_from_user(message: types.Message, state: FSMContext, bot: Bot) -> None: + telegram_id = message.from_user.id + profile_pictures = await bot.get_user_profile_photos(user_id=telegram_id, limit=1) + print(profile_pictures) + + +# https://ru.stackoverflow.com/questions/1456135/ @profile_router.message( F.content_type.in_([ContentType.PHOTO]), StateFilter(states.RegistrationSG.photos), @@ -179,78 +324,50 @@ async def input_hobbies_handler(message: types, state: FSMContext) -> None: async def user_handle_album( message: types.Message, state: FSMContext, - bot: Bot + bot: Bot, + album: list[types.Message], ) -> None: - print(await state.get_data()) - # tg_id = message.from_user.id - # root = "environ.Path(__file__) - 3" - # user_folder = rf"{root}/photos/{tg_id}/" - # if not os.path.exists(user_folder): - # os.mkdir(user_folder) - # file_id = message.photo[-1].file_id - - # file = await bot.get_file(file_id=file_id) - # file_name = f"photo_{file_id}.jpg" - # file_destination = os.path.join(user_folder, file_name) - # await bot.download_file(file_path=file.file_path, destination=file_destination) - # logging.info("Фотографии сохранены") - # data = await state.get_data() - # await message.answer_photo( - # photo=file_id, caption=render_template(name="profile.html", user=data), - # reply_markup=await confirm_keyboard() - # ) - # await state.update_data( - # { - # "file_id1": file_id, - # "folder": user_folder, - # "destination": file_destination - # } - # ) - - # await state.set_state(UserFormState.confirm_registration) - -# @profile_router.callback_query( -# inline.HobbiesCallbackFactory.filter(F.action == "toggle" or F.action == "confirm"), -# StateFilter(states.RegistrationSG.hobbies) -# ) -# async def choose_hobbies_handler( -# call: types.CallbackQuery, -# state: FSMContext, -# callback_data: inline.HobbiesCallbackFactory, -# bot: Bot, -# ) -> None: -# action = callback_data.action -# hobby_name = callback_data.hobby_name -# user_data = await state.get_data() -# selected_interests = user_data.get("selected_interests", []) -# if action == "toggle": -# keyboard = inline.hobbies_menu() -# -# for row_button in keyboard.inline_keyboard: -# for button in row_button: -# if hobby_name in selected_interests -# and hobby_name in button.callback_data and button.text.startswith("🔘"): -# button_text = button.text.replace("🔘", "⚪️") -# button.text = button_text -# selected_interests.remove(hobby_name) -# print(selected_interests) -# await state.update_data(selected_interests=selected_interests) -# if hobby_name in button.callback_data and hobby_name not in selected_interests: -# selected_interests.append(hobby_name) -# button_text = button.text.replace("⚪️", "🔘") -# button.text = button_text -# -# await state.update_data(selected_interests=selected_interests) -# # print(selected_interests) -# await bot.edit_message_reply_markup( -# chat_id=call.from_user.id, -# message_id=call.message.message_id, -# reply_markup=keyboard, -# ) -# elif action == "confirm": -# text = ( -# "И напоследок, пришлите мне ваши фотографии, которые будут отображаться в анкете (от 1 до 5)" -# ) -# await call.message.edit_text(text=text) -# await state.set_state(states.RegistrationSG.photos) -# print(user_data) + storage = await state.get_data() + profile = storage.get("profile") + tg_id = message.from_user.id + root = Path(__file__).resolve().parent.parent.parent.parent + user_folder = rf"{root}/photos/{tg_id}/" + if not os.path.exists(user_folder): + os.mkdir(user_folder) + if len(album) > 5: + await message.answer(text="Вы превысили максимальное количество фотографий. Фотографий должно быть не больше 5") + return + for msg in album: + file_id = msg.photo[-1].file_id + file = await bot.get_file(file_id=file_id) + file_name = f"photo_{msg.photo[-1].file_id}.jpg" + file_destination = os.path.join(user_folder, file_name) + await bot.download_file(file_path=file.file_path, destination=file_destination) + logging.info("Фотографии сохранены") + profile["folder_path"] = user_folder + await state.update_data({"profile": profile}) + profile = (await state.get_data()).get("profile") + text = ( + "*Подтвердите корректность данных:*\n\n" + "" + ) + + await message.answer(text=text, reply_markup=reply.confirmation_menu()) + + +async def send_photos(folder_path: str, c: QueClient, token: str) -> None: + for file_name in os.listdir(folder_path): + file_path = os.path.join(folder_path, file_name) + with open(file_path, "rb") as file: + status_code, response = await c.upload_photo(access_token=token, file=file) + if status_code != 200: + raise Exception(f"Failed to upload photo {file_name}: {response}") + + +@profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.confirmation)) +async def send_profile_data_to_server(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + que_client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + profile = storage.get("profile") + await send_photos(c=que_client, token=storage.get("access_token"), folder_path=profile.get("folder_path")) + await message.answer("Поздравляем, вы создали профиль") diff --git a/src/tgbot/handlers/user.py b/src/tgbot/handlers/user.py index 7accb02..b159036 100644 --- a/src/tgbot/handlers/user.py +++ b/src/tgbot/handlers/user.py @@ -38,10 +38,14 @@ @user_router.message(F.text == "👤 Аккаунт") @user_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.first_name)) @user_router.callback_query(F.data == "back_to_user_menu") -async def user_handler(obj: types.Message | types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: +async def user_handler(obj: types.TelegramObject, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() - status_code, response = await que_client.get_user_me(access_token=storage.get("access_token")) + user = storage.get("user") + if user is None: + _, response = await que_client.get_user_me(access_token=storage.get("access_token")) + else: + response = user days = response.get("days_since_created") text = ( "Имя пользователя: *{username}*\n" @@ -57,6 +61,7 @@ async def user_handler(obj: types.Message | types.CallbackQuery, state: FSMConte if isinstance(obj, types.Message): if obj.text == "<< Вернуться назад": await obj.answer(text="👤", reply_markup=reply.main_menu()) + await state.set_state(None) await obj.answer(text=text, reply_markup=inline.user_menu(is_profile=profile_created)) elif isinstance(obj, types.CallbackQuery): await obj.message.edit_text(text=text, reply_markup=inline.user_menu(is_profile=profile_created)) From b8ce2e9ba5809be5139dc8216018f2e6c802c1f5 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:27:50 +0300 Subject: [PATCH 130/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20que-sdk=20to=200.2?= =?UTF-8?q?.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5d82ad8..bb16cb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ nudenet = "^3.0.8" pydantic = "^2.4.1" redis = "^5.0.1" betterlogging = ">=0.2.1,<0.3.0" -que-sdk = "^0.2.0" +que-sdk = "^0.2.3" babel = "^2.15.0" yandex-geo = "^2.1.1" aiogram-calendar = "^0.5.0" From 27d486b35e6fe71c0d6703237cfcc9b127484eea Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:28:07 +0300 Subject: [PATCH 131/148] =?UTF-8?q?=E2=9E=95=20add=20numpy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bb16cb8..e1d0845 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ que-sdk = "^0.2.3" babel = "^2.15.0" yandex-geo = "^2.1.1" aiogram-calendar = "^0.5.0" - +numpy = "1.26.4" [tool.poetry.group.dev.dependencies] telethon = "^1.35.0" From 30cfe1319319577cf79d7ec903dee04749fd0bbc Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:28:24 +0300 Subject: [PATCH 132/148] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 147 ++++++++++++++++++++++++---------------------------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2ceeda8..a2817cc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1269,96 +1269,87 @@ opencv-python-headless = "*" [[package]] name = "numpy" -version = "2.0.0" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, - {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, - {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, - {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, - {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, - {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, - {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, - {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, - {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, - {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, - {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, - {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, - {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, - {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, - {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, - {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, - {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, - {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, - {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, - {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, - {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, - {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, - {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, - {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, - {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, - {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, - {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, - {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, - {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, - {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, - {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, - {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, - {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] [[package]] name = "onnxruntime" -version = "1.18.0" +version = "1.18.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.18.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:5a3b7993a5ecf4a90f35542a4757e29b2d653da3efe06cdd3164b91167bbe10d"}, - {file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15b944623b2cdfe7f7945690bfb71c10a4531b51997c8320b84e7b0bb59af902"}, - {file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e61ce5005118064b1a0ed73ebe936bc773a102f067db34108ea6c64dd62a179"}, - {file = "onnxruntime-1.18.0-cp310-cp310-win32.whl", hash = "sha256:a4fc8a2a526eb442317d280610936a9f73deece06c7d5a91e51570860802b93f"}, - {file = "onnxruntime-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:71ed219b768cab004e5cd83e702590734f968679bf93aa488c1a7ffbe6e220c3"}, - {file = "onnxruntime-1.18.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:3d24bd623872a72a7fe2f51c103e20fcca2acfa35d48f2accd6be1ec8633d960"}, - {file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f15e41ca9b307a12550bfd2ec93f88905d9fba12bab7e578f05138ad0ae10d7b"}, - {file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f45ca2887f62a7b847d526965686b2923efa72538c89b7703c7b3fe970afd59"}, - {file = "onnxruntime-1.18.0-cp311-cp311-win32.whl", hash = "sha256:9e24d9ecc8781323d9e2eeda019b4b24babc4d624e7d53f61b1fe1a929b0511a"}, - {file = "onnxruntime-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:f8608398976ed18aef450d83777ff6f77d0b64eced1ed07a985e1a7db8ea3771"}, - {file = "onnxruntime-1.18.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f1d79941f15fc40b1ee67738b2ca26b23e0181bf0070b5fb2984f0988734698f"}, - {file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e8caf3a8565c853a22d323a3eebc2a81e3de7591981f085a4f74f7a60aab2d"}, - {file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:498d2b8380635f5e6ebc50ec1b45f181588927280f32390fb910301d234f97b8"}, - {file = "onnxruntime-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ba7cc0ce2798a386c082aaa6289ff7e9bedc3dee622eef10e74830cff200a72e"}, - {file = "onnxruntime-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:1fa175bd43f610465d5787ae06050c81f7ce09da2bf3e914eb282cb8eab363ef"}, - {file = "onnxruntime-1.18.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0284c579c20ec8b1b472dd190290a040cc68b6caec790edb960f065d15cf164a"}, - {file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d47353d036d8c380558a5643ea5f7964d9d259d31c86865bad9162c3e916d1f6"}, - {file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:885509d2b9ba4b01f08f7fa28d31ee54b6477953451c7ccf124a84625f07c803"}, - {file = "onnxruntime-1.18.0-cp38-cp38-win32.whl", hash = "sha256:8614733de3695656411d71fc2f39333170df5da6c7efd6072a59962c0bc7055c"}, - {file = "onnxruntime-1.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:47af3f803752fce23ea790fd8d130a47b2b940629f03193f780818622e856e7a"}, - {file = "onnxruntime-1.18.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:9153eb2b4d5bbab764d0aea17adadffcfc18d89b957ad191b1c3650b9930c59f"}, - {file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c7fd86eca727c989bb8d9c5104f3c45f7ee45f445cc75579ebe55d6b99dfd7c"}, - {file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac67a4de9c1326c4d87bcbfb652c923039b8a2446bb28516219236bec3b494f5"}, - {file = "onnxruntime-1.18.0-cp39-cp39-win32.whl", hash = "sha256:6ffb445816d06497df7a6dd424b20e0b2c39639e01e7fe210e247b82d15a23b9"}, - {file = "onnxruntime-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:46de6031cb6745f33f7eca9e51ab73e8c66037fb7a3b6b4560887c5b55ab5d5d"}, + {file = "onnxruntime-1.18.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ef7683312393d4ba04252f1b287d964bd67d5e6048b94d2da3643986c74d80"}, + {file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc706eb1df06ddf55776e15a30519fb15dda7697f987a2bbda4962845e3cec05"}, + {file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7de69f5ced2a263531923fa68bbec52a56e793b802fcd81a03487b5e292bc3a"}, + {file = "onnxruntime-1.18.1-cp310-cp310-win32.whl", hash = "sha256:221e5b16173926e6c7de2cd437764492aa12b6811f45abd37024e7cf2ae5d7e3"}, + {file = "onnxruntime-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:75211b619275199c861ee94d317243b8a0fcde6032e5a80e1aa9ded8ab4c6060"}, + {file = "onnxruntime-1.18.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:f26582882f2dc581b809cfa41a125ba71ad9e715738ec6402418df356969774a"}, + {file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef36f3a8b768506d02be349ac303fd95d92813ba3ba70304d40c3cd5c25d6a4c"}, + {file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:170e711393e0618efa8ed27b59b9de0ee2383bd2a1f93622a97006a5ad48e434"}, + {file = "onnxruntime-1.18.1-cp311-cp311-win32.whl", hash = "sha256:9b6a33419b6949ea34e0dc009bc4470e550155b6da644571ecace4b198b0d88f"}, + {file = "onnxruntime-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c1380a9f1b7788da742c759b6a02ba771fe1ce620519b2b07309decbd1a2fe1"}, + {file = "onnxruntime-1.18.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:31bd57a55e3f983b598675dfc7e5d6f0877b70ec9864b3cc3c3e1923d0a01919"}, + {file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9e03c4ba9f734500691a4d7d5b381cd71ee2f3ce80a1154ac8f7aed99d1ecaa"}, + {file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:781aa9873640f5df24524f96f6070b8c550c66cb6af35710fd9f92a20b4bfbf6"}, + {file = "onnxruntime-1.18.1-cp312-cp312-win32.whl", hash = "sha256:3a2d9ab6254ca62adbb448222e630dc6883210f718065063518c8f93a32432be"}, + {file = "onnxruntime-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:ad93c560b1c38c27c0275ffd15cd7f45b3ad3fc96653c09ce2931179982ff204"}, + {file = "onnxruntime-1.18.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:3b55dc9d3c67626388958a3eb7ad87eb7c70f75cb0f7ff4908d27b8b42f2475c"}, + {file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f80dbcfb6763cc0177a31168b29b4bd7662545b99a19e211de8c734b657e0669"}, + {file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1ff2c61a16d6c8631796c54139bafea41ee7736077a0fc64ee8ae59432f5c58"}, + {file = "onnxruntime-1.18.1-cp38-cp38-win32.whl", hash = "sha256:219855bd272fe0c667b850bf1a1a5a02499269a70d59c48e6f27f9c8bcb25d02"}, + {file = "onnxruntime-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:afdf16aa607eb9a2c60d5ca2d5abf9f448e90c345b6b94c3ed14f4fb7e6a2d07"}, + {file = "onnxruntime-1.18.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:128df253ade673e60cea0955ec9d0e89617443a6d9ce47c2d79eb3f72a3be3de"}, + {file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9839491e77e5c5a175cab3621e184d5a88925ee297ff4c311b68897197f4cde9"}, + {file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad3187c1faff3ac15f7f0e7373ef4788c582cafa655a80fdbb33eaec88976c66"}, + {file = "onnxruntime-1.18.1-cp39-cp39-win32.whl", hash = "sha256:34657c78aa4e0b5145f9188b550ded3af626651b15017bf43d280d7e23dbf195"}, + {file = "onnxruntime-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c14fd97c3ddfa97da5feef595e2c73f14c2d0ec1d4ecbea99c8d96603c89589"}, ] [package.dependencies] coloredlogs = "*" flatbuffers = "*" -numpy = ">=1.21.6" +numpy = ">=1.21.6,<2.0" packaging = "*" protobuf = "*" sympy = "*" @@ -1894,14 +1885,14 @@ files = [ ] [[package]] -name = "que-sdk" -version = "0.2.0" +name = "que_sdk" +version = "0.2.3" description = "" optional = false python-versions = "<4.0,>=3.11" files = [ - {file = "que_sdk-0.2.0-py3-none-any.whl", hash = "sha256:25b66792942d3952472817dee3a3f9d0f7437c85e1e1547a4af5e3abac368705"}, - {file = "que_sdk-0.2.0.tar.gz", hash = "sha256:4fc36344f5f175e668079fa8e18dbb79e9576e9289034560e647706e9605c970"}, + {file = "que_sdk-0.2.3-py3-none-any.whl", hash = "sha256:867f6db3b5d397ef701536f4b3877f2886b2530a0793df977c36755799f30cfb"}, + {file = "que_sdk-0.2.3.tar.gz", hash = "sha256:afe1564d0766afc47df4233500069289f41149e83dc6303722bfd8ce72b35a09"}, ] [package.dependencies] @@ -2287,4 +2278,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "7bce95a5a55eedec89a24c5ec98f2687c9b60f2571ab606b0855a4e3ed16b7e6" +content-hash = "4157b8b428eb4606de62c62e6888611475ece1fceee9e65979a7cb13d0fb2dc5" From 94e7527e7d1a84a1e6bd443789bdcd0a69413512 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:42:48 +0300 Subject: [PATCH 133/148] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20remove=20infra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/NudeNet/__init__.py | 9 -------- src/infrastructure/NudeNet/predictor.py | 23 ------------------- .../NudeNet/profanity_filter.py | 13 ----------- src/infrastructure/__init__.py | 1 - 4 files changed, 46 deletions(-) delete mode 100644 src/infrastructure/NudeNet/__init__.py delete mode 100644 src/infrastructure/NudeNet/predictor.py delete mode 100644 src/infrastructure/NudeNet/profanity_filter.py delete mode 100644 src/infrastructure/__init__.py diff --git a/src/infrastructure/NudeNet/__init__.py b/src/infrastructure/NudeNet/__init__.py deleted file mode 100644 index 9c46a5f..0000000 --- a/src/infrastructure/NudeNet/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .predictor import ( - classification_image, - generate_censored_image, -) - -__all__ = ( - "classification_image", - "generate_censored_image" -) diff --git a/src/infrastructure/NudeNet/predictor.py b/src/infrastructure/NudeNet/predictor.py deleted file mode 100644 index 4f44a57..0000000 --- a/src/infrastructure/NudeNet/predictor.py +++ /dev/null @@ -1,23 +0,0 @@ -import pathlib -from typing import ( - Any, -) - -from nudenet import ( - NudeDetector, -) - -detector = NudeDetector() - - -async def classification_image(image_path: str | pathlib.Path) -> list[dict[str, Any]]: - return detector.detect(image_path=image_path) - - -async def generate_censored_image( - image_path: str | pathlib.Path, out_path: str | pathlib.Path -) -> None: - detector.censor( - image_path=image_path, - output_path=out_path, - ) diff --git a/src/infrastructure/NudeNet/profanity_filter.py b/src/infrastructure/NudeNet/profanity_filter.py deleted file mode 100644 index 9a39be8..0000000 --- a/src/infrastructure/NudeNet/profanity_filter.py +++ /dev/null @@ -1,13 +0,0 @@ -from better_profanity import ( - profanity, -) - - -def censored_message(message: str) -> str: - """ - Get censored message. - - This function only works with English words. - """ - censored_text = profanity.censor(message) - return censored_text diff --git a/src/infrastructure/__init__.py b/src/infrastructure/__init__.py deleted file mode 100644 index ac8c604..0000000 --- a/src/infrastructure/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# TODO: Вынести модуль infra на серверную часть From e64b043b7f391bede40aec15bb0a202fc2cf30b6 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:43:18 +0300 Subject: [PATCH 134/148] =?UTF-8?q?=E2=9C=A8=20add=20image=20classificatio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/app/__init__.py | 4 ++++ src/tgbot/services/app/nude_classifier.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/tgbot/services/app/nude_classifier.py diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py index 3168c53..6600f24 100644 --- a/src/tgbot/services/app/__init__.py +++ b/src/tgbot/services/app/__init__.py @@ -1,6 +1,9 @@ from . import ( broadcaster, ) +from .nude_classifier import ( + classification_image, +) from .set_bot_commands import ( set_default_commands, ) @@ -24,4 +27,5 @@ "handle_send_start_message", "set_default_commands", "handle_login", + "classification_image", ) diff --git a/src/tgbot/services/app/nude_classifier.py b/src/tgbot/services/app/nude_classifier.py new file mode 100644 index 0000000..8cb7a9d --- /dev/null +++ b/src/tgbot/services/app/nude_classifier.py @@ -0,0 +1,15 @@ +import pathlib + +from nudenet import ( + NudeDetector, +) + + +def classification_image(image_path: str | pathlib.Path) -> bool: + detector = NudeDetector() + classes = detector.detect(image_path=image_path) + censor_classes = ["FEMALE_BREAST_EXPOSED", "FEMALE_GENITALIA_EXPOSED", "ANUS_EXPOSED", "MALE_GENITALIA_EXPOSED"] + for class_ in classes: + if class_.get("class") in censor_classes and class_.get("score") >= 0.5: + return True + return False From 23c9ebddf8beecdba2b1b5833a26e1d405a486aa Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:44:54 +0300 Subject: [PATCH 135/148] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20add?= =?UTF-8?q?=20consts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 18 ++++++------------ src/tgbot/misc/const.py | 9 +++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 src/tgbot/misc/const.py diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index f2474f3..01f19de 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -50,6 +50,7 @@ reply, ) from src.tgbot.misc import ( + const, states, ) @@ -117,14 +118,11 @@ async def input_first_name_handler( async def input_gender_handler(message: types.Message, state: FSMContext) -> None: storage = await state.get_data() profile = storage.get("profile") - genders = { - "♂ Мужской": "male", - "♀ Женский": "female" - } + current_year = datetime.datetime.now().year year = current_year - 18 - if genders.get(message.text): - profile["gender"] = genders[message.text] + if const.genders.get(message.text): + profile["gender"] = const.genders[message.text] await state.update_data({"profile": profile}) text = ( "Теперь выберите дату своего рождения" @@ -262,15 +260,11 @@ async def input_about_me_handler(message: types.Message, state: FSMContext) -> N StateFilter(states.RegistrationSG.photos) ) async def input_interested_in_handler(message: types.Message, state: FSMContext) -> None: - genders = { - "♂ Парня": "male", - "♀ Девушку": "female", - } gender = message.text storage = await state.get_data() profile = storage.get("profile") - if genders.get(gender): - profile["interested_in"] = genders.get(gender) + if const.interested_genders.get(gender): + profile["interested_in"] = const.interested_genders.get(gender) await state.update_data({"profile": profile}) text = ( "Отлично! Выберите интересные для вас занятия" diff --git a/src/tgbot/misc/const.py b/src/tgbot/misc/const.py new file mode 100644 index 0000000..b0d8eec --- /dev/null +++ b/src/tgbot/misc/const.py @@ -0,0 +1,9 @@ +genders = { + "♂ Мужской": "male", + "♀ Женский": "female" +} + +interested_genders = { + "male": "Парня", + "female": "Девушку", +} From 1ae789434a52ddd69564f35bce5ed6d5682c9601 Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 22:46:33 +0300 Subject: [PATCH 136/148] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20sped-up=20photo=20?= =?UTF-8?q?processing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 158 ++++++++++++++++++++++++++-------- src/tgbot/handlers/utils.py | 6 ++ 2 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 src/tgbot/handlers/utils.py diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index 01f19de..0bebb0e 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -1,10 +1,12 @@ import asyncio +from concurrent.futures import ( + ThreadPoolExecutor, +) import datetime from decimal import ( Decimal, ) -import logging -import os +import http from pathlib import ( Path, ) @@ -12,6 +14,10 @@ Any, ) +import aiofiles # type: ignore +from aiofiles import ( + os, +) from aiogram import ( Bot, F, @@ -28,6 +34,7 @@ FSMContext, ) from aiogram.types import ( + InputMediaPhoto, ReplyKeyboardRemove, ) from aiogram_calendar import ( @@ -38,6 +45,9 @@ from que_sdk import ( QueClient, ) +from que_sdk.schemas import ( + ProfileCreateSchema, +) from yandex_geocoder import ( Client, ) @@ -45,6 +55,9 @@ from src.tgbot.filters import ( ChatTypeFilter, ) +from src.tgbot.handlers.utils import ( + path_join, +) from src.tgbot.keyboards import ( inline, reply, @@ -53,6 +66,9 @@ const, states, ) +from src.tgbot.services import ( + classification_image, +) profile_router = Router() profile_router.message.filter( @@ -60,6 +76,57 @@ ) +def profile_text(profile: dict[str, Any]) -> str: + text = ( + f"*Имя:* {profile['first_name']}\n" + f"*Пол:* {const.genders[profile['gender']]}\n" + f"*Дата рождения:* {profile['birthdate']}\n" + f"*Город:* {profile['city']}\n" + f"*О себе:* {profile['description']}\n" + f"*Хочешь найти:* {const.interested_genders[profile['interested_in']]}\n" + f"*Хобби:* {', '.join(profile['hobbies'])}\n" + ) + return text + + +async def send_photos(folder_path: str, c: QueClient, token: str) -> None: + for file_name in await os.listdir(folder_path): + file_path = path_join(folder_path, file_name) + async with aiofiles.open(file_path, "rb") as file: + file_data = await file.read() + status_code, response = await c.upload_photo(access_token=token, file=file_data) + if status_code != 200: + raise Exception(f"Failed to upload photo {file_name}: {response}") + + +async def delete_files_in_folder(folder_path: str) -> None: + filenames = await aiofiles.os.listdir(folder_path) + tasks = [aiofiles.os.remove(path_join(folder_path, filename)) for filename in filenames] + await asyncio.gather(*tasks) + + +async def classify_images(user_folder: str) -> list[Any]: + async def classify_image(file_path: str) -> bool: + loop = asyncio.get_running_loop() + with ThreadPoolExecutor() as pool: + return await loop.run_in_executor(pool, classification_image, file_path) + + filenames = await aiofiles.os.listdir(user_folder) + tasks = [classify_image(path_join(user_folder, filename)) for filename in filenames] + # noinspection PyTypeChecker + return await asyncio.gather(*tasks) + + +async def download_photos(bot: Bot, album: list[types.Message], user_folder: str) -> None: + tasks = [] + for i, photo in enumerate(album): + file_id = photo.photo[-1].file_id + file = await bot.get_file(file_id=file_id) + file_destination = path_join(user_folder, f"photo_{file_id}.jpg") + tasks.append(bot.download_file(file_path=file.file_path, destination=file_destination)) + await asyncio.gather(*tasks) + + @profile_router.callback_query(F.data == "user:profile") async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") @@ -165,9 +232,8 @@ async def process_dialog_calendar( if selected: current_date = datetime.datetime.now().date() if date.date() < current_date: - profile["birthday"] = date.strftime("%Y-%m-%d") + profile["birthdate"] = date.strftime("%Y-%m-%d") await state.update_data({"profile": profile}) - await call.message.answer(text=text, reply_markup=reply.get_location_menu()) await state.set_state(states.RegistrationSG.city) else: @@ -184,9 +250,9 @@ async def process_dialog_calendar( ) async def handle_user_location(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: ya_client: Client = middleware_data.get("ya_client") - longitude = Decimal(message.location.longitude) - latitude = Decimal(message.location.latitude) - city = await ya_client.aioaddress(longitude=longitude, latitude=latitude, level="city") + longitude = message.location.longitude + latitude = message.location.latitude + city = await ya_client.aioaddress(longitude=Decimal(longitude), latitude=Decimal(latitude), level="city") storage = await state.get_data() profile = storage.get("profile") profile.update({ @@ -217,8 +283,8 @@ async def input_city_handler(message: types.Message, state: FSMContext, **middle ).format(city=city) profile.update({ "city": city, - "longitude": longitude, - "latitude": latitude + "longitude": float(longitude), + "latitude": float(latitude) }) await state.update_data({"profile": profile}) @@ -276,7 +342,7 @@ async def input_interested_in_handler(message: types.Message, state: FSMContext) @profile_router.message(F.text, StateFilter(states.RegistrationSG.hobbies)) async def input_hobbies_handler(message: types, state: FSMContext) -> None: storage = await state.get_data() - selected_interests = storage.get("hobbies", []) + selected_interests = storage.get("profile").get("hobbies", []) profile = storage.get("profile") hobby_name = message.text text = ( @@ -311,6 +377,7 @@ async def get_photo_from_user(message: types.Message, state: FSMContext, bot: Bo # https://ru.stackoverflow.com/questions/1456135/ +# TODO: Добавить обработку одной фотографии @profile_router.message( F.content_type.in_([ContentType.PHOTO]), StateFilter(states.RegistrationSG.photos), @@ -323,39 +390,35 @@ async def user_handle_album( ) -> None: storage = await state.get_data() profile = storage.get("profile") + text = profile_text(profile) tg_id = message.from_user.id root = Path(__file__).resolve().parent.parent.parent.parent user_folder = rf"{root}/photos/{tg_id}/" - if not os.path.exists(user_folder): - os.mkdir(user_folder) + + if not await os.path.exists(user_folder): + await os.mkdir(user_folder) if len(album) > 5: - await message.answer(text="Вы превысили максимальное количество фотографий. Фотографий должно быть не больше 5") + await message.answer(text="Превышено максимальное количество фото: не более 5") return - for msg in album: - file_id = msg.photo[-1].file_id - file = await bot.get_file(file_id=file_id) - file_name = f"photo_{msg.photo[-1].file_id}.jpg" - file_destination = os.path.join(user_folder, file_name) - await bot.download_file(file_path=file.file_path, destination=file_destination) - logging.info("Фотографии сохранены") - profile["folder_path"] = user_folder - await state.update_data({"profile": profile}) - profile = (await state.get_data()).get("profile") - text = ( - "*Подтвердите корректность данных:*\n\n" - "" - ) - await message.answer(text=text, reply_markup=reply.confirmation_menu()) + await download_photos(bot, album, user_folder) + is_nude = await classify_images(user_folder) -async def send_photos(folder_path: str, c: QueClient, token: str) -> None: - for file_name in os.listdir(folder_path): - file_path = os.path.join(folder_path, file_name) - with open(file_path, "rb") as file: - status_code, response = await c.upload_photo(access_token=token, file=file) - if status_code != 200: - raise Exception(f"Failed to upload photo {file_name}: {response}") + if any(is_nude): + await message.answer(text="Система обнаружила наготу в фотографиях. Попробуйте ещё раз") + await delete_files_in_folder(folder_path=user_folder) + return + + media_group = [ + InputMediaPhoto(media=photo.photo[-1].file_id, caption=text if i == 0 else '') + for i, photo in enumerate(album) + ] + profile["folder_path"] = user_folder + await state.update_data({"profile": profile}) + await message.answer_media_group(media=media_group) + await message.answer(text="Подтвердите корректность данных", reply_markup=reply.confirmation_menu()) + await state.set_state(states.RegistrationSG.confirmation) @profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.confirmation)) @@ -363,5 +426,26 @@ async def send_profile_data_to_server(message: types.Message, state: FSMContext, que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() profile = storage.get("profile") - await send_photos(c=que_client, token=storage.get("access_token"), folder_path=profile.get("folder_path")) - await message.answer("Поздравляем, вы создали профиль") + status_code, response = await que_client.create_profile( + data_in=ProfileCreateSchema( + first_name=profile.get("first_name"), + gender=profile.get("gender"), + city=profile.get("city"), + latitude=profile.get("latitude"), + longitude=profile.get("longitude"), + birthdate=profile.get("birthdate"), + description=profile.get("description"), + interested_in=profile.get("interested_in"), + hobbies=profile.get("hobbies") + ), + access_token=storage.get("access_token") + ) + if status_code == http.HTTPStatus.CREATED: + await send_photos(c=que_client, token=storage.get("access_token"), folder_path=profile.get("folder_path")) + await asyncio.sleep(0.1) + await message.answer(text="Поздравляем, вы создали профиль", reply_markup=reply.main_menu()) + else: + await message.answer(text="Произошла какая-то ошибка на стороне сервера. Попробуйте еще раз немного позже") + await delete_files_in_folder(folder_path=profile.get("folder_path")) + await state.update_data({"profile": None}) + await state.update_data({"user": None}) diff --git a/src/tgbot/handlers/utils.py b/src/tgbot/handlers/utils.py new file mode 100644 index 0000000..9a31b8d --- /dev/null +++ b/src/tgbot/handlers/utils.py @@ -0,0 +1,6 @@ +import os + +# TODO: Вынести в misc + +def path_join(folder_path: str, file_name: str) -> str: + return os.path.join(folder_path, file_name) From 19fe9eb8ddee2bcd17e213bc8fdd1c070869701a Mon Sep 17 00:00:00 2001 From: dromanov Date: Fri, 28 Jun 2024 23:46:15 +0300 Subject: [PATCH 137/148] =?UTF-8?q?=F0=9F=9A=A8=20fix=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index 0bebb0e..d103586 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -33,10 +33,6 @@ from aiogram.fsm.context import ( FSMContext, ) -from aiogram.types import ( - InputMediaPhoto, - ReplyKeyboardRemove, -) from aiogram_calendar import ( DialogCalendar, DialogCalendarCallback, @@ -194,7 +190,7 @@ async def input_gender_handler(message: types.Message, state: FSMContext) -> Non text = ( "Теперь выберите дату своего рождения" ) - await message.answer(text=text, reply_markup=ReplyKeyboardRemove()) + await message.answer(text=text, reply_markup=types.ReplyKeyboardRemove()) await asyncio.sleep(0.25) await message.answer( text="Календарь:", @@ -411,7 +407,7 @@ async def user_handle_album( return media_group = [ - InputMediaPhoto(media=photo.photo[-1].file_id, caption=text if i == 0 else '') + types.InputMediaPhoto(media=photo.photo[-1].file_id, caption=text if i == 0 else '') for i, photo in enumerate(album) ] profile["folder_path"] = user_folder From e52e50a9afe0b016673da2688f76fc5872b5762f Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:14:02 +0300 Subject: [PATCH 138/148] =?UTF-8?q?=F0=9F=9A=9A=20give=20more=20suitable?= =?UTF-8?q?=20name=20for=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 10 +++++----- src/tgbot/middlewares/{config.py => di.py} | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/tgbot/middlewares/{config.py => di.py} (95%) diff --git a/bot.py b/bot.py index 6213f4e..65c01ca 100644 --- a/bot.py +++ b/bot.py @@ -44,15 +44,15 @@ from src.tgbot.handlers import ( routers_list, ) -from src.tgbot.middlewares import ( # ThrottlingMiddleware, +from src.tgbot.middlewares import ( # type: ignore AccessControlMiddleware, AlbumMiddleware, - MiscMiddleware, + DIMiddleware, ) async def on_startup(bot: Bot, admin_ids: Sequence[int]) -> None: - await services.broadcaster.broadcast(bot, list(admin_ids), "Бот запущен") + await services.broadcast(bot, list(admin_ids), "Бот запущен") # type: ignore def setup_logging() -> None: @@ -91,7 +91,7 @@ def register_global_middlewares( ) -> None: logging.info("Setup middlewares...") middleware_types = [ - MiscMiddleware(config, client, ya_client), + DIMiddleware(config, client, ya_client), AccessControlMiddleware(client=client), ConstI18nMiddleware(locale="ru", i18n=i18n) ] @@ -128,7 +128,7 @@ async def main() -> None: i18n = I18n(path=config.tg_bot.LOCALES_DIR, default_locale="ru", domain="messages") storage = get_storage(config) client = QueClient() - ya_client = Client(api_key=config.misc.yandex_map_api_key) # type: ignore + ya_client = Client(api_key=config.misc.yandex_map_api_key) redis = Redis( host=config.redis.host, diff --git a/src/tgbot/middlewares/config.py b/src/tgbot/middlewares/di.py similarity index 95% rename from src/tgbot/middlewares/config.py rename to src/tgbot/middlewares/di.py index 78d1807..0354576 100644 --- a/src/tgbot/middlewares/config.py +++ b/src/tgbot/middlewares/di.py @@ -23,7 +23,7 @@ ) -class MiscMiddleware(BaseMiddleware): +class DIMiddleware(BaseMiddleware): def __init__( self, config: Config, From ae3cb45c69afe5c6113b6cbfb62d8a24f20b5e65 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:15:05 +0300 Subject: [PATCH 139/148] =?UTF-8?q?=F0=9F=92=84=20update=20keyboards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/inline.py | 1 - src/tgbot/keyboards/reply.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index a9f4146..e022ea8 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -32,7 +32,6 @@ def user_menu(is_profile: bool) -> types.InlineKeyboardMarkup: ) builder.row( types.InlineKeyboardButton(text=_("Изменить"), callback_data="user:edit"), - types.InlineKeyboardButton(text=_("Устройства"), callback_data="user:session") ) builder.row( types.InlineKeyboardButton(text=_("🔙 Выйти из аккаунта"), callback_data="user:signout") diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index 08826ac..e1a0a19 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -110,9 +110,6 @@ def get_location_menu() -> types.ReplyKeyboardMarkup: def get_photo_from_user_menu() -> types.ReplyKeyboardMarkup: builder = ReplyKeyboardBuilder() - builder.row( - types.KeyboardButton(text="Взять из профиля") - ) builder.row( types.KeyboardButton(text="<< Вернуться назад") ) From 570991906ef6d927f42edb2b3992c19081cf8e2b Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:20:02 +0300 Subject: [PATCH 140/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/start.py | 5 +++- src/tgbot/middlewares/__init__.py | 6 ++--- src/tgbot/middlewares/access_control.py | 9 +++---- src/tgbot/misc/__init__.py | 6 +++++ src/tgbot/misc/constants.py | 22 ++++++++++++++++ src/tgbot/services/{app => }/user.py | 34 ++++--------------------- 6 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 src/tgbot/misc/constants.py rename src/tgbot/services/{app => }/user.py (65%) diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 302d711..03f2cbd 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -35,6 +35,9 @@ from src.tgbot.keyboards import ( inline, ) +from src.tgbot.misc import ( + messages, +) start_router = Router() start_router.message.filter( @@ -56,7 +59,7 @@ async def start_handler(message: types.Message, state: FSMContext, **middleware_ if status_code == http.HTTPStatus.BAD_REQUEST: code = response.get("detail").get("code") if code == 3002: - await message.answer(text=services.welcoming_message(message_type="deactivate_user")) + await message.answer(text=messages.deactivate_user) else: if status_code != http.HTTPStatus.UNAUTHORIZED: await services.handle_send_start_message(message=message, response=response) diff --git a/src/tgbot/middlewares/__init__.py b/src/tgbot/middlewares/__init__.py index 9adaf31..218ca11 100644 --- a/src/tgbot/middlewares/__init__.py +++ b/src/tgbot/middlewares/__init__.py @@ -22,8 +22,8 @@ from .album import ( AlbumMiddleware, ) -from .config import ( - MiscMiddleware, +from .di import ( + DIMiddleware, ) from .throttling import ( ThrottlingMiddleware, @@ -39,7 +39,7 @@ "ThrottlingMiddleware", # "SupportMiddleware", # "LinkCheckMiddleware", - "MiscMiddleware", + "DIMiddleware", "AccessControlMiddleware", "AlbumMiddleware", ) diff --git a/src/tgbot/middlewares/access_control.py b/src/tgbot/middlewares/access_control.py index d95c227..e9cdef6 100644 --- a/src/tgbot/middlewares/access_control.py +++ b/src/tgbot/middlewares/access_control.py @@ -18,12 +18,12 @@ QueClient, ) +from src.tgbot.misc import ( + messages, +) from src.tgbot.misc.exceptions import ( CancelHandler, ) -from src.tgbot.services import ( - welcoming_message, -) from src.tgbot.types import ( Handler, ) @@ -32,7 +32,6 @@ class AccessControlMiddleware(BaseMiddleware): def __init__(self, client: QueClient) -> None: self.client = client - self.text_deactivate = welcoming_message(message_type="deactivate_user") self.text_unauthorized = "Вы не вошли в аккаунт" async def __call__( @@ -96,7 +95,7 @@ async def _on_process_event(self, state: FSMContext) -> None: async def _handle_cancel_event(self, event: TelegramObject, e: CancelHandler) -> None: response_texts = { - "deactivate": self.text_deactivate, + "deactivate": messages.deactivate_user, "unauthorized": self.text_unauthorized, } response_text = response_texts.get(e.title) diff --git a/src/tgbot/misc/__init__.py b/src/tgbot/misc/__init__.py index 0f7cac6..a3bae78 100644 --- a/src/tgbot/misc/__init__.py +++ b/src/tgbot/misc/__init__.py @@ -2,8 +2,14 @@ security, states, ) +from .constants import messages +from .utils import ( + os_path_join, +) __all__ = ( "security", "states", + "os_path_join", + "messages", ) diff --git a/src/tgbot/misc/constants.py b/src/tgbot/misc/constants.py new file mode 100644 index 0000000..549c915 --- /dev/null +++ b/src/tgbot/misc/constants.py @@ -0,0 +1,22 @@ +import dataclasses + + +@dataclasses.dataclass +class MessageTexts: + welcome: str = "🎉 Добро пожаловать, {username}! Вы создали новый аккаунт" + greet_auth_user: str = "👋 Привет, {username}, вы вошли в свой аккаунт" + deactivate_user: str = ("🛑 К сожалению, ваш аккаунт был деактивирован.\n" + "Чтобы возобновить работу с нашим приложением, пожалуйста, активируйте аккаунт.\n" + "Для этого используйте команду /reactivate.") + invalid_credentials: str = ("🔐 Ой, кажется, вы ввели неправильный логин или пароль. Пожалуйста, проверьте " + "введенные данные и попробуйте снова.") + not_found_user: str = ("🔍 Извините, мы не смогли найти ваш аккаунт.\nВы можете создать новый " + "аккаунт, нажав на кнопку 'Создать аккаунт' ниже.\nили войти в свой " + "аккаунт, используя ваш логин и пароль.") + + +messages = MessageTexts() + +__all__ = ( + "messages", +) diff --git a/src/tgbot/services/app/user.py b/src/tgbot/services/user.py similarity index 65% rename from src/tgbot/services/app/user.py rename to src/tgbot/services/user.py index c9d052d..4ee00c6 100644 --- a/src/tgbot/services/app/user.py +++ b/src/tgbot/services/user.py @@ -1,7 +1,6 @@ import http from typing import ( Any, - Literal, ) from aiogram import ( @@ -25,25 +24,11 @@ reply, ) from src.tgbot.misc import ( + messages, security, ) -def welcoming_message( - message_type: Literal["welcome", "greet_auth_user", "deactivate_user"], - **kwargs: Any, -) -> str: - messages = { - "welcome": "🎉 Добро пожаловать, {username}! Вы создали новый аккаунт", - "greet_auth_user": "👋 Привет {username} вы вошли в аккаунт", - "deactivate_user": ("🛑 К сожалению, ваш аккаунт был деактивирован, и доступ к приложению ограничен.\n" - "Чтобы возобновить работу с нашим приложением, пожалуйста, активируйте аккаунт.\n" - "Чтобы активировать аккаунт, используйте команду /reactivate."), - } - - return messages[message_type].format(**kwargs) - - async def get_user_data(client: QueClient, storage: dict[str, Any]) -> tuple[http.HTTPStatus, dict[str, Any]]: access_token = storage.get("access_token") status_code, response = await client.get_user_me(access_token=access_token) @@ -57,7 +42,7 @@ async def handle_send_start_message( ) -> None: username = response.get("username") if response.get("username") is not None else message.from_user.username await message.answer( - text=welcoming_message(message_type="greet_auth_user", username=username), + text=messages.greet_auth_user.format(username=username), reply_markup=reply.main_menu() ) @@ -93,7 +78,7 @@ async def handle_signup( ) await message.answer( - text=welcoming_message(message_type="welcome", username=username), + text=messages.welcome.format(username=username), reply_markup=reply.main_menu() ) await handle_login_t_me(client=client, state=state, config=config, message=message) @@ -102,14 +87,8 @@ async def handle_signup( async def handle_not_founded_user(message: types.Message) -> None: - text = _( - "🔍 Извините, мы не смогли найти ваш аккаунт.\nВы можете создать новый " - "аккаунт, нажав на кнопку 'Создать аккаунт' ниже.\nили войти в свой " - "аккаунт, используя ваш логин и пароль." - - ) await message.answer( - text=text, + text=messages.not_found_user, reply_markup=reply.login_signup_menu() ) @@ -136,9 +115,6 @@ async def handle_login( ) if status_code == http.HTTPStatus.UNAUTHORIZED: await message.answer( - text=_( - "🔐 Ой, кажется, вы ввели неправильный логин или пароль. Пожалуйста, проверьте " - "введенные данные и попробуйте снова." - ) + text=messages.invalid_credentials ) return status_code, response From 5759a4e22babb6380fb9a6ff76768cec7f728729 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:20:18 +0300 Subject: [PATCH 141/148] =?UTF-8?q?=F0=9F=94=A5=20remove=20codes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/utils.py | 6 ------ src/tgbot/misc/const.py | 9 -------- src/tgbot/services/app/__init__.py | 31 --------------------------- src/tgbot/services/dating/__init__.py | 0 src/tgbot/services/event/__init__.py | 0 5 files changed, 46 deletions(-) delete mode 100644 src/tgbot/handlers/utils.py delete mode 100644 src/tgbot/misc/const.py delete mode 100644 src/tgbot/services/app/__init__.py delete mode 100644 src/tgbot/services/dating/__init__.py delete mode 100644 src/tgbot/services/event/__init__.py diff --git a/src/tgbot/handlers/utils.py b/src/tgbot/handlers/utils.py deleted file mode 100644 index 9a31b8d..0000000 --- a/src/tgbot/handlers/utils.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -# TODO: Вынести в misc - -def path_join(folder_path: str, file_name: str) -> str: - return os.path.join(folder_path, file_name) diff --git a/src/tgbot/misc/const.py b/src/tgbot/misc/const.py deleted file mode 100644 index b0d8eec..0000000 --- a/src/tgbot/misc/const.py +++ /dev/null @@ -1,9 +0,0 @@ -genders = { - "♂ Мужской": "male", - "♀ Женский": "female" -} - -interested_genders = { - "male": "Парня", - "female": "Девушку", -} diff --git a/src/tgbot/services/app/__init__.py b/src/tgbot/services/app/__init__.py deleted file mode 100644 index 6600f24..0000000 --- a/src/tgbot/services/app/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -from . import ( - broadcaster, -) -from .nude_classifier import ( - classification_image, -) -from .set_bot_commands import ( - set_default_commands, -) -from .user import ( - get_user_data, - handle_login, - handle_login_t_me, - handle_not_founded_user, - handle_send_start_message, - handle_signup, - welcoming_message, -) - -__all__ = ( - "broadcaster", - "welcoming_message", - "get_user_data", - "handle_login_t_me", - "handle_signup", - "handle_not_founded_user", - "handle_send_start_message", - "set_default_commands", - "handle_login", - "classification_image", -) diff --git a/src/tgbot/services/dating/__init__.py b/src/tgbot/services/dating/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/tgbot/services/event/__init__.py b/src/tgbot/services/event/__init__.py deleted file mode 100644 index e69de29..0000000 From 0edb84174d758ab0d6ecc537fddcbe52fd715d95 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:21:05 +0300 Subject: [PATCH 142/148] =?UTF-8?q?=F0=9F=8E=A8=20move=20to=20a=20higher?= =?UTF-8?q?=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/services/{app => }/broadcaster.py | 0 src/tgbot/services/{app => }/nude_classifier.py | 0 src/tgbot/services/{app => }/set_bot_commands.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/tgbot/services/{app => }/broadcaster.py (100%) rename src/tgbot/services/{app => }/nude_classifier.py (100%) rename src/tgbot/services/{app => }/set_bot_commands.py (100%) diff --git a/src/tgbot/services/app/broadcaster.py b/src/tgbot/services/broadcaster.py similarity index 100% rename from src/tgbot/services/app/broadcaster.py rename to src/tgbot/services/broadcaster.py diff --git a/src/tgbot/services/app/nude_classifier.py b/src/tgbot/services/nude_classifier.py similarity index 100% rename from src/tgbot/services/app/nude_classifier.py rename to src/tgbot/services/nude_classifier.py diff --git a/src/tgbot/services/app/set_bot_commands.py b/src/tgbot/services/set_bot_commands.py similarity index 100% rename from src/tgbot/services/app/set_bot_commands.py rename to src/tgbot/services/set_bot_commands.py From 2939ba44dca9c411fdd7750c8fb2b3be5b965f41 Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:23:31 +0300 Subject: [PATCH 143/148] =?UTF-8?q?=F0=9F=8E=A8=20move=20funcs=20to=20one?= =?UTF-8?q?=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile.py | 164 +++++++++++---------------------- src/tgbot/services/__init__.py | 34 ++++++- src/tgbot/services/profile.py | 104 +++++++++++++++++++++ 3 files changed, 191 insertions(+), 111 deletions(-) create mode 100644 src/tgbot/services/profile.py diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile.py index d103586..ef5a5d9 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile.py @@ -1,7 +1,4 @@ import asyncio -from concurrent.futures import ( - ThreadPoolExecutor, -) import datetime from decimal import ( Decimal, @@ -14,8 +11,7 @@ Any, ) -import aiofiles # type: ignore -from aiofiles import ( +from aiofiles import ( # type: ignore os, ) from aiogram import ( @@ -48,23 +44,19 @@ Client, ) +from src.tgbot import ( + services, +) from src.tgbot.filters import ( ChatTypeFilter, ) -from src.tgbot.handlers.utils import ( - path_join, -) from src.tgbot.keyboards import ( inline, reply, ) from src.tgbot.misc import ( - const, states, ) -from src.tgbot.services import ( - classification_image, -) profile_router = Router() profile_router.message.filter( @@ -72,57 +64,6 @@ ) -def profile_text(profile: dict[str, Any]) -> str: - text = ( - f"*Имя:* {profile['first_name']}\n" - f"*Пол:* {const.genders[profile['gender']]}\n" - f"*Дата рождения:* {profile['birthdate']}\n" - f"*Город:* {profile['city']}\n" - f"*О себе:* {profile['description']}\n" - f"*Хочешь найти:* {const.interested_genders[profile['interested_in']]}\n" - f"*Хобби:* {', '.join(profile['hobbies'])}\n" - ) - return text - - -async def send_photos(folder_path: str, c: QueClient, token: str) -> None: - for file_name in await os.listdir(folder_path): - file_path = path_join(folder_path, file_name) - async with aiofiles.open(file_path, "rb") as file: - file_data = await file.read() - status_code, response = await c.upload_photo(access_token=token, file=file_data) - if status_code != 200: - raise Exception(f"Failed to upload photo {file_name}: {response}") - - -async def delete_files_in_folder(folder_path: str) -> None: - filenames = await aiofiles.os.listdir(folder_path) - tasks = [aiofiles.os.remove(path_join(folder_path, filename)) for filename in filenames] - await asyncio.gather(*tasks) - - -async def classify_images(user_folder: str) -> list[Any]: - async def classify_image(file_path: str) -> bool: - loop = asyncio.get_running_loop() - with ThreadPoolExecutor() as pool: - return await loop.run_in_executor(pool, classification_image, file_path) - - filenames = await aiofiles.os.listdir(user_folder) - tasks = [classify_image(path_join(user_folder, filename)) for filename in filenames] - # noinspection PyTypeChecker - return await asyncio.gather(*tasks) - - -async def download_photos(bot: Bot, album: list[types.Message], user_folder: str) -> None: - tasks = [] - for i, photo in enumerate(album): - file_id = photo.photo[-1].file_id - file = await bot.get_file(file_id=file_id) - file_destination = path_join(user_folder, f"photo_{file_id}.jpg") - tasks.append(bot.download_file(file_path=file.file_path, destination=file_destination)) - await asyncio.gather(*tasks) - - @profile_router.callback_query(F.data == "user:profile") async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") @@ -143,11 +84,12 @@ async def profile_create_handler(obj: types.TelegramObject, state: FSMContext) - "Вам нужно пройти опрос, чтобы создать профиль:\n\n" "Напишите мне ваше имя, которое будут все видеть в анкете" ) + reply_markup = reply.get_user_first_name() if isinstance(obj, types.Message): - await obj.answer(text=text, reply_markup=reply.get_user_first_name()) + await obj.answer(text=text, reply_markup=reply_markup) if isinstance(obj, types.CallbackQuery) and state is not None: await obj.message.delete() - await obj.message.answer(text=text, reply_markup=reply.get_user_first_name()) + await obj.message.answer(text=text, reply_markup=reply_markup) await state.set_state(states.RegistrationSG.first_name) @@ -181,11 +123,14 @@ async def input_first_name_handler( async def input_gender_handler(message: types.Message, state: FSMContext) -> None: storage = await state.get_data() profile = storage.get("profile") - + genders = { + "♂ Мужской": "male", + "♀ Женский": "female" + } current_year = datetime.datetime.now().year year = current_year - 18 - if const.genders.get(message.text): - profile["gender"] = const.genders[message.text] + if genders.get(message.text): + profile["gender"] = genders[message.text] await state.update_data({"profile": profile}) text = ( "Теперь выберите дату своего рождения" @@ -324,9 +269,13 @@ async def input_about_me_handler(message: types.Message, state: FSMContext) -> N async def input_interested_in_handler(message: types.Message, state: FSMContext) -> None: gender = message.text storage = await state.get_data() + interested_genders = { + "♂ Парня": "male", + "♀ Девушку": "female", + } profile = storage.get("profile") - if const.interested_genders.get(gender): - profile["interested_in"] = const.interested_genders.get(gender) + if interested_genders.get(gender): + profile["interested_in"] = interested_genders.get(gender) await state.update_data({"profile": profile}) text = ( "Отлично! Выберите интересные для вас занятия" @@ -345,12 +294,11 @@ async def input_hobbies_handler(message: types, state: FSMContext) -> None: "И напоследок, пришлите одну или несколько своих фотографий" ) if hobby_name == "Подтвердить выбор": - # FIXME: - # if not selected_interests: - # await message.answer(text="Вы должны выбрать как минимум один интерес") - # else: - await message.answer(text=text, reply_markup=reply.get_photo_from_user_menu()) - await state.set_state(states.RegistrationSG.photos) + if not selected_interests: + await message.answer(text="Вы должны выбрать как минимум один интерес") + else: + await message.answer(text=text, reply_markup=reply.get_photo_from_user_menu()) + await state.set_state(states.RegistrationSG.photos) elif hobby_name == "Очистить список": selected_interests = [] profile["hobbies"] = selected_interests @@ -361,19 +309,7 @@ async def input_hobbies_handler(message: types, state: FSMContext) -> None: await state.update_data({"profile": profile}) -# TODO: WIP -@profile_router.message( - F.text == "Взять из профиля", - StateFilter(states.RegistrationSG.photos), -) -async def get_photo_from_user(message: types.Message, state: FSMContext, bot: Bot) -> None: - telegram_id = message.from_user.id - profile_pictures = await bot.get_user_profile_photos(user_id=telegram_id, limit=1) - print(profile_pictures) - - # https://ru.stackoverflow.com/questions/1456135/ -# TODO: Добавить обработку одной фотографии @profile_router.message( F.content_type.in_([ContentType.PHOTO]), StateFilter(states.RegistrationSG.photos), @@ -382,37 +318,45 @@ async def user_handle_album( message: types.Message, state: FSMContext, bot: Bot, - album: list[types.Message], + album: list[types.Message] | None = None, ) -> None: storage = await state.get_data() profile = storage.get("profile") - text = profile_text(profile) - tg_id = message.from_user.id + text = services.profile_text(profile) + telegram_id = message.from_user.id root = Path(__file__).resolve().parent.parent.parent.parent - user_folder = rf"{root}/photos/{tg_id}/" + user_folder = rf"{root}/photos/{telegram_id}/" + profile_service = services.ProfileService(user_folder) + censor_warning_text = "Система обнаружила наготу в фотографиях. Попробуйте ещё раз" if not await os.path.exists(user_folder): await os.mkdir(user_folder) - if len(album) > 5: - await message.answer(text="Превышено максимальное количество фото: не более 5") - return - - await download_photos(bot, album, user_folder) - is_nude = await classify_images(user_folder) + if album is None: + await profile_service.download_photos(bot=bot, photos=message) - if any(is_nude): - await message.answer(text="Система обнаружила наготу в фотографиях. Попробуйте ещё раз") - await delete_files_in_folder(folder_path=user_folder) - return + if await profile_service.check_photos(): + await message.answer(text=censor_warning_text) - media_group = [ - types.InputMediaPhoto(media=photo.photo[-1].file_id, caption=text if i == 0 else '') - for i, photo in enumerate(album) - ] + file_id = message.photo[-1].file_id + await message.answer_photo(photo=file_id, caption=text) + else: + if len(album) > 5: + await message.answer(text="Превышено максимальное количество фото: не более 5") + return + + await profile_service.download_photos(bot=bot, photos=album) + if await profile_service.check_photos(): + await message.answer(text=censor_warning_text) + + media_group = [ + types.InputMediaPhoto(media=photo.photo[-1].file_id, caption=text if i == 0 else '') + for i, photo in enumerate(album) + ] + await message.answer_media_group(media=media_group) profile["folder_path"] = user_folder await state.update_data({"profile": profile}) - await message.answer_media_group(media=media_group) + await state.update_data({"profile_service": profile_service}) await message.answer(text="Подтвердите корректность данных", reply_markup=reply.confirmation_menu()) await state.set_state(states.RegistrationSG.confirmation) @@ -422,6 +366,7 @@ async def send_profile_data_to_server(message: types.Message, state: FSMContext, que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() profile = storage.get("profile") + profile_service: services.ProfileService = storage.get("profile_service") status_code, response = await que_client.create_profile( data_in=ProfileCreateSchema( first_name=profile.get("first_name"), @@ -437,11 +382,10 @@ async def send_profile_data_to_server(message: types.Message, state: FSMContext, access_token=storage.get("access_token") ) if status_code == http.HTTPStatus.CREATED: - await send_photos(c=que_client, token=storage.get("access_token"), folder_path=profile.get("folder_path")) - await asyncio.sleep(0.1) + await profile_service.send_photos(client=que_client, access_token=storage.get("access_token")) await message.answer(text="Поздравляем, вы создали профиль", reply_markup=reply.main_menu()) else: await message.answer(text="Произошла какая-то ошибка на стороне сервера. Попробуйте еще раз немного позже") - await delete_files_in_folder(folder_path=profile.get("folder_path")) + await profile_service.delete_files_in_folder() await state.update_data({"profile": None}) await state.update_data({"user": None}) diff --git a/src/tgbot/services/__init__.py b/src/tgbot/services/__init__.py index 23e40fa..1033b29 100644 --- a/src/tgbot/services/__init__.py +++ b/src/tgbot/services/__init__.py @@ -1 +1,33 @@ -from .app import * # noqa: F403 +from .broadcaster import ( + broadcast, +) +from .nude_classifier import ( + classification_image, +) +from .profile import ProfileService, profile_text +from .set_bot_commands import ( + set_default_commands, +) +from .user import ( + get_user_data, + handle_login, + handle_login_t_me, + handle_not_founded_user, + handle_send_start_message, + handle_signup, +) + +__all__ = ( + "ProfileService", + "profile_text", + "broadcaster", + "get_user_data", + "handle_login_t_me", + "handle_signup", + "handle_not_founded_user", + "handle_send_start_message", + "set_default_commands", + "handle_login", + "classification_image", + "broadcast", +) diff --git a/src/tgbot/services/profile.py b/src/tgbot/services/profile.py new file mode 100644 index 0000000..10a5a65 --- /dev/null +++ b/src/tgbot/services/profile.py @@ -0,0 +1,104 @@ +import asyncio +from concurrent.futures import ( + ThreadPoolExecutor, +) +from typing import ( + Any, +) + +import aiofiles # type: ignore +from aiofiles import ( + os, +) +from aiogram import ( + Bot, + types, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot import ( + misc, +) +from src.tgbot.services import ( + classification_image, +) + + +def profile_text(profile: dict[str, Any]) -> str: + genders = { + "male": "Мужской", + "female": "Женский" + } + + interested_genders = { + "male": "Парня", + "female": "Девушку", + } + + text = ( + f"*Имя:* {profile['first_name']}\n" + f"*Пол:* {genders[profile['gender']]}\n" + f"*Дата рождения:* {profile['birthdate']}\n" + f"*Город:* {profile['city']}\n" + f"*О себе:* {profile['description']}\n" + f"*Хочешь найти:* {interested_genders[profile['interested_in']]}\n" + f"*Хобби:* {', '.join(profile['hobbies'])}\n" + ) + return text + + +class ProfileService: + + def __init__( + self, + folder_path: str + ): + self.folder_path = folder_path + + async def send_photos(self, client: QueClient, access_token: str) -> None: + for file_name in await os.listdir(self.folder_path): + file_path = misc.os_path_join(self.folder_path, file_name) + async with aiofiles.open(file_path, "rb") as file: + file_data = await file.read() + status_code, response = await client.upload_photo(access_token=access_token, file=file_data) + if status_code != 200: + raise Exception(f"Failed to upload photo {file_name}: {response}") + + async def delete_files_in_folder(self) -> None: + filenames = await aiofiles.os.listdir(self.folder_path) + tasks = [aiofiles.os.remove(misc.os_path_join(self.folder_path, filename)) for filename in filenames] + await asyncio.gather(*tasks) + + async def classify_images(self) -> list[Any]: + async def classify_image(file_path: str) -> bool: + loop = asyncio.get_running_loop() + with ThreadPoolExecutor() as pool: + return await loop.run_in_executor(pool, classification_image, file_path) + + filenames = await aiofiles.os.listdir(self.folder_path) + tasks = [classify_image(misc.os_path_join(self.folder_path, filename)) for filename in filenames] + # noinspection PyTypeChecker + return await asyncio.gather(*tasks) + + async def download_photos(self, bot: Bot, photos: list[types.Message] | types.Message) -> None: + tasks = [] + + if isinstance(photos, types.Message): + photos = [photos] + + for photo in photos: + file_id = photo.photo[-1].file_id + file = await bot.get_file(file_id=file_id) + file_destination = misc.os_path_join(self.folder_path, f"photo_{file_id}.jpg") + tasks.append(bot.download_file(file_path=file.file_path, destination=file_destination)) + + await asyncio.gather(*tasks) + + async def check_photos(self) -> bool: + is_nude = await self.classify_images() + if any(is_nude): + await self.delete_files_in_folder() + return True + return False From b2490f084f35cfa9ad254dfdc58953c05f65942e Mon Sep 17 00:00:00 2001 From: dromanov Date: Mon, 1 Jul 2024 15:23:44 +0300 Subject: [PATCH 144/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/misc/utils.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/tgbot/misc/utils.py diff --git a/src/tgbot/misc/utils.py b/src/tgbot/misc/utils.py new file mode 100644 index 0000000..bd29a5b --- /dev/null +++ b/src/tgbot/misc/utils.py @@ -0,0 +1,5 @@ +import os + + +def os_path_join(folder_path: str, file_name: str) -> str: + return os.path.join(folder_path, file_name) From 9cffc534ae5d3ccf9d6b6c1a1554916dcbf14710 Mon Sep 17 00:00:00 2001 From: dromanov Date: Wed, 3 Jul 2024 23:40:50 +0300 Subject: [PATCH 145/148] =?UTF-8?q?=F0=9F=9A=9A=20split=20single=20file=20?= =?UTF-8?q?into=20multiple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/profile/__init__.py | 9 +++ .../{profile.py => profile/create.py} | 70 +++++++------------ src/tgbot/handlers/profile/get.py | 43 ++++++++++++ src/tgbot/handlers/profile/update.py | 59 ++++++++++++++++ 4 files changed, 137 insertions(+), 44 deletions(-) create mode 100644 src/tgbot/handlers/profile/__init__.py rename src/tgbot/handlers/{profile.py => profile/create.py} (83%) create mode 100644 src/tgbot/handlers/profile/get.py create mode 100644 src/tgbot/handlers/profile/update.py diff --git a/src/tgbot/handlers/profile/__init__.py b/src/tgbot/handlers/profile/__init__.py new file mode 100644 index 0000000..f187007 --- /dev/null +++ b/src/tgbot/handlers/profile/__init__.py @@ -0,0 +1,9 @@ +from .create import create_profile_router +from .get import get_profile_router +from .update import update_profile_router + +__all__ = ( + "get_profile_router", + "create_profile_router", + "update_profile_router", +) diff --git a/src/tgbot/handlers/profile.py b/src/tgbot/handlers/profile/create.py similarity index 83% rename from src/tgbot/handlers/profile.py rename to src/tgbot/handlers/profile/create.py index ef5a5d9..3836e97 100644 --- a/src/tgbot/handlers/profile.py +++ b/src/tgbot/handlers/profile/create.py @@ -47,38 +47,18 @@ from src.tgbot import ( services, ) -from src.tgbot.filters import ( - ChatTypeFilter, -) from src.tgbot.keyboards import ( - inline, reply, ) from src.tgbot.misc import ( states, ) -profile_router = Router() -profile_router.message.filter( - ChatTypeFilter(chat_type=["private"]) -) - - -@profile_router.callback_query(F.data == "user:profile") -async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: - que_client: QueClient = middleware_data.get("que-client") - storage = await state.get_data() - # TODO: Нужно сделать кэш на время, чтобы постоянно не отправлять запросы на сервер - _, profile = await que_client.get_profile(user_id=storage.get("id"), access_token=storage.get("access_token")) - # await call.message.delete() - # await call.message.answer_photo() - await call.message.edit_text( - text="Ваш прфиль: {profile_id}".format(profile_id=profile.get("id")), reply_markup=inline.profile_menu() - ) +create_profile_router = Router() -@profile_router.callback_query(F.data == "user:profile-create") -@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.gender)) +@create_profile_router.callback_query(F.data == "user:profile-create") +@create_profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.gender)) async def profile_create_handler(obj: types.TelegramObject, state: FSMContext) -> None: text = ( "Вам нужно пройти опрос, чтобы создать профиль:\n\n" @@ -93,11 +73,11 @@ async def profile_create_handler(obj: types.TelegramObject, state: FSMContext) - await state.set_state(states.RegistrationSG.first_name) -@profile_router.message(F.text, StateFilter(states.RegistrationSG.first_name)) +@create_profile_router.message(F.text, StateFilter(states.RegistrationSG.first_name)) async def input_first_name_handler( obj: types.TelegramObject, state: FSMContext, - bot: Bot + bot: Bot, ) -> None: text = "Принято! Выберите ваш гендер" first_name: str @@ -117,9 +97,9 @@ async def input_first_name_handler( await state.set_state(states.RegistrationSG.gender) -@profile_router.message(F.text == "♂ Мужской", StateFilter(states.RegistrationSG.gender)) -@profile_router.message(F.text == "♀ Женский", StateFilter(states.RegistrationSG.gender)) -@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.city)) +@create_profile_router.message(F.text == "♂ Мужской", StateFilter(states.RegistrationSG.gender)) +@create_profile_router.message(F.text == "♀ Женский", StateFilter(states.RegistrationSG.gender)) +@create_profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.city)) async def input_gender_handler(message: types.Message, state: FSMContext) -> None: storage = await state.get_data() profile = storage.get("profile") @@ -136,7 +116,7 @@ async def input_gender_handler(message: types.Message, state: FSMContext) -> Non "Теперь выберите дату своего рождения" ) await message.answer(text=text, reply_markup=types.ReplyKeyboardRemove()) - await asyncio.sleep(0.25) + await asyncio.sleep(0.15) await message.answer( text="Календарь:", reply_markup=await DialogCalendar( @@ -146,11 +126,11 @@ async def input_gender_handler(message: types.Message, state: FSMContext) -> Non await state.set_state(states.RegistrationSG.birthday) -@profile_router.callback_query( +@create_profile_router.callback_query( DialogCalendarCallback.filter(), StateFilter(states.RegistrationSG.birthday) ) -@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.about_me)) +@create_profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.about_me)) async def process_dialog_calendar( obj: types.TelegramObject, state: FSMContext, @@ -185,7 +165,7 @@ async def process_dialog_calendar( await state.set_state(states.RegistrationSG.city) -@profile_router.message( +@create_profile_router.message( F.content_type.in_([ContentType.LOCATION]), StateFilter(states.RegistrationSG.city), ) @@ -209,8 +189,8 @@ async def handle_user_location(message: types.Message, state: FSMContext, **midd await state.set_state(states.RegistrationSG.about_me) -@profile_router.message(F.text != "✅ Да все хорошо!", StateFilter(states.RegistrationSG.city)) -@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.interested_in)) +@create_profile_router.message(F.text != "✅ Да все хорошо!", StateFilter(states.RegistrationSG.city)) +@create_profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.interested_in)) async def input_city_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: ya_client: Client = middleware_data.get("ya_client") longitude, latitude = await ya_client.aiocoordinates(message.text) @@ -232,7 +212,7 @@ async def input_city_handler(message: types.Message, state: FSMContext, **middle await message.answer(text=text, reply_markup=reply.confirmation_menu()) -@profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.city)) +@create_profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.city)) async def handle_confirmation_city(message: types.Message, state: FSMContext) -> None: text = ( "Отлично! Теперь напишите о себе" @@ -241,8 +221,8 @@ async def handle_confirmation_city(message: types.Message, state: FSMContext) -> await state.set_state(states.RegistrationSG.about_me) -@profile_router.message(F.text, StateFilter(states.RegistrationSG.about_me)) -@profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.hobbies)) +@create_profile_router.message(F.text, StateFilter(states.RegistrationSG.about_me)) +@create_profile_router.message(F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.hobbies)) async def input_about_me_handler(message: types.Message, state: FSMContext) -> None: storage = await state.get_data() profile = storage.get("profile") @@ -254,15 +234,15 @@ async def input_about_me_handler(message: types.Message, state: FSMContext) -> N await state.set_state(states.RegistrationSG.interested_in) -@profile_router.message( +@create_profile_router.message( F.text == "♂ Парня", StateFilter(states.RegistrationSG.interested_in), ) -@profile_router.message( +@create_profile_router.message( F.text == "♀ Девушку", StateFilter(states.RegistrationSG.interested_in), ) -@profile_router.message( +@create_profile_router.message( F.text == "<< Вернуться назад", StateFilter(states.RegistrationSG.photos) ) @@ -284,7 +264,7 @@ async def input_interested_in_handler(message: types.Message, state: FSMContext) await state.set_state(states.RegistrationSG.hobbies) -@profile_router.message(F.text, StateFilter(states.RegistrationSG.hobbies)) +@create_profile_router.message(F.text, StateFilter(states.RegistrationSG.hobbies)) async def input_hobbies_handler(message: types, state: FSMContext) -> None: storage = await state.get_data() selected_interests = storage.get("profile").get("hobbies", []) @@ -310,7 +290,7 @@ async def input_hobbies_handler(message: types, state: FSMContext) -> None: # https://ru.stackoverflow.com/questions/1456135/ -@profile_router.message( +@create_profile_router.message( F.content_type.in_([ContentType.PHOTO]), StateFilter(states.RegistrationSG.photos), ) @@ -324,7 +304,9 @@ async def user_handle_album( profile = storage.get("profile") text = services.profile_text(profile) telegram_id = message.from_user.id - root = Path(__file__).resolve().parent.parent.parent.parent + # Если изменяем расположение файла, то еще нужно изменить root + # TODO: Надо бы перенести в services + root = Path(__file__).resolve().parent.parent.parent.parent.parent user_folder = rf"{root}/photos/{telegram_id}/" profile_service = services.ProfileService(user_folder) censor_warning_text = "Система обнаружила наготу в фотографиях. Попробуйте ещё раз" @@ -361,7 +343,7 @@ async def user_handle_album( await state.set_state(states.RegistrationSG.confirmation) -@profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.confirmation)) +@create_profile_router.message(F.text == "✅ Да все хорошо!", StateFilter(states.RegistrationSG.confirmation)) async def send_profile_data_to_server(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() diff --git a/src/tgbot/handlers/profile/get.py b/src/tgbot/handlers/profile/get.py new file mode 100644 index 0000000..a0ad82f --- /dev/null +++ b/src/tgbot/handlers/profile/get.py @@ -0,0 +1,43 @@ +from typing import ( + Any, +) + +from aiogram import ( + F, + Router, + types, +) +from aiogram.fsm.context import ( + FSMContext, +) +from que_sdk import ( + QueClient, +) + +from src.tgbot import ( + services, +) +from src.tgbot.keyboards import ( + reply, +) + +get_profile_router = Router() + + +@get_profile_router.callback_query(F.data == "user:profile") +async def profile_handler(call: types.CallbackQuery, state: FSMContext, **middleware_data: Any) -> None: + que_client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + _, user = await que_client.get_user_me(access_token=storage.get("access_token")) + profile = user.get("profile") + photos = user.get("photos") + text = services.profile_text(profile) + media_group = [ + types.InputMediaPhoto(media=photo.get("remote_url"), caption=text if i == 0 else '') + for i, photo in enumerate(photos) + ] + await call.message.delete() + await call.message.answer(text="💫", reply_markup=reply.profile_menu()) # type: ignore + await call.message.answer_media_group( + media=media_group, + ) diff --git a/src/tgbot/handlers/profile/update.py b/src/tgbot/handlers/profile/update.py new file mode 100644 index 0000000..61b304f --- /dev/null +++ b/src/tgbot/handlers/profile/update.py @@ -0,0 +1,59 @@ +from typing import ( + Any, +) + +from aiogram import ( + F, + Router, + types, +) +from aiogram.filters import ( + StateFilter, +) +from aiogram.fsm.context import ( + FSMContext, +) +from que_sdk import ( + QueClient, +) +from que_sdk.schemas import ( + ProfileUpdateSchema, +) + +from src.tgbot.keyboards import ( + inline, + reply, +) +from src.tgbot.misc import ( + states, +) + +update_profile_router = Router() + + +@update_profile_router.message(F.text == "✏️ Изменить") +async def update_profile_handler(message: types.Message) -> None: + text = "Выберите, что вы хотите изменить в своём профиле" + await message.answer(text=text, reply_markup=inline.profile_update_menu()) # type: ignore + + +@update_profile_router.callback_query(F.data == "first_name") +async def update_first_name_handler(call: types.CallbackQuery, state: FSMContext) -> None: + await call.message.delete() + await call.message.answer(text="Введите новое имя", reply_markup=reply.back_to_menu()) + await state.set_state(states.UpdateProfileSG.first_name) # type: ignore + + +@update_profile_router.message(F.text, StateFilter(states.UpdateProfileSG.first_name)) # type: ignore +async def input_first_name_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: + que_client: QueClient = middleware_data.get("que-client") + storage = await state.get_data() + profile_id = storage.get("user").get("profile").get("id") + access_token = storage.get("access_token") + await que_client.update_profile( + access_token=access_token, + profile_id=profile_id, + data_in=ProfileUpdateSchema( + first_name=message.text + ) + ) From 192753a6593013c53d12cca4f0854bec3f4cad78 Mon Sep 17 00:00:00 2001 From: dromanov Date: Wed, 3 Jul 2024 23:42:46 +0300 Subject: [PATCH 146/148] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/handlers/__init__.py | 8 ++- src/tgbot/handlers/start.py | 60 +++++++++++++++--- src/tgbot/services/__init__.py | 14 +---- src/tgbot/services/profile.py | 1 + src/tgbot/services/user.py | 110 ++++++--------------------------- 5 files changed, 79 insertions(+), 114 deletions(-) diff --git a/src/tgbot/handlers/__init__.py b/src/tgbot/handlers/__init__.py index 389cec4..c2cc351 100644 --- a/src/tgbot/handlers/__init__.py +++ b/src/tgbot/handlers/__init__.py @@ -1,5 +1,7 @@ from .profile import ( - profile_router, + get_profile_router, + create_profile_router, + update_profile_router, ) from .start import ( start_router, @@ -11,7 +13,9 @@ routers_list = [ start_router, user_router, - profile_router, + get_profile_router, + create_profile_router, + update_profile_router, # echo_router, # echo_router must be last ] diff --git a/src/tgbot/handlers/start.py b/src/tgbot/handlers/start.py index 03f2cbd..fde7b77 100644 --- a/src/tgbot/handlers/start.py +++ b/src/tgbot/handlers/start.py @@ -21,11 +21,9 @@ ) from que_sdk import ( QueClient, + schemas, ) -from src.tgbot import ( - services, -) from src.tgbot.config import ( Config, ) @@ -34,10 +32,14 @@ ) from src.tgbot.keyboards import ( inline, + reply, ) from src.tgbot.misc import ( messages, ) +from src.tgbot.services import ( + AuthService, +) start_router = Router() start_router.message.filter( @@ -50,33 +52,71 @@ async def start_handler(message: types.Message, state: FSMContext, **middleware_ config: Config = middleware_data.get("config") que_client: QueClient = middleware_data.get("que-client") storage = await state.get_data() + auth = AuthService(client=que_client, config=config) if not storage: - status_code, response = await services.handle_login_t_me(que_client, config, message, state) + status_code, response = await auth.handle_login_t_me(message=message, state=state) if status_code == http.HTTPStatus.NOT_FOUND: - await services.handle_not_founded_user(message=message) + await message.answer( + text=messages.not_found_user, + reply_markup=reply.login_signup_menu() + ) storage = await state.get_data() - status_code, response = await services.get_user_data(que_client, storage) + status_code, response = await auth.get_user_data(storage=storage) if status_code == http.HTTPStatus.BAD_REQUEST: code = response.get("detail").get("code") if code == 3002: await message.answer(text=messages.deactivate_user) else: if status_code != http.HTTPStatus.UNAUTHORIZED: - await services.handle_send_start_message(message=message, response=response) + username = response.get("username") if response.get("username") is not None else message.from_user.username + await message.answer( + text=messages.greet_auth_user.format(username=username), + reply_markup=reply.main_menu() + ) @start_router.message(F.text == __("📝 Создать аккаунт")) async def signup_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: - client: QueClient = middleware_data.get("que-client") + que_client: QueClient = middleware_data.get("que-client") config: Config = middleware_data.get("config") - await services.handle_signup(client=client, message=message, state=state, config=config) + auth = AuthService(client=que_client, config=config) + username = message.from_user.username + status_code, response = await que_client.signup( + data_in=schemas.SignUpSchema( + username=username, + telegram_id=message.from_user.id, + ) + ) + + await auth.handle_login_t_me(state=state, message=message) + await message.answer( + text=messages.welcome.format(username=username), + reply_markup=reply.main_menu() + ) @start_router.message(F.web_app_data) async def web_app_login_handler(message: types.Message, state: FSMContext, **middleware_data: Any) -> None: client: QueClient = middleware_data.get("que-client") data = json.loads(message.web_app_data.data) - await services.handle_login(client=client, message=message, state=state, data=data) + status_code, response = await client.login( + data_in=schemas.LoginSchema( + username=data.get("login"), + password=data.get("password"), + telegram_id=message.from_user.id + ) + ) + if status_code == http.HTTPStatus.OK: + access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') + await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) + await message.answer( + text=_("С возвращением, {username}").format(username=data.get("login")), + reply_markup=reply.main_menu() + ) + if status_code == http.HTTPStatus.UNAUTHORIZED: + await message.answer( + text=messages.invalid_credentials + ) @start_router.message(F.text == __("ℹ️ О проекте")) diff --git a/src/tgbot/services/__init__.py b/src/tgbot/services/__init__.py index 1033b29..adf67b8 100644 --- a/src/tgbot/services/__init__.py +++ b/src/tgbot/services/__init__.py @@ -9,25 +9,15 @@ set_default_commands, ) from .user import ( - get_user_data, - handle_login, - handle_login_t_me, - handle_not_founded_user, - handle_send_start_message, - handle_signup, + AuthService ) __all__ = ( "ProfileService", + "AuthService", "profile_text", "broadcaster", - "get_user_data", - "handle_login_t_me", - "handle_signup", - "handle_not_founded_user", - "handle_send_start_message", "set_default_commands", - "handle_login", "classification_image", "broadcast", ) diff --git a/src/tgbot/services/profile.py b/src/tgbot/services/profile.py index 10a5a65..8507fe9 100644 --- a/src/tgbot/services/profile.py +++ b/src/tgbot/services/profile.py @@ -51,6 +51,7 @@ def profile_text(profile: dict[str, Any]) -> str: class ProfileService: + # TODO: Убрать из параметров folder_path, а задать его прямо в __init__ def __init__( self, folder_path: str diff --git a/src/tgbot/services/user.py b/src/tgbot/services/user.py index 4ee00c6..d1fe880 100644 --- a/src/tgbot/services/user.py +++ b/src/tgbot/services/user.py @@ -9,9 +9,6 @@ from aiogram.fsm.context import ( FSMContext, ) -from aiogram.utils.i18n import ( - gettext as _, -) from que_sdk import ( QueClient, schemas, @@ -20,101 +17,34 @@ from src.tgbot.config import ( Config, ) -from src.tgbot.keyboards import ( - reply, -) from src.tgbot.misc import ( - messages, security, ) -async def get_user_data(client: QueClient, storage: dict[str, Any]) -> tuple[http.HTTPStatus, dict[str, Any]]: - access_token = storage.get("access_token") - status_code, response = await client.get_user_me(access_token=access_token) - - return status_code, response - - -async def handle_send_start_message( - message: types.Message, - response: dict[Any, Any] -) -> None: - username = response.get("username") if response.get("username") is not None else message.from_user.username - await message.answer( - text=messages.greet_auth_user.format(username=username), - reply_markup=reply.main_menu() - ) - - -async def handle_login_t_me( - client: QueClient, - config: Config, - message: types.Message, - state: FSMContext, -) -> tuple[http.HTTPStatus, dict[str, Any]] | None: - auth_data = security.generate_signature(telegram_id=message.from_user.id, secret_key=config.misc.secret_key) - status_code, response = await client.login_t_me(data_in=schemas.TMELoginSchema(**auth_data)) - if status_code == http.HTTPStatus.OK: - access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') - - await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) +class AuthService: + def __init__(self, client: QueClient, config: Config): + self.client = client + self.config = config - return status_code, response + async def get_user_data(self, storage: dict[str, Any]) -> tuple[http.HTTPStatus, dict[str, Any]]: + access_token = storage.get("access_token") + status_code, response = await self.client.get_user_me(access_token=access_token) + return status_code, response -async def handle_signup( - client: QueClient, - message: types.Message, - state: FSMContext, - config: Config -) -> tuple[http.HTTPStatus, dict[str, Any]]: - username = message.from_user.username - status_code, response = await client.signup( - data_in=schemas.SignUpSchema( - username=username, - telegram_id=message.from_user.id, + async def handle_login_t_me( + self, + message: types.Message, + state: FSMContext, + ) -> tuple[http.HTTPStatus, dict[str, Any]] | None: + auth_data = security.generate_signature( + telegram_id=message.from_user.id, secret_key=self.config.misc.secret_key ) - ) + status_code, response = await self.client.login_t_me(data_in=schemas.TMELoginSchema(**auth_data)) + if status_code == http.HTTPStatus.OK: + access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') - await message.answer( - text=messages.welcome.format(username=username), - reply_markup=reply.main_menu() - ) - await handle_login_t_me(client=client, state=state, config=config, message=message) + await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) - return status_code, response - - -async def handle_not_founded_user(message: types.Message) -> None: - await message.answer( - text=messages.not_found_user, - reply_markup=reply.login_signup_menu() - ) - - -async def handle_login( - client: QueClient, - state: FSMContext, - message: types.Message, - data: dict[str, Any] -) -> tuple[http.HTTPStatus, dict[str, Any]]: - status_code, response = await client.login( - data_in=schemas.LoginSchema( - username=data.get("login"), - password=data.get("password"), - telegram_id=message.from_user.id - ) - ) - if status_code == http.HTTPStatus.OK: - access_token, refresh_toke = response.get('access_token'), response.get('refresh_token') - await state.update_data({"access_token": access_token, "refresh_token": refresh_toke}) - await message.answer( - text=_("С возвращением, {username}").format(username=data.get("login")), - reply_markup=reply.main_menu() - ) - if status_code == http.HTTPStatus.UNAUTHORIZED: - await message.answer( - text=messages.invalid_credentials - ) - return status_code, response + return status_code, response From 876f6edfe1b51c72f40d4e6452155e1245ec94f2 Mon Sep 17 00:00:00 2001 From: dromanov Date: Wed, 3 Jul 2024 23:43:25 +0300 Subject: [PATCH 147/148] =?UTF-8?q?=F0=9F=92=84=20update=20keyboards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/keyboards/inline.py | 16 ++++++++++------ src/tgbot/keyboards/reply.py | 9 +++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/tgbot/keyboards/inline.py b/src/tgbot/keyboards/inline.py index e022ea8..292d266 100644 --- a/src/tgbot/keyboards/inline.py +++ b/src/tgbot/keyboards/inline.py @@ -48,13 +48,17 @@ def user_activation_menu() -> types.InlineKeyboardMarkup: return builder.as_markup() -def profile_menu() -> types.InlineKeyboardMarkup: +def profile_update_menu() -> types.InlineKeyboardMarkup: builder = InlineKeyboardBuilder() + builder.row( - types.InlineKeyboardButton(text="Изменить", callback_data="profile:edit"), - types.InlineKeyboardButton(text="Удалить", callback_data="profile:delete") - ) - builder.row( - types.InlineKeyboardButton(text="<< Вернуться назад", callback_data="back_to_user_menu") + types.InlineKeyboardButton(text=_("📇 Имя"), callback_data="first_name"), + types.InlineKeyboardButton(text=_("⚧️ Пол"), callback_data="gender"), + types.InlineKeyboardButton(text=_("🏙️ Город"), callback_data="city"), + types.InlineKeyboardButton(text=_("🎂 Дата рождения"), callback_data="birthdate"), + types.InlineKeyboardButton(text=_("📸 Фото"), callback_data="photo"), + types.InlineKeyboardButton(text=_("✍️ О себе"), callback_data="about_me"), + types.InlineKeyboardButton(text=_("🧑‍🎨 Хобби"), callback_data="hobbies") ) + builder.adjust(1, 2, 1, 2) return builder.as_markup() diff --git a/src/tgbot/keyboards/reply.py b/src/tgbot/keyboards/reply.py index e1a0a19..1af56ab 100644 --- a/src/tgbot/keyboards/reply.py +++ b/src/tgbot/keyboards/reply.py @@ -141,3 +141,12 @@ def confirmation_menu() -> types.ReplyKeyboardMarkup: types.KeyboardButton(text="✅ Да все хорошо!") ) return builder.as_markup(resize_keyboard=True) + + +def profile_menu() -> types.ReplyKeyboardMarkup: + builder = ReplyKeyboardBuilder() + builder.row( + types.KeyboardButton(text="✏️ Изменить"), + types.KeyboardButton(text="🗑 Удалить") + ) + return builder.as_markup(resize_keyboard=True) From 962516e7afae54c1cad1ae3787e0549b041f0909 Mon Sep 17 00:00:00 2001 From: dromanov Date: Wed, 3 Jul 2024 23:44:56 +0300 Subject: [PATCH 148/148] =?UTF-8?q?=E2=9C=A8=20add=20states=20for=20update?= =?UTF-8?q?=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tgbot/misc/states.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tgbot/misc/states.py b/src/tgbot/misc/states.py index 7b12e8d..764e1a0 100644 --- a/src/tgbot/misc/states.py +++ b/src/tgbot/misc/states.py @@ -13,3 +13,14 @@ class RegistrationSG(state.StatesGroup): hobbies = state.State() photos = state.State() confirmation = state.State() + + +class UpdateProfileSG(state.StatesGroup): + first_name = state.State() + gender = state.State() + city = state.State() + birthday = state.State() + about_me = state.State() + interested_in = state.State() + hobbies = state.State() + photos = state.State()