├── .flake8 ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── __init__.py ├── __main__.py ├── data_structures │ ├── __init__.py │ ├── arq.py │ ├── callback_data.py │ ├── captcha.py │ └── redis.py ├── filters │ ├── __init__.py │ └── chat_type.py ├── handlers │ ├── __init__.py │ ├── chat │ │ ├── __init__.py │ │ ├── chat_join_request.py │ │ └── my_chat_member.py │ ├── globals │ │ ├── __init__.py │ │ └── errors.py │ └── private │ │ ├── __init__.py │ │ ├── callback_query.py │ │ └── message.py ├── misc │ ├── __init__.py │ ├── configure.py │ ├── exceptions.py │ ├── filename_utils.py │ ├── kb_generators.py │ ├── loggers.py │ ├── paths.py │ ├── settings_reader.py │ ├── uuid.py │ └── webhook.py └── services │ ├── __init__.py │ ├── captcha.py │ ├── captcha_generator.py │ ├── captcha_scheduler.py │ ├── lock_user.py │ ├── redis.py │ └── scheduler.py ├── assets ├── img │ ├── 1F308.png │ ├── 1F334.png │ ├── 1F335.png │ ├── 1F339.png │ ├── 1F345.png │ ├── 1F346.png │ ├── 1F347.png │ ├── 1F349.png │ ├── 1F34B.png │ ├── 1F34C.png │ ├── 1F34D.png │ ├── 1F34E.png │ ├── 1F34F.png │ ├── 1F352.png │ ├── 1F353.png │ ├── 1F354.png │ ├── 1F355.png │ ├── 1F366.png │ ├── 1F369.png │ ├── 1F36A.png │ ├── 1F36B.png │ ├── 1F36D.png │ ├── 1F37A.png │ ├── 1F37F.png │ ├── 1F382.png │ ├── 1F385.png │ ├── 1F388.png │ ├── 1F3D4.png │ ├── 1F3E0.png │ ├── 1F404.png │ ├── 1F40A.png │ ├── 1F40C.png │ ├── 1F40D.png │ ├── 1F411.png │ ├── 1F414.png │ ├── 1F416.png │ ├── 1F419.png │ ├── 1F41D.png │ ├── 1F422.png │ ├── 1F424.png │ ├── 1F427.png │ ├── 1F42B.png │ ├── 1F42C.png │ ├── 1F42D.png │ ├── 1F430.png │ ├── 1F43B.png │ ├── 1F440.png │ ├── 1F44A.png │ ├── 1F44B.png │ ├── 1F44D.png │ ├── 1F44E.png │ ├── 1F451.png │ ├── 1F478.png │ ├── 1F47B.png │ ├── 1F47D.png │ ├── 1F480.png │ ├── 1F48B.png │ ├── 1F494.png │ ├── 1F499.png │ ├── 1F49A.png │ ├── 1F49B.png │ ├── 1F49C.png │ ├── 1F4A3.png │ ├── 1F4A6.png │ ├── 1F4A7.png │ ├── 1F4F7.png │ ├── 1F514.png │ ├── 1F525.png │ ├── 1F52B.png │ ├── 1F5A4.png │ ├── 1F601.png │ ├── 1F609.png │ ├── 1F60E.png │ ├── 1F618.png │ ├── 1F621.png │ ├── 1F622.png │ ├── 1F633.png │ ├── 1F634.png │ ├── 1F637.png │ ├── 1F642.png │ ├── 1F643.png │ ├── 1F648.png │ ├── 1F649.png │ ├── 1F64A.png │ ├── 1F680.png │ ├── 1F697.png │ ├── 1F69C.png │ ├── 1F6A2.png │ ├── 1F6A6.png │ ├── 1F6B2.png │ ├── 1F7E0.png │ ├── 1F7E1.png │ ├── 1F7E2.png │ ├── 1F7E3.png │ ├── 1F7E4.png │ ├── 1F7E5.png │ ├── 1F7E6.png │ ├── 1F7E7.png │ ├── 1F7E8.png │ ├── 1F7E9.png │ ├── 1F7EA.png │ ├── 1F7EB.png │ ├── 1F90E.png │ ├── 1F914.png │ ├── 1F916.png │ ├── 1F91D.png │ ├── 1F91E.png │ ├── 1F921.png │ ├── 1F922.png │ ├── 1F92F.png │ ├── 1F935.png │ ├── 1F937.png │ ├── 1F947.png │ ├── 1F955.png │ ├── 1F95A.png │ ├── 1F971.png │ ├── 1F980.png │ ├── 1F984.png │ ├── 1F987.png │ ├── 1F98B.png │ ├── 1F98C.png │ ├── 1F98D.png │ ├── 1F98F.png │ ├── 1F992.png │ ├── 1F994.png │ ├── 1F998.png │ ├── 1F9A2.png │ ├── 1F9B4.png │ ├── 1F9B7.png │ ├── 1F9C0.png │ ├── 1F9CA.png │ ├── 1F9DA.png │ ├── 1F9DB.png │ ├── 1F9E0.png │ ├── 1F9E1.png │ ├── 1F9F1.png │ ├── 23F0.png │ ├── 2693.png │ ├── 26F2.png │ ├── 2B1B.png │ ├── 2B50.png │ ├── captcha_failure.png │ └── captcha_success.png └── mapping.json ├── config_example.ini ├── docker-compose.yml ├── poetry.lock ├── pyproject.toml ├── redis.conf ├── screenshot.jpeg └── worker ├── __init__.py ├── __main__.py └── tasks ├── __init__.py └── join_expired.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 99 3 | select = C,E,F,W,B,B950 4 | exclude = 5 | .git 6 | build 7 | dist 8 | venv 9 | docs 10 | *.egg-info -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | venv/ 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # IDE files 28 | .idea/ 29 | 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # Env files 61 | *.env 62 | 63 | # Config files 64 | config.ini 65 | 66 | # Sessions 67 | *.session 68 | *.session-* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile from https://github.com/python-poetry/poetry/discussions/1879 2 | # `python-base` sets up all our shared environment variables 3 | FROM python:3.10-slim as python-base 4 | ENV PYTHONUNBUFFERED=1 \ 5 | # prevents python creating .pyc as files 6 | PYTHONDONTWRITEBYTECODE=1 \ 7 | \ 8 | # pip 9 | PIP_NO_CACHE_DIR=off \ 10 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 11 | PIP_DEFAULT_TIMEOUT=100 \ 12 | \ 13 | # poetry 14 | # https://python-poetry.org/docs/configuration/#using-environment-variables 15 | POETRY_VERSION=1.8.2 \ 16 | # make poetry install to this location 17 | POETRY_HOME="/opt/poetry" \ 18 | # make poetry create the virtual environment in the project's root 19 | # it gets named `.venv` 20 | POETRY_VIRTUALENVS_IN_PROJECT=true \ 21 | # do not ask any interactive question 22 | POETRY_NO_INTERACTION=1 \ 23 | \ 24 | # paths 25 | # this is where our requirements + virtual environment will live 26 | PYSETUP_PATH="/opt/pysetup" \ 27 | VENV_PATH="/opt/pysetup/.venv" 28 | 29 | # prepend poetry and venv to path 30 | ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" 31 | 32 | 33 | # `builder-base` stage is used to build deps + create our virtual environment 34 | FROM python-base as builder-base 35 | RUN apt-get update && apt-get install --no-install-recommends -y \ 36 | # deps for installing poetry 37 | curl \ 38 | # deps for building python deps 39 | build-essential 40 | 41 | # install poetry - respects $POETRY_VERSION & $POETRY_HOME 42 | RUN curl -sSL https://install.python-poetry.org | python - 43 | 44 | # copy project requirement files here to ensure they will be cached. 45 | WORKDIR $PYSETUP_PATH 46 | COPY poetry.lock pyproject.toml ./ 47 | 48 | # install runtime deps - uses $POETRY_VIRTUALENVS_IN_PROJECT internally 49 | RUN poetry install --no-dev 50 | 51 | 52 | # `production` image used for runtime 53 | FROM python-base as production 54 | COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH 55 | WORKDIR /usr/src/simplecaptcha-bot 56 | COPY . /usr/src/simplecaptcha-bot 57 | 58 | CMD python -m app -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mikhail Smolnikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Captcha Bot 2 | 3 | **Simple Captcha Bot** is your best friend when you want to protect your Telegram chat from spammers. 4 | 5 | Try it out! [@simplecaptcha_bot](https://t.me/simplecaptcha_bot) 6 | 7 | ![screenshot](screenshot.jpeg) 8 | ## Features 9 | 10 | - Works only in groups with the "request to join" option enabled 11 | - Does not send captcha messages to groups 12 | - Automatically rejects requests if user does not respond to captcha after 2 minutes 13 | 14 | ## Tech Stack 15 | - [Aiogram](https://github.com/aiogram/aiogram) v3.0.0b3 - framework for Telegram Bot API 16 | - [Redis](https://redis.io) v7.0 - storage of temporary data and scheduled tasks 17 | - [Arq](https://github.com/samuelcolvin/arq) v0.22 - scheduler 18 | 19 | ## Installation 20 | 21 | 1. `git clone https://github.com/prostmich/simplecaptcha-bot.git` 22 | 2. `cd simplecaptcha-bot` 23 | 3. Rename `config_example.ini` to `config.ini` 24 | 4. Fill in `config.ini` with your credentials (if you do not want to use webhooks, do not fill in the `webhook` section) 25 | 6. Set the Redis password in `redis.conf` 26 | 27 | ## Usage 28 | Run `docker-compose up -d` to start. -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/app/__init__.py -------------------------------------------------------------------------------- /app/__main__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiogram import Bot, Dispatcher 4 | from aiohttp import web 5 | 6 | from app.handlers import main_router 7 | from app.misc.configure import configure_logging, configure_services 8 | from app.misc.settings_reader import Settings 9 | from app.misc.webhook import configure_app 10 | 11 | settings = Settings() 12 | 13 | 14 | async def on_startup(dispatcher: Dispatcher, bot: Bot) -> None: 15 | services = await configure_services(settings) 16 | dispatcher.workflow_data.update(services) 17 | await bot.delete_webhook() 18 | if settings.webhook.url: 19 | await bot.set_webhook( 20 | settings.webhook.url, allowed_updates=dispatcher.resolve_used_update_types() 21 | ) 22 | 23 | 24 | async def on_shutdown(bot: Bot) -> None: 25 | await bot.delete_webhook() 26 | await bot.session.close() 27 | 28 | 29 | async def main() -> None: 30 | configure_logging() 31 | bot = Bot(token=settings.bot.token, parse_mode="html") 32 | dp = Dispatcher() 33 | dp.include_router(main_router) 34 | dp.startup.register(on_startup) 35 | dp.shutdown.register(on_shutdown) 36 | try: 37 | if settings.webhook.url: 38 | app = configure_app(dp, bot, settings) 39 | runner = web.AppRunner(app) 40 | await runner.setup() 41 | site = web.TCPSite( 42 | runner, host=settings.webapp.host, port=settings.webapp.port 43 | ) 44 | await site.start() 45 | await asyncio.Event().wait() 46 | else: 47 | await dp.start_polling(bot, settings=settings) 48 | finally: 49 | await bot.session.close() 50 | 51 | 52 | if __name__ == "__main__": 53 | try: 54 | asyncio.run(main()) 55 | except KeyboardInterrupt: 56 | exit(0) 57 | -------------------------------------------------------------------------------- /app/data_structures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/app/data_structures/__init__.py -------------------------------------------------------------------------------- /app/data_structures/arq.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime, timedelta 3 | from typing import Any, Dict, Optional, Union 4 | 5 | 6 | @dataclass 7 | class JobConfig: 8 | job_id: Optional[str] = None 9 | run_after: Optional[Union[timedelta, int, float]] = None 10 | run_date: Optional[datetime] = None 11 | 12 | def __post_init__(self) -> None: 13 | assert not ( 14 | self.run_after and self.run_date 15 | ), "Only one of run_after or run_date can be specified" 16 | 17 | def as_dict(self) -> Dict[str, Any]: 18 | return { 19 | "_job_id": self.job_id, 20 | "_defer_by": self.run_after, 21 | "_defer_until": self.run_date, 22 | } 23 | 24 | def __str__(self): 25 | return ", ".join([f"{k}={v}" for k, v in self.__annotations__.items()]) 26 | -------------------------------------------------------------------------------- /app/data_structures/callback_data.py: -------------------------------------------------------------------------------- 1 | from aiogram.dispatcher.filters.callback_data import CallbackData 2 | 3 | 4 | class CaptchaAnswerCallbackData(CallbackData, prefix="captcha"): 5 | chat_id: int 6 | user_id: int 7 | salt: str 8 | answer: str 9 | -------------------------------------------------------------------------------- /app/data_structures/captcha.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum 3 | from io import BytesIO 4 | from typing import List, NamedTuple, Set 5 | 6 | from pydantic import BaseModel 7 | 8 | from app.data_structures.redis import RedisBaseKey 9 | 10 | Emoji = NamedTuple("Emoji", [("symbol", str), ("code", str)]) 11 | 12 | 13 | @dataclass 14 | class CaptchaData: 15 | image: BytesIO 16 | correct_emoji_code: str 17 | emoji_data: Set[Emoji] 18 | 19 | 20 | class CaptchaResultStatus(str, Enum): 21 | SUCCESS = "success" 22 | FAILURE = "failure" 23 | 24 | 25 | class EmojiData(BaseModel): 26 | symbol: str 27 | title: str 28 | code: str 29 | 30 | 31 | class CaptchaStaticData(BaseModel): 32 | emoji: List[EmojiData] 33 | 34 | 35 | class CaptchaRedisKeyPrefix(str): 36 | LOCK_JOB = "LOCK" 37 | ANSWER = "ANSWER" 38 | 39 | 40 | @dataclass 41 | class LockJobKey(RedisBaseKey): 42 | prefix = CaptchaRedisKeyPrefix.LOCK_JOB 43 | chat_id: int 44 | user_id: int 45 | salt: str 46 | 47 | 48 | @dataclass 49 | class AnswerKey(RedisBaseKey): 50 | prefix = CaptchaRedisKeyPrefix.ANSWER 51 | chat_id: int 52 | user_id: int 53 | salt: str 54 | -------------------------------------------------------------------------------- /app/data_structures/redis.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class RedisBaseKey: 6 | @property 7 | def prefix(self) -> str: 8 | raise NotImplementedError 9 | 10 | def pack(self) -> str: 11 | attrs = [getattr(self, attr) for attr in self.__annotations__] 12 | return f"{self.prefix}:{':'.join(str(attr) for attr in attrs)}" 13 | 14 | @classmethod 15 | def parse(cls, key: str) -> "RedisBaseKey": 16 | parts = key.split(":") 17 | if parts[0] != cls.prefix: 18 | raise ValueError(f"Invalid key prefix: {parts[0]}") 19 | return cls(**dict(zip(cls.__annotations__, parts[1:]))) 20 | -------------------------------------------------------------------------------- /app/filters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/app/filters/__init__.py -------------------------------------------------------------------------------- /app/filters/chat_type.py: -------------------------------------------------------------------------------- 1 | from typing import List, Set, Tuple, Union 2 | 3 | from aiogram.dispatcher.filters import BaseFilter 4 | from aiogram.types import ( 5 | CallbackQuery, 6 | Chat, 7 | ChatJoinRequest, 8 | ChatMemberUpdated, 9 | Message, 10 | ) 11 | from pydantic import validator 12 | 13 | ChatUpdate = Union[Message, CallbackQuery, Message, ChatJoinRequest, ChatMemberUpdated] 14 | ChatTypeT = Union[str, List[str], Set[str], Tuple[str, ...]] 15 | CHAT_TYPES: Set[str] = {"private", "group", "supergroup", "channel"} 16 | 17 | 18 | class ChatType(BaseFilter): 19 | types: ChatTypeT 20 | 21 | @validator("types", pre=True) 22 | def pre_check_types( 23 | cls, value: ChatTypeT 24 | ) -> Union[List[str], Set[str], Tuple[str, ...]]: 25 | if isinstance(value, str): 26 | value = {value} 27 | if any(chat_type not in CHAT_TYPES for chat_type in value): 28 | raise ValueError("Unknown chat type") 29 | return value 30 | 31 | async def __call__(self, obj: ChatUpdate, event_chat: Chat) -> bool: 32 | if event_chat is None: 33 | # e.g. callback query in a message sent from inline mode 34 | return False 35 | return event_chat.type in self.types 36 | -------------------------------------------------------------------------------- /app/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | 3 | from .chat import router as chat_router 4 | from .globals import router as global_router 5 | from .private import router as private_router 6 | 7 | main_router = Router() 8 | 9 | main_router.include_router(chat_router) 10 | main_router.include_router(global_router) 11 | main_router.include_router(private_router) 12 | -------------------------------------------------------------------------------- /app/handlers/chat/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | 3 | from app.filters.chat_type import ChatType 4 | 5 | from .chat_join_request import router as chat_join_request_router 6 | from .my_chat_member import router as my_chat_member_router 7 | 8 | router = Router() 9 | router.chat_join_request.filter(ChatType(types={"group", "supergroup"})) 10 | router.my_chat_member.filter(ChatType(types={"group", "supergroup"})) 11 | 12 | router.include_router(chat_join_request_router) 13 | router.include_router(my_chat_member_router) 14 | -------------------------------------------------------------------------------- /app/handlers/chat/chat_join_request.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Router, html 2 | from aiogram.types import BufferedInputFile, ChatJoinRequest 3 | 4 | from app.misc.filename_utils import generate_captcha_image_filename 5 | from app.misc.kb_generators import generate_captcha_keyboard 6 | from app.services.captcha import CaptchaService 7 | 8 | router = Router() 9 | 10 | 11 | @router.chat_join_request() 12 | async def handle_chat_join_request( 13 | update: ChatJoinRequest, bot: Bot, captcha: CaptchaService 14 | ) -> None: 15 | chat_id = update.chat.id 16 | user_id = update.from_user.id 17 | captcha_data = await captcha.generate_captcha() 18 | salt = await captcha.lock_user( 19 | chat_id, user_id, correct_code=captcha_data.correct_emoji_code 20 | ) 21 | captcha_text = ( 22 | "Привет 👋\n" 23 | "Ты отправил(а) заявку на вступление в чат {chat}.\n" 24 | "Но прежде чем я её одобрю, давай проверим, что ты действительно человек:\n" 25 | "Выбери правильный вариант в соответствии с заданием на картинке." 26 | ).format(chat=html.bold(update.chat.title) if update.chat.title else "") 27 | captcha_kb = generate_captcha_keyboard( 28 | chat_id, user_id, salt, emoji_data=captcha_data.emoji_data 29 | ) 30 | captcha_photo = BufferedInputFile( 31 | file=captcha_data.image.getvalue(), 32 | filename=generate_captcha_image_filename(chat_id, user_id), 33 | ) 34 | await bot.send_photo( 35 | user_id, photo=captcha_photo, caption=captcha_text, reply_markup=captcha_kb 36 | ) 37 | -------------------------------------------------------------------------------- /app/handlers/chat/my_chat_member.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Router, types 2 | from aiogram.dispatcher.filters import ( 3 | ADMINISTRATOR, 4 | JOIN_TRANSITION, 5 | KICKED, 6 | LEFT, 7 | MEMBER, 8 | RESTRICTED, 9 | ChatMemberUpdatedFilter, 10 | ) 11 | 12 | router = Router() 13 | 14 | NEED_PERMISSIONS = ("can_invite_users",) 15 | PROMOTED_TRANSITION = ( 16 | MEMBER | RESTRICTED | LEFT | KICKED | ADMINISTRATOR 17 | ) >> ADMINISTRATOR 18 | 19 | 20 | def has_bot_need_permissions(member: types.ChatMember) -> bool: 21 | if not isinstance(member, types.ChatMemberAdministrator): 22 | return False 23 | return all(getattr(member, permission) for permission in NEED_PERMISSIONS) 24 | 25 | 26 | @router.my_chat_member( 27 | ChatMemberUpdatedFilter(member_status_changed=JOIN_TRANSITION), 28 | ) 29 | async def bot_joined(update: types.ChatMemberUpdated, bot: Bot) -> None: 30 | text = ( 31 | "Привет! У вас есть проблема с ботами-спамерами в чате?\n" 32 | "У меня есть решение - captcha.\n\n" 33 | ) 34 | if has_bot_need_permissions(update.new_chat_member): 35 | text += ( 36 | "🎉 Я вижу, вы мне уже выдали нужные права администратора.\n" 37 | "Теперь я буду проверять всех новых участников на наличие спамеров." 38 | ) 39 | else: 40 | text += ( 41 | "😢 Я вижу, вы мне ещё не выдали нужные права администратора.\n" 42 | "К сожалению, без них я не смогу проверять новых участников на наличие спамеров.\n\n" 43 | "Права, которые мне нужны: \n" 44 | "👉 Пригласительные ссылки" 45 | ) 46 | 47 | await bot.send_message(chat_id=update.chat.id, text=text) 48 | 49 | 50 | @router.my_chat_member( 51 | ChatMemberUpdatedFilter(member_status_changed=PROMOTED_TRANSITION), 52 | ) 53 | async def bot_promoted(update: types.ChatMemberUpdated, bot: Bot) -> None: 54 | if has_bot_need_permissions(update.new_chat_member): 55 | text = ( 56 | "🎉 Я получил нужные права администратора.\n" 57 | "Теперь я буду проверять новые заявки и отсеивать спамеров." 58 | ) 59 | else: 60 | text = ( 61 | "😢 Я получил не все нужные права администратора.\n" 62 | "К сожалению, без них я не смогу проверять новых заявки и отсеивать спамеров.\n\n" 63 | "Права, которые мне нужны: \n" 64 | "👉 Пригласительные ссылки" 65 | ) 66 | await bot.send_message(chat_id=update.chat.id, text=text) 67 | 68 | 69 | @router.my_chat_member( 70 | ChatMemberUpdatedFilter(member_status_changed=ADMINISTRATOR >> MEMBER), 71 | ) 72 | async def bot_demoted(update: types.ChatMemberUpdated, bot: Bot) -> None: 73 | text = ( 74 | "😢 К сожалению, без прав администратора я не смогу проверять новых заявки " 75 | "и отсеивать спамеров.\n\n" 76 | "Права, которые мне нужны: \n" 77 | "👉 Пригласительные ссылки" 78 | ) 79 | await bot.send_message(chat_id=update.chat.id, text=text) 80 | -------------------------------------------------------------------------------- /app/handlers/globals/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | 3 | from .errors import router as errors_router 4 | 5 | router = Router() 6 | router.include_router(errors_router) 7 | -------------------------------------------------------------------------------- /app/handlers/globals/errors.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | from aiogram.types import Update 3 | 4 | from app.misc.loggers import logger 5 | 6 | router = Router() 7 | 8 | 9 | @router.errors() 10 | async def error_handler(update: Update, exception: Exception): 11 | exception_message = ( 12 | exception.message if hasattr(exception, "message") else str(exception) 13 | ) 14 | logger.exception(f"Caused error in update {update.json()}: {exception_message} ") 15 | return True 16 | -------------------------------------------------------------------------------- /app/handlers/private/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router 2 | 3 | from app.filters.chat_type import ChatType 4 | 5 | from .callback_query import router as callback_query_router 6 | from .message import router as message_router 7 | 8 | router = Router() 9 | router.callback_query.filter(ChatType(types="private")) 10 | router.message.filter(ChatType(types="private")) 11 | 12 | router.include_router(callback_query_router) 13 | router.include_router(message_router) 14 | -------------------------------------------------------------------------------- /app/handlers/private/callback_query.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Router, html 2 | from aiogram.types import BufferedInputFile, CallbackQuery, InputMediaPhoto 3 | 4 | from app.data_structures.callback_data import CaptchaAnswerCallbackData 5 | from app.data_structures.captcha import CaptchaResultStatus 6 | from app.misc.filename_utils import generate_captcha_image_filename 7 | from app.misc.kb_generators import generate_chat_url_keyboard 8 | from app.services.captcha import CaptchaService 9 | 10 | router = Router() 11 | 12 | 13 | @router.callback_query(CaptchaAnswerCallbackData.filter()) 14 | async def handle_captcha_answer( 15 | query: CallbackQuery, 16 | bot: Bot, 17 | callback_data: CaptchaAnswerCallbackData, 18 | captcha: CaptchaService, 19 | ) -> None: 20 | chat_id = callback_data.chat_id 21 | user_id = callback_data.user_id 22 | salt = callback_data.salt 23 | answer = callback_data.answer 24 | markup = None 25 | if not await captcha.is_captcha_target(chat_id, user_id, salt): 26 | text = "Капча уже недействительна" 27 | result_status = CaptchaResultStatus.FAILURE 28 | else: 29 | if await captcha.is_correct_answer(chat_id, user_id, salt, answer): 30 | chat = await bot.get_chat(chat_id) 31 | text = "Верно! Вы были допущены в чат {chat}".format( 32 | chat=html.bold(chat.title) if chat.title else "" 33 | ) 34 | markup = ( 35 | generate_chat_url_keyboard(chat.username) if chat.username else None 36 | ) 37 | result_status = CaptchaResultStatus.SUCCESS 38 | await bot.approve_chat_join_request(chat_id, user_id) 39 | else: 40 | text = "К сожалению ответ неверный. Попробуйте ещё раз позже." 41 | result_status = CaptchaResultStatus.FAILURE 42 | await bot.decline_chat_join_request(chat_id, user_id) 43 | await captcha.unlock_user(chat_id, user_id, salt) 44 | result_image = await captcha.get_captcha_result_image(result_status) 45 | image_filename = generate_captcha_image_filename(chat_id, user_id, result_status) 46 | await bot.edit_message_media( 47 | media=InputMediaPhoto( 48 | media=BufferedInputFile(result_image.getvalue(), filename=image_filename), 49 | caption=text, 50 | ), 51 | chat_id=query.message.chat.id, 52 | message_id=query.message.message_id, 53 | reply_markup=markup, 54 | ) 55 | -------------------------------------------------------------------------------- /app/handlers/private/message.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Router 2 | from aiogram.dispatcher.filters.command import Command, CommandStart 3 | from aiogram.types import Message 4 | 5 | from app.misc.kb_generators import generate_invite_bot_keyboard 6 | from app.misc.settings_reader import Settings 7 | 8 | router = Router() 9 | 10 | 11 | @router.message(CommandStart()) 12 | async def handle_start_command(message: Message, bot: Bot) -> None: 13 | text = ( 14 | "Привет! У вас есть проблема с ботами-спамерами в чате?\n" 15 | "У меня есть решение - captcha.\n\n" 16 | "Я буду проверять всех новые заявки в вашем чате на наличие спамеров.\n" 17 | "Для того, чтобы начать, выполните следующие действия:\n" 18 | "1. Перейдите в настройки чата и включите вступление по заявкам\n" 19 | "Тип чата > Заявки на вступление\n" 20 | "2. Нажмите на кнопку ниже, чтобы добавить меня в чат\n" 21 | "3. Следуйте моим дальнейшим инструкциям" 22 | ) 23 | bot_user = await bot.get_me() 24 | markup = generate_invite_bot_keyboard(bot_username=bot_user.username) 25 | await message.answer(text, reply_markup=markup) 26 | 27 | 28 | @router.message(Command(commands=["privacy"])) 29 | async def handle_privacy_command(message: Message, settings: Settings) -> None: 30 | text = f"Privacy Policy:\n{settings.bot.privacy_policy_link}" 31 | await message.answer(text) 32 | -------------------------------------------------------------------------------- /app/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/app/misc/__init__.py -------------------------------------------------------------------------------- /app/misc/configure.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Dict 3 | 4 | from app.misc.settings_reader import Settings 5 | from app.services.captcha import CaptchaService 6 | from app.services.captcha_generator import CaptchaGenerator 7 | from app.services.captcha_scheduler import CaptchaScheduler 8 | from app.services.lock_user import LockUserService 9 | 10 | 11 | async def configure_services(settings: Settings) -> Dict[str, Any]: 12 | lock_service = LockUserService(connection_uri=settings.redis.connection_uri) 13 | captcha_scheduler = CaptchaScheduler() 14 | captcha_generator = CaptchaGenerator() 15 | captcha = CaptchaService( 16 | lock_service, 17 | captcha_scheduler, 18 | captcha_generator, 19 | captcha_duration=settings.captcha.duration, 20 | ) 21 | await captcha_scheduler.init(connection_uri=settings.redis.connection_uri) 22 | captcha_generator.load_emoji() 23 | return {"captcha": captcha} 24 | 25 | 26 | def configure_logging() -> None: 27 | logging.getLogger("aiohttp.access").setLevel(logging.WARNING) 28 | logging.basicConfig( 29 | level=logging.INFO, 30 | format="%(filename)s:%(lineno)d #%(levelname)-8s [%(asctime)s] - %(name)s - %(message)s", 31 | ) 32 | -------------------------------------------------------------------------------- /app/misc/exceptions.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | 4 | class CustomException(Exception): 5 | def __init__( 6 | self, 7 | text: str = "", 8 | user_id: Optional[int] = None, 9 | chat_id: Optional[int] = None, 10 | *args: Optional[Any], 11 | ) -> None: 12 | super().__init__(text, args) 13 | self.text = text 14 | self.user_id = user_id 15 | self.chat_id = chat_id 16 | 17 | def __str__(self) -> str: 18 | text = f"{self.__class__.__name__}: {self.text}" 19 | if self.user_id is not None: 20 | text += f", by user {self.user_id} " 21 | if self.chat_id is not None: 22 | text += f"in chat {self.chat_id}" 23 | return text 24 | 25 | def __repr__(self) -> str: 26 | return str(self) 27 | 28 | 29 | class FileOpenError(IOError, CustomException): 30 | def __init__(self, *args: Optional[Any]) -> None: 31 | super(CustomException).__init__(*args) 32 | 33 | 34 | class CaptchaLoadError(ValueError, CustomException): 35 | def __init__(self, *args: Optional[Any]) -> None: 36 | super(CustomException).__init__(*args) 37 | -------------------------------------------------------------------------------- /app/misc/filename_utils.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from app.data_structures.captcha import CaptchaResultStatus 4 | 5 | 6 | def generate_captcha_image_filename( 7 | chat_id: int, user_id: int, result_status: Optional[CaptchaResultStatus] = None 8 | ) -> str: 9 | filename = f"captcha_{chat_id}_{user_id}" 10 | if result_status is not None: 11 | filename += f"_{result_status.name}" 12 | return f"{filename}.png" 13 | -------------------------------------------------------------------------------- /app/misc/kb_generators.py: -------------------------------------------------------------------------------- 1 | from typing import Set 2 | 3 | from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup 4 | from aiogram.utils.keyboard import InlineKeyboardBuilder 5 | 6 | from app.data_structures.callback_data import CaptchaAnswerCallbackData 7 | from app.data_structures.captcha import Emoji 8 | 9 | MAX_CAPTCHA_BUTTONS_ROW_LENGTH = 5 10 | 11 | 12 | def generate_captcha_keyboard( 13 | chat_id: int, user_id: int, salt: str, emoji_data: Set[Emoji] 14 | ) -> InlineKeyboardMarkup: 15 | builder = InlineKeyboardBuilder() 16 | for emoji in emoji_data: 17 | builder.add( 18 | InlineKeyboardButton( 19 | text=emoji.symbol, 20 | callback_data=CaptchaAnswerCallbackData( 21 | chat_id=chat_id, user_id=user_id, salt=salt, answer=emoji.code 22 | ).pack(), 23 | ) 24 | ) 25 | builder.adjust(MAX_CAPTCHA_BUTTONS_ROW_LENGTH, repeat=True) 26 | return builder.as_markup() 27 | 28 | 29 | def generate_invite_bot_keyboard(bot_username: str) -> InlineKeyboardMarkup: 30 | return InlineKeyboardMarkup( 31 | inline_keyboard=[ 32 | [ 33 | InlineKeyboardButton( 34 | text="Пригласить бота", 35 | url=f"https://t.me/{bot_username}?startgroup=&admin=invite_users+delete_messages", 36 | ) 37 | ] 38 | ] 39 | ) 40 | 41 | 42 | def generate_chat_url_keyboard(chat_username: str) -> InlineKeyboardMarkup: 43 | return InlineKeyboardMarkup( 44 | inline_keyboard=[ 45 | [ 46 | InlineKeyboardButton( 47 | text="Перейти в чат", 48 | url=f"https://t.me/{chat_username}", 49 | ) 50 | ] 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /app/misc/loggers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger("captcha_bot") 4 | arq_logger = logging.getLogger("arq") 5 | -------------------------------------------------------------------------------- /app/misc/paths.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | BASE_DIR = Path(__file__).parent.parent.parent 4 | APP_DIR = BASE_DIR / "app" 5 | ASSETS_DIR = BASE_DIR / "assets" 6 | -------------------------------------------------------------------------------- /app/misc/settings_reader.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import datetime 3 | from typing import Any, Mapping, Tuple, Union 4 | 5 | from pydantic import BaseModel, BaseSettings, validator 6 | 7 | from app.misc.paths import BASE_DIR 8 | 9 | 10 | def ini_file_settings(_: Any) -> Mapping[str, Any]: 11 | config = configparser.ConfigParser() 12 | config.read(BASE_DIR / "config.ini") 13 | return { 14 | section: values for section, values in config.items() if section != "DEFAULT" 15 | } 16 | 17 | 18 | class BotSettings(BaseModel): 19 | token: str 20 | privacy_policy_link: str 21 | 22 | 23 | class WebhookSettings(BaseModel): 24 | host: str 25 | path: str 26 | 27 | @validator("host") 28 | def host_to_url(cls, v: str) -> str: 29 | if v.startswith("https"): 30 | return v 31 | return f"https://{v}" 32 | 33 | @property 34 | def url(self) -> str: 35 | if self.host and self.path: 36 | return f"{self.host}{self.path}" 37 | return "" 38 | 39 | 40 | class WebAppSettings(BaseModel): 41 | host: str 42 | port: int 43 | 44 | 45 | class RedisSettings(BaseModel): 46 | host: str 47 | port: int = 6379 48 | db: int 49 | password: str = "" 50 | 51 | @property 52 | def connection_uri(self) -> str: 53 | return f"redis://:{self.password}@{self.host}:{self.port}/{self.db}" 54 | 55 | 56 | class CaptchaSettings(BaseModel): 57 | duration: Union[int, datetime.timedelta] 58 | 59 | @validator("duration") 60 | def to_timedelta(cls, v: Union[int, datetime.timedelta]) -> datetime.timedelta: 61 | if isinstance(v, datetime.timedelta): 62 | return v 63 | return datetime.timedelta(seconds=v) 64 | 65 | 66 | class Settings(BaseSettings): 67 | bot: BotSettings 68 | webhook: WebhookSettings 69 | webapp: WebAppSettings 70 | redis: RedisSettings 71 | captcha: CaptchaSettings 72 | 73 | class Config: 74 | @classmethod 75 | def customise_sources( 76 | cls, 77 | init_settings: Mapping[str, Any], 78 | env_settings: Mapping[str, Any], 79 | file_secret_settings: Mapping[str, Any], 80 | ) -> Tuple: # type: ignore 81 | return ( 82 | init_settings, 83 | ini_file_settings, 84 | env_settings, 85 | file_secret_settings, 86 | ) 87 | -------------------------------------------------------------------------------- /app/misc/uuid.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import os 3 | 4 | ALPHABET = list("23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") 5 | DEFAULT_UUID_LENGTH = 20 6 | 7 | 8 | def int_to_string(number: int, padding: int) -> str: 9 | output = "" 10 | alpha_len = len(ALPHABET) 11 | while number: 12 | number, digit = divmod(number, alpha_len) 13 | output += ALPHABET[digit] 14 | remainder = max(padding - len(output), 0) 15 | output = output + ALPHABET[0] * remainder 16 | return output[::-1] 17 | 18 | 19 | def generate_uuid(length: int = DEFAULT_UUID_LENGTH) -> str: 20 | random_num = int(binascii.b2a_hex(os.urandom(length)), 16) 21 | return int_to_string(random_num, padding=length)[:length] 22 | -------------------------------------------------------------------------------- /app/misc/webhook.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Dispatcher 2 | from aiogram.dispatcher.webhook.aiohttp_server import ( 3 | SimpleRequestHandler, 4 | setup_application, 5 | ) 6 | from aiohttp import web 7 | 8 | from app.misc.settings_reader import Settings 9 | 10 | 11 | def configure_app(dp: Dispatcher, bot: Bot, settings: Settings) -> web.Application: 12 | app = web.Application() 13 | SimpleRequestHandler(dispatcher=dp, bot=bot, settings=settings).register( 14 | app, path=settings.webhook.path 15 | ) 16 | setup_application(app, dp, bot=bot) 17 | return app 18 | -------------------------------------------------------------------------------- /app/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/app/services/__init__.py -------------------------------------------------------------------------------- /app/services/captcha.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from io import BytesIO 3 | 4 | from app.data_structures.captcha import CaptchaData, CaptchaResultStatus 5 | from app.misc.uuid import generate_uuid 6 | from app.services.captcha_generator import CaptchaGenerator 7 | from app.services.captcha_scheduler import CaptchaScheduler 8 | from app.services.lock_user import LockUserService 9 | 10 | 11 | class CaptchaService: 12 | def __init__( 13 | self, 14 | lock_service: LockUserService, 15 | scheduler: CaptchaScheduler, 16 | captcha_generator: CaptchaGenerator, 17 | captcha_duration: datetime.timedelta, 18 | ) -> None: 19 | self._lock_service = lock_service 20 | self._scheduler = scheduler 21 | self._captcha_duration = captcha_duration 22 | self._captcha_generator = captcha_generator 23 | 24 | async def generate_captcha(self) -> CaptchaData: 25 | return await self._captcha_generator.generate_captcha_data() 26 | 27 | async def get_captcha_result_image(self, status: CaptchaResultStatus) -> BytesIO: 28 | filename = f"captcha_{status.value}" 29 | return self._captcha_generator.get_image(filename, "png") 30 | 31 | async def is_captcha_target(self, chat_id: int, user_id: int, salt: str) -> bool: 32 | return await self._lock_service.is_captcha_target(chat_id, user_id, salt) 33 | 34 | async def is_correct_answer( 35 | self, chat_id: int, user_id: int, salt: str, answer: str 36 | ) -> bool: 37 | correct_code = await self._lock_service.get_correct_answer( 38 | chat_id, user_id, salt 39 | ) 40 | return correct_code == answer 41 | 42 | async def lock_user( 43 | self, 44 | chat_id: int, 45 | user_id: int, 46 | correct_code: str, 47 | ) -> str: 48 | salt = generate_uuid(length=5) 49 | await self._lock_service.set_correct_answer( 50 | chat_id, user_id, salt, correct_code 51 | ) 52 | await self._scheduler.enqueue_join_expire_job( 53 | chat_id, user_id, salt, captcha_duration=self._captcha_duration 54 | ) 55 | return salt 56 | 57 | async def unlock_user(self, chat_id: int, user_id: int, salt: str) -> None: 58 | await self._lock_service.delete_correct_answer(chat_id, user_id, salt) 59 | await self._scheduler.abort_join_expire_job(chat_id, user_id, salt) 60 | -------------------------------------------------------------------------------- /app/services/captcha_generator.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from io import BytesIO 4 | from typing import List, Optional, Tuple 5 | 6 | from app.data_structures.captcha import CaptchaData, CaptchaStaticData, Emoji, EmojiData 7 | from app.misc.exceptions import CaptchaLoadError, FileOpenError 8 | from app.misc.loggers import logger 9 | from app.misc.paths import ASSETS_DIR 10 | 11 | DEFAULT_CAPTCHA_BUTTONS_NUMBER = 10 12 | 13 | MAPPING_FILE = ASSETS_DIR / "mapping.json" 14 | IMG_DIR = ASSETS_DIR / "img" 15 | 16 | 17 | class CaptchaGenerator: 18 | _all_emoji: Optional[List[EmojiData]] = None 19 | 20 | async def generate_captcha_data( 21 | self, buttons_number: int = DEFAULT_CAPTCHA_BUTTONS_NUMBER 22 | ) -> CaptchaData: 23 | if self._all_emoji is None: 24 | raise CaptchaLoadError("Emoji didn't loaded") 25 | try: 26 | correct_emoji, chosen_emoji = self._make_random_emoji_sequence( 27 | buttons_number 28 | ) 29 | image = self.get_image(correct_emoji.code, "png") 30 | chosen_emoji_data = { 31 | Emoji(emoji.symbol, emoji.code) for emoji in chosen_emoji 32 | } 33 | return CaptchaData( 34 | image=image, 35 | correct_emoji_code=correct_emoji.code, 36 | emoji_data=chosen_emoji_data, 37 | ) 38 | except FileOpenError: 39 | await asyncio.sleep(0.1) 40 | await self.generate_captcha_data(buttons_number) 41 | 42 | def _make_random_emoji_sequence( 43 | self, total_number: int 44 | ) -> Tuple[EmojiData, List[EmojiData]]: 45 | if self._all_emoji is None: 46 | raise CaptchaLoadError("Emoji didn't loaded") 47 | chosen_emoji = random.sample(self._all_emoji, total_number) 48 | correct_emoji = chosen_emoji[0] 49 | random.shuffle(chosen_emoji) 50 | return correct_emoji, chosen_emoji 51 | 52 | @staticmethod 53 | def get_image(filename: str, extension: str = "png") -> BytesIO: 54 | full_filename = f"{filename}.{extension}" 55 | try: 56 | with open(IMG_DIR / full_filename, "rb") as f: 57 | img = BytesIO() 58 | img.write(f.read()) 59 | return img 60 | except OSError as e: 61 | logger.error( 62 | "Error on opening image file {filename}: {error!r}".format( 63 | filename=full_filename, 64 | error=e, 65 | ), 66 | ) 67 | raise FileOpenError 68 | 69 | @classmethod 70 | def load_emoji(cls) -> None: 71 | logger.debug("Loading emoji from JSON...") 72 | captcha_static_data: CaptchaStaticData = CaptchaStaticData.parse_file( 73 | MAPPING_FILE 74 | ) 75 | logger.debug( 76 | "{number} emoji was loaded successfully".format( 77 | number=len(captcha_static_data.emoji) 78 | ) 79 | ) 80 | cls._all_emoji = captcha_static_data.emoji 81 | -------------------------------------------------------------------------------- /app/services/captcha_scheduler.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | from app.data_structures.arq import JobConfig 4 | from app.data_structures.captcha import LockJobKey 5 | from app.services.scheduler import ArqScheduler 6 | 7 | 8 | class CaptchaScheduler(ArqScheduler): 9 | async def enqueue_join_expire_job( 10 | self, chat_id: int, user_id: int, salt: str, captcha_duration: timedelta 11 | ) -> None: 12 | await self.enqueue_job( 13 | "join_expired_task", 14 | job_kwargs={"chat_id": chat_id, "user_id": user_id, "salt": salt}, 15 | job_config=JobConfig( 16 | job_id=LockJobKey(chat_id=chat_id, user_id=user_id, salt=salt).pack(), 17 | run_after=captcha_duration, 18 | ), 19 | ) 20 | 21 | async def abort_join_expire_job( 22 | self, chat_id: int, user_id: int, salt: str 23 | ) -> None: 24 | lock_key = LockJobKey(chat_id=chat_id, user_id=user_id, salt=salt).pack() 25 | await self.abort_job(lock_key) 26 | -------------------------------------------------------------------------------- /app/services/lock_user.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from app.data_structures.captcha import AnswerKey 4 | from app.services.redis import BaseRedis 5 | 6 | 7 | class LockUserService(BaseRedis): 8 | def __init__(self, connection_uri: str): 9 | super().__init__(connection_uri) 10 | 11 | async def is_captcha_target(self, chat_id: int, user_id: int, salt: str) -> bool: 12 | answer_key = AnswerKey(chat_id, user_id, salt).pack() 13 | return bool(await self.redis.exists(answer_key)) 14 | 15 | async def set_correct_answer( 16 | self, chat_id: int, user_id: int, salt: str, correct_code: str 17 | ) -> None: 18 | answer_key = AnswerKey(chat_id, user_id, salt).pack() 19 | await self.redis.set(answer_key, correct_code) 20 | 21 | async def get_correct_answer( 22 | self, chat_id: int, user_id: int, salt: str 23 | ) -> Optional[str]: 24 | answer_key = AnswerKey(chat_id, user_id, salt).pack() 25 | return await self.redis.get(answer_key) 26 | 27 | async def delete_correct_answer( 28 | self, chat_id: int, user_id: int, salt: str 29 | ) -> None: 30 | answer_key = AnswerKey(chat_id, user_id, salt).pack() 31 | await self.redis.delete(answer_key) 32 | -------------------------------------------------------------------------------- /app/services/redis.py: -------------------------------------------------------------------------------- 1 | from redis import asyncio as aioredis 2 | 3 | 4 | class BaseRedis: 5 | def __init__( 6 | self, 7 | connection_uri: str, 8 | decode_responses: bool = True, 9 | ): 10 | self._redis: aioredis.Redis = aioredis.from_url( 11 | connection_uri, decode_responses=decode_responses 12 | ) 13 | 14 | @property 15 | def redis(self) -> aioredis.Redis: 16 | return self._redis 17 | -------------------------------------------------------------------------------- /app/services/scheduler.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from contextlib import suppress 3 | from typing import Any, Dict, Optional 4 | 5 | from arq import create_pool 6 | from arq.connections import ArqRedis, RedisSettings 7 | from arq.jobs import Job 8 | 9 | from app.data_structures.arq import JobConfig 10 | from app.misc.loggers import logger 11 | 12 | 13 | class ArqScheduler: 14 | def __init__(self) -> None: 15 | self._redis: Optional[ArqRedis] = None 16 | 17 | async def init( 18 | self, 19 | connection_uri: str, 20 | ) -> None: 21 | self._redis = await create_pool(RedisSettings.from_dsn(connection_uri)) 22 | 23 | async def enqueue_job( 24 | self, task: str, job_kwargs: Dict[str, Any], job_config: JobConfig 25 | ) -> Optional[Job]: 26 | job = await self._enqueue_job(task, job_kwargs, job_config) 27 | logger.info( 28 | "Enqueued job ({task}) with params {kwargs}".format( 29 | task=task, kwargs=job_kwargs 30 | ) 31 | ) 32 | return job 33 | 34 | async def abort_job(self, job_id: str) -> None: 35 | await self._abort_job(job_id) 36 | logger.info("Aborted job ({job_id})".format(job_id=job_id)) 37 | 38 | async def _enqueue_job( 39 | self, task: str, task_kwargs: Dict[str, Any], task_config: JobConfig 40 | ) -> Optional[Job]: 41 | if self._redis is None: 42 | raise Exception("Redis connection is not initialized") 43 | kwargs = task_kwargs | task_config.as_dict() 44 | return await self._redis.enqueue_job(function=task, **kwargs) 45 | 46 | async def _abort_job(self, job_id: str) -> None: 47 | if self._redis is None: 48 | raise Exception("Redis connection is not initialized") 49 | with suppress(asyncio.TimeoutError): 50 | await Job(job_id, redis=self._redis).abort(timeout=0) 51 | -------------------------------------------------------------------------------- /assets/img/1F308.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F308.png -------------------------------------------------------------------------------- /assets/img/1F334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F334.png -------------------------------------------------------------------------------- /assets/img/1F335.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F335.png -------------------------------------------------------------------------------- /assets/img/1F339.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F339.png -------------------------------------------------------------------------------- /assets/img/1F345.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F345.png -------------------------------------------------------------------------------- /assets/img/1F346.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F346.png -------------------------------------------------------------------------------- /assets/img/1F347.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F347.png -------------------------------------------------------------------------------- /assets/img/1F349.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F349.png -------------------------------------------------------------------------------- /assets/img/1F34B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F34B.png -------------------------------------------------------------------------------- /assets/img/1F34C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F34C.png -------------------------------------------------------------------------------- /assets/img/1F34D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F34D.png -------------------------------------------------------------------------------- /assets/img/1F34E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F34E.png -------------------------------------------------------------------------------- /assets/img/1F34F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F34F.png -------------------------------------------------------------------------------- /assets/img/1F352.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F352.png -------------------------------------------------------------------------------- /assets/img/1F353.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F353.png -------------------------------------------------------------------------------- /assets/img/1F354.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F354.png -------------------------------------------------------------------------------- /assets/img/1F355.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F355.png -------------------------------------------------------------------------------- /assets/img/1F366.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F366.png -------------------------------------------------------------------------------- /assets/img/1F369.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F369.png -------------------------------------------------------------------------------- /assets/img/1F36A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F36A.png -------------------------------------------------------------------------------- /assets/img/1F36B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F36B.png -------------------------------------------------------------------------------- /assets/img/1F36D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F36D.png -------------------------------------------------------------------------------- /assets/img/1F37A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F37A.png -------------------------------------------------------------------------------- /assets/img/1F37F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F37F.png -------------------------------------------------------------------------------- /assets/img/1F382.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F382.png -------------------------------------------------------------------------------- /assets/img/1F385.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F385.png -------------------------------------------------------------------------------- /assets/img/1F388.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F388.png -------------------------------------------------------------------------------- /assets/img/1F3D4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F3D4.png -------------------------------------------------------------------------------- /assets/img/1F3E0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F3E0.png -------------------------------------------------------------------------------- /assets/img/1F404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F404.png -------------------------------------------------------------------------------- /assets/img/1F40A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F40A.png -------------------------------------------------------------------------------- /assets/img/1F40C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F40C.png -------------------------------------------------------------------------------- /assets/img/1F40D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F40D.png -------------------------------------------------------------------------------- /assets/img/1F411.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F411.png -------------------------------------------------------------------------------- /assets/img/1F414.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F414.png -------------------------------------------------------------------------------- /assets/img/1F416.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F416.png -------------------------------------------------------------------------------- /assets/img/1F419.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F419.png -------------------------------------------------------------------------------- /assets/img/1F41D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F41D.png -------------------------------------------------------------------------------- /assets/img/1F422.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F422.png -------------------------------------------------------------------------------- /assets/img/1F424.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F424.png -------------------------------------------------------------------------------- /assets/img/1F427.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F427.png -------------------------------------------------------------------------------- /assets/img/1F42B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F42B.png -------------------------------------------------------------------------------- /assets/img/1F42C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F42C.png -------------------------------------------------------------------------------- /assets/img/1F42D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F42D.png -------------------------------------------------------------------------------- /assets/img/1F430.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F430.png -------------------------------------------------------------------------------- /assets/img/1F43B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F43B.png -------------------------------------------------------------------------------- /assets/img/1F440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F440.png -------------------------------------------------------------------------------- /assets/img/1F44A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F44A.png -------------------------------------------------------------------------------- /assets/img/1F44B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F44B.png -------------------------------------------------------------------------------- /assets/img/1F44D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F44D.png -------------------------------------------------------------------------------- /assets/img/1F44E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F44E.png -------------------------------------------------------------------------------- /assets/img/1F451.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F451.png -------------------------------------------------------------------------------- /assets/img/1F478.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F478.png -------------------------------------------------------------------------------- /assets/img/1F47B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F47B.png -------------------------------------------------------------------------------- /assets/img/1F47D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F47D.png -------------------------------------------------------------------------------- /assets/img/1F480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F480.png -------------------------------------------------------------------------------- /assets/img/1F48B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F48B.png -------------------------------------------------------------------------------- /assets/img/1F494.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F494.png -------------------------------------------------------------------------------- /assets/img/1F499.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F499.png -------------------------------------------------------------------------------- /assets/img/1F49A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F49A.png -------------------------------------------------------------------------------- /assets/img/1F49B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F49B.png -------------------------------------------------------------------------------- /assets/img/1F49C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F49C.png -------------------------------------------------------------------------------- /assets/img/1F4A3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F4A3.png -------------------------------------------------------------------------------- /assets/img/1F4A6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F4A6.png -------------------------------------------------------------------------------- /assets/img/1F4A7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F4A7.png -------------------------------------------------------------------------------- /assets/img/1F4F7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F4F7.png -------------------------------------------------------------------------------- /assets/img/1F514.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F514.png -------------------------------------------------------------------------------- /assets/img/1F525.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F525.png -------------------------------------------------------------------------------- /assets/img/1F52B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F52B.png -------------------------------------------------------------------------------- /assets/img/1F5A4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F5A4.png -------------------------------------------------------------------------------- /assets/img/1F601.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F601.png -------------------------------------------------------------------------------- /assets/img/1F609.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F609.png -------------------------------------------------------------------------------- /assets/img/1F60E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F60E.png -------------------------------------------------------------------------------- /assets/img/1F618.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F618.png -------------------------------------------------------------------------------- /assets/img/1F621.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F621.png -------------------------------------------------------------------------------- /assets/img/1F622.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F622.png -------------------------------------------------------------------------------- /assets/img/1F633.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F633.png -------------------------------------------------------------------------------- /assets/img/1F634.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F634.png -------------------------------------------------------------------------------- /assets/img/1F637.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F637.png -------------------------------------------------------------------------------- /assets/img/1F642.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F642.png -------------------------------------------------------------------------------- /assets/img/1F643.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F643.png -------------------------------------------------------------------------------- /assets/img/1F648.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F648.png -------------------------------------------------------------------------------- /assets/img/1F649.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F649.png -------------------------------------------------------------------------------- /assets/img/1F64A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F64A.png -------------------------------------------------------------------------------- /assets/img/1F680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F680.png -------------------------------------------------------------------------------- /assets/img/1F697.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F697.png -------------------------------------------------------------------------------- /assets/img/1F69C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F69C.png -------------------------------------------------------------------------------- /assets/img/1F6A2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F6A2.png -------------------------------------------------------------------------------- /assets/img/1F6A6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F6A6.png -------------------------------------------------------------------------------- /assets/img/1F6B2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F6B2.png -------------------------------------------------------------------------------- /assets/img/1F7E0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E0.png -------------------------------------------------------------------------------- /assets/img/1F7E1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E1.png -------------------------------------------------------------------------------- /assets/img/1F7E2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E2.png -------------------------------------------------------------------------------- /assets/img/1F7E3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E3.png -------------------------------------------------------------------------------- /assets/img/1F7E4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E4.png -------------------------------------------------------------------------------- /assets/img/1F7E5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E5.png -------------------------------------------------------------------------------- /assets/img/1F7E6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E6.png -------------------------------------------------------------------------------- /assets/img/1F7E7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E7.png -------------------------------------------------------------------------------- /assets/img/1F7E8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E8.png -------------------------------------------------------------------------------- /assets/img/1F7E9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7E9.png -------------------------------------------------------------------------------- /assets/img/1F7EA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7EA.png -------------------------------------------------------------------------------- /assets/img/1F7EB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F7EB.png -------------------------------------------------------------------------------- /assets/img/1F90E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F90E.png -------------------------------------------------------------------------------- /assets/img/1F914.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F914.png -------------------------------------------------------------------------------- /assets/img/1F916.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F916.png -------------------------------------------------------------------------------- /assets/img/1F91D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F91D.png -------------------------------------------------------------------------------- /assets/img/1F91E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F91E.png -------------------------------------------------------------------------------- /assets/img/1F921.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F921.png -------------------------------------------------------------------------------- /assets/img/1F922.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F922.png -------------------------------------------------------------------------------- /assets/img/1F92F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F92F.png -------------------------------------------------------------------------------- /assets/img/1F935.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F935.png -------------------------------------------------------------------------------- /assets/img/1F937.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F937.png -------------------------------------------------------------------------------- /assets/img/1F947.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F947.png -------------------------------------------------------------------------------- /assets/img/1F955.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F955.png -------------------------------------------------------------------------------- /assets/img/1F95A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F95A.png -------------------------------------------------------------------------------- /assets/img/1F971.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F971.png -------------------------------------------------------------------------------- /assets/img/1F980.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F980.png -------------------------------------------------------------------------------- /assets/img/1F984.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F984.png -------------------------------------------------------------------------------- /assets/img/1F987.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F987.png -------------------------------------------------------------------------------- /assets/img/1F98B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F98B.png -------------------------------------------------------------------------------- /assets/img/1F98C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F98C.png -------------------------------------------------------------------------------- /assets/img/1F98D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F98D.png -------------------------------------------------------------------------------- /assets/img/1F98F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F98F.png -------------------------------------------------------------------------------- /assets/img/1F992.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F992.png -------------------------------------------------------------------------------- /assets/img/1F994.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F994.png -------------------------------------------------------------------------------- /assets/img/1F998.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F998.png -------------------------------------------------------------------------------- /assets/img/1F9A2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9A2.png -------------------------------------------------------------------------------- /assets/img/1F9B4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9B4.png -------------------------------------------------------------------------------- /assets/img/1F9B7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9B7.png -------------------------------------------------------------------------------- /assets/img/1F9C0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9C0.png -------------------------------------------------------------------------------- /assets/img/1F9CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9CA.png -------------------------------------------------------------------------------- /assets/img/1F9DA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9DA.png -------------------------------------------------------------------------------- /assets/img/1F9DB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9DB.png -------------------------------------------------------------------------------- /assets/img/1F9E0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9E0.png -------------------------------------------------------------------------------- /assets/img/1F9E1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9E1.png -------------------------------------------------------------------------------- /assets/img/1F9F1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/1F9F1.png -------------------------------------------------------------------------------- /assets/img/23F0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/23F0.png -------------------------------------------------------------------------------- /assets/img/2693.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/2693.png -------------------------------------------------------------------------------- /assets/img/26F2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/26F2.png -------------------------------------------------------------------------------- /assets/img/2B1B.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/2B1B.png -------------------------------------------------------------------------------- /assets/img/2B50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/2B50.png -------------------------------------------------------------------------------- /assets/img/captcha_failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/captcha_failure.png -------------------------------------------------------------------------------- /assets/img/captcha_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/assets/img/captcha_success.png -------------------------------------------------------------------------------- /assets/mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "emoji": [ 3 | { 4 | "symbol": "😉", 5 | "title": "Подмигивание", 6 | "code": "1F609" 7 | }, 8 | { 9 | "symbol": "😁", 10 | "title": "Смех", 11 | "code": "1F601" 12 | }, 13 | { 14 | "symbol": "🙂", 15 | "title": "Улыбка", 16 | "code": "1F642" 17 | }, 18 | { 19 | "symbol": "🙃", 20 | "title": "Улыбка вверх ногами", 21 | "code": "1F643" 22 | }, 23 | { 24 | "symbol": "😘", 25 | "title": "Воздушный поцелуй", 26 | "code": "1F618" 27 | }, 28 | { 29 | "symbol": "🤔", 30 | "title": "Задумчивое лицо", 31 | "code": "1F914" 32 | }, 33 | { 34 | "symbol": "😴", 35 | "title": "Сон", 36 | "code": "1F634" 37 | }, 38 | { 39 | "symbol": "😷", 40 | "title": "В медицинской маске", 41 | "code": "1F637" 42 | }, 43 | { 44 | "symbol": "🤯", 45 | "title": "Взрывающаяся голова", 46 | "code": "1F92F" 47 | }, 48 | { 49 | "symbol": "🤢", 50 | "title": "Тошнота", 51 | "code": "1F922" 52 | }, 53 | { 54 | "symbol": "😎", 55 | "title": "В солнечных очках", 56 | "code": "1F60E" 57 | }, 58 | { 59 | "symbol": "😢", 60 | "title": "Плачущее лицо", 61 | "code": "1F622" 62 | }, 63 | { 64 | "symbol": "🥱", 65 | "title": "Зевающее лицо", 66 | "code": "1F971" 67 | }, 68 | { 69 | "symbol": "😳", 70 | "title": "Покрасневшее лицо", 71 | "code": "1F633" 72 | }, 73 | { 74 | "symbol": "😡", 75 | "title": "Злость", 76 | "code": "1F621" 77 | }, 78 | { 79 | "symbol": "💀", 80 | "title": "Череп", 81 | "code": "1F480" 82 | }, 83 | { 84 | "symbol": "🤡", 85 | "title": "Клоун", 86 | "code": "1F921" 87 | }, 88 | { 89 | "symbol": "🤖", 90 | "title": "Робот", 91 | "code": "1F916" 92 | }, 93 | { 94 | "symbol": "👽", 95 | "title": "Инопланетянин", 96 | "code": "1F47D" 97 | }, 98 | { 99 | "symbol": "👻", 100 | "title": "Призрак", 101 | "code": "1F47B" 102 | }, 103 | { 104 | "symbol": "🙉", 105 | "title": "Обезьянка закрыла уши", 106 | "code": "1F649" 107 | }, 108 | { 109 | "symbol": "🙊", 110 | "title": "Обезьянка закрыла рот", 111 | "code": "1F64A" 112 | }, 113 | { 114 | "symbol": "🙈", 115 | "title": "Обезьянка закрыла глаза", 116 | "code": "1F648" 117 | }, 118 | { 119 | "symbol": "🧡", 120 | "title": "Оранжевое сердце", 121 | "code": "1F9E1" 122 | }, 123 | { 124 | "symbol": "🤎", 125 | "title": "Коричневое сердце", 126 | "code": "1F90E" 127 | }, 128 | { 129 | "symbol": "🖤", 130 | "title": "Черное сердце", 131 | "code": "1F5A4" 132 | }, 133 | { 134 | "symbol": "💋", 135 | "title": "Следы поцелуя", 136 | "code": "1F48B" 137 | }, 138 | { 139 | "symbol": "💦", 140 | "title": "Брызги воды", 141 | "code": "1F4A6" 142 | }, 143 | { 144 | "symbol": "💔", 145 | "title": "Разбитое сердце", 146 | "code": "1F494" 147 | }, 148 | { 149 | "symbol": "💚", 150 | "title": "Зелёное сердце", 151 | "code": "1F49A" 152 | }, 153 | { 154 | "symbol": "💙", 155 | "title": "Голубое сердце", 156 | "code": "1F499" 157 | }, 158 | { 159 | "symbol": "💜", 160 | "title": "Фиолетовое сердце", 161 | "code": "1F49C" 162 | }, 163 | { 164 | "symbol": "💛", 165 | "title": "Жёлтое сердце", 166 | "code": "1F49B" 167 | }, 168 | { 169 | "symbol": "💣", 170 | "title": "Бомба", 171 | "code": "1F4A3" 172 | }, 173 | { 174 | "symbol": "👋", 175 | "title": "Ладонь", 176 | "code": "1F44B" 177 | }, 178 | { 179 | "symbol": "🤞", 180 | "title": "Перекрещенные пальцы", 181 | "code": "1F91E" 182 | }, 183 | { 184 | "symbol": "👍", 185 | "title": "Палец вверх", 186 | "code": "1F44D" 187 | }, 188 | { 189 | "symbol": "👊", 190 | "title": "Кулак", 191 | "code": "1F44A" 192 | }, 193 | { 194 | "symbol": "👎", 195 | "title": "Палец вниз", 196 | "code": "1F44E" 197 | }, 198 | { 199 | "symbol": "🤝", 200 | "title": "Рукопожатие", 201 | "code": "1F91D" 202 | }, 203 | { 204 | "symbol": "🦴", 205 | "title": "Кость", 206 | "code": "1F9B4" 207 | }, 208 | { 209 | "symbol": "👀", 210 | "title": "Глаза", 211 | "code": "1F440" 212 | }, 213 | { 214 | "symbol": "🦷", 215 | "title": "Зуб", 216 | "code": "1F9B7" 217 | }, 218 | { 219 | "symbol": "🧠", 220 | "title": "Мозг", 221 | "code": "1F9E0" 222 | }, 223 | { 224 | "symbol": "🤷", 225 | "title": "Пожимание плечами", 226 | "code": "1F937" 227 | }, 228 | { 229 | "symbol": "👸", 230 | "title": "Принцесса", 231 | "code": "1F478" 232 | }, 233 | { 234 | "symbol": "🤵", 235 | "title": "Мужчина в смокинге", 236 | "code": "1F935" 237 | }, 238 | { 239 | "symbol": "🧛", 240 | "title": "Вампир", 241 | "code": "1F9DB" 242 | }, 243 | { 244 | "symbol": "🧚", 245 | "title": "Фея", 246 | "code": "1F9DA" 247 | }, 248 | { 249 | "symbol": "🎅", 250 | "title": "Санта Клаус", 251 | "code": "1F385" 252 | }, 253 | { 254 | "symbol": "🐭", 255 | "title": "Мышь", 256 | "code": "1F42D" 257 | }, 258 | { 259 | "symbol": "🐫", 260 | "title": "Верблюд", 261 | "code": "1F42B" 262 | }, 263 | { 264 | "symbol": "🦏", 265 | "title": "Носорог", 266 | "code": "1F98F" 267 | }, 268 | { 269 | "symbol": "🦍", 270 | "title": "Горилла", 271 | "code": "1F98D" 272 | }, 273 | { 274 | "symbol": "🦌", 275 | "title": "Олень", 276 | "code": "1F98C" 277 | }, 278 | { 279 | "symbol": "🦒", 280 | "title": "Жираф", 281 | "code": "1F992" 282 | }, 283 | { 284 | "symbol": "🦘", 285 | "title": "Кенгуру", 286 | "code": "1F998" 287 | }, 288 | { 289 | "symbol": "🦇", 290 | "title": "Летучая мышь", 291 | "code": "1F987" 292 | }, 293 | { 294 | "symbol": "🐑", 295 | "title": "Овца", 296 | "code": "1F411" 297 | }, 298 | { 299 | "symbol": "🐖", 300 | "title": "Свинья", 301 | "code": "1F416" 302 | }, 303 | { 304 | "symbol": "🐰", 305 | "title": "Кролик", 306 | "code": "1F430" 307 | }, 308 | { 309 | "symbol": "🦔", 310 | "title": "Ёж", 311 | "code": "1F994" 312 | }, 313 | { 314 | "symbol": "🐄", 315 | "title": "Корова", 316 | "code": "1F404" 317 | }, 318 | { 319 | "symbol": "🦄", 320 | "title": "Единорог", 321 | "code": "1F984" 322 | }, 323 | { 324 | "symbol": "🐻", 325 | "title": "Медведь", 326 | "code": "1F43B" 327 | }, 328 | { 329 | "symbol": "🐔", 330 | "title": "Курица", 331 | "code": "1F414" 332 | }, 333 | { 334 | "symbol": "🐤", 335 | "title": "Цыплёнок", 336 | "code": "1F424" 337 | }, 338 | { 339 | "symbol": "🦢", 340 | "title": "Лебедь", 341 | "code": "1F9A2" 342 | }, 343 | { 344 | "symbol": "🐧", 345 | "title": "Пингвин", 346 | "code": "1F427" 347 | }, 348 | { 349 | "symbol": "🐍", 350 | "title": "Змея", 351 | "code": "1F40D" 352 | }, 353 | { 354 | "symbol": "🐢", 355 | "title": "Черепаха", 356 | "code": "1F422" 357 | }, 358 | { 359 | "symbol": "🐊", 360 | "title": "Крокодил", 361 | "code": "1F40A" 362 | }, 363 | { 364 | "symbol": "🐙", 365 | "title": "Осьминог", 366 | "code": "1F419" 367 | }, 368 | { 369 | "symbol": "🐬", 370 | "title": "Дельфин", 371 | "code": "1F42C" 372 | }, 373 | { 374 | "symbol": "🦋", 375 | "title": "Бабочка", 376 | "code": "1F98B" 377 | }, 378 | { 379 | "symbol": "🐝", 380 | "title": "Пчела", 381 | "code": "1F41D" 382 | }, 383 | { 384 | "symbol": "🐌", 385 | "title": "Улитка", 386 | "code": "1F40C" 387 | }, 388 | { 389 | "symbol": "🌹", 390 | "title": "Роза", 391 | "code": "1F339" 392 | }, 393 | { 394 | "symbol": "🌴", 395 | "title": "Пальма", 396 | "code": "1F334" 397 | }, 398 | { 399 | "symbol": "🌵", 400 | "title": "Кактус", 401 | "code": "1F335" 402 | }, 403 | { 404 | "symbol": "🍓", 405 | "title": "Клубника", 406 | "code": "1F353" 407 | }, 408 | { 409 | "symbol": "🍒", 410 | "title": "Вишня", 411 | "code": "1F352" 412 | }, 413 | { 414 | "symbol": "🍋", 415 | "title": "Лимон", 416 | "code": "1F34B" 417 | }, 418 | { 419 | "symbol": "🍉", 420 | "title": "Арбуз", 421 | "code": "1F349" 422 | }, 423 | { 424 | "symbol": "🍍", 425 | "title": "Ананас", 426 | "code": "1F34D" 427 | }, 428 | { 429 | "symbol": "🍎", 430 | "title": "Красное яблоко", 431 | "code": "1F34E" 432 | }, 433 | { 434 | "symbol": "🍇", 435 | "title": "Виноград", 436 | "code": "1F347" 437 | }, 438 | { 439 | "symbol": "🍅", 440 | "title": "Помидор", 441 | "code": "1F345" 442 | }, 443 | { 444 | "symbol": "🍌", 445 | "title": "Банан", 446 | "code": "1F34C" 447 | }, 448 | { 449 | "symbol": "🍏", 450 | "title": "Зеленое яблоко", 451 | "code": "1F34F" 452 | }, 453 | { 454 | "symbol": "🥕", 455 | "title": "Морковь", 456 | "code": "1F955" 457 | }, 458 | { 459 | "symbol": "🍆", 460 | "title": "Баклажан", 461 | "code": "1F346" 462 | }, 463 | { 464 | "symbol": "🧀", 465 | "title": "Сыр", 466 | "code": "1F9C0" 467 | }, 468 | { 469 | "symbol": "🍕", 470 | "title": "Кусочек пиццы", 471 | "code": "1F355" 472 | }, 473 | { 474 | "symbol": "🍔", 475 | "title": "Гамбургер", 476 | "code": "1F354" 477 | }, 478 | { 479 | "symbol": "🍿", 480 | "title": "Попкорн", 481 | "code": "1F37F" 482 | }, 483 | { 484 | "symbol": "🥚", 485 | "title": "Яйцо", 486 | "code": "1F95A" 487 | }, 488 | { 489 | "symbol": "🦀", 490 | "title": "Краб", 491 | "code": "1F980" 492 | }, 493 | { 494 | "symbol": "🍫", 495 | "title": "Шоколад", 496 | "code": "1F36B" 497 | }, 498 | { 499 | "symbol": "🍦", 500 | "title": "Мороженое", 501 | "code": "1F366" 502 | }, 503 | { 504 | "symbol": "🍩", 505 | "title": "Пончик", 506 | "code": "1F369" 507 | }, 508 | { 509 | "symbol": "🍪", 510 | "title": "Печенье", 511 | "code": "1F36A" 512 | }, 513 | { 514 | "symbol": "🍭", 515 | "title": "Леденец на палочке", 516 | "code": "1F36D" 517 | }, 518 | { 519 | "symbol": "🎂", 520 | "title": "Торт", 521 | "code": "1F382" 522 | }, 523 | { 524 | "symbol": "🍺", 525 | "title": "Пивная кружка", 526 | "code": "1F37A" 527 | }, 528 | { 529 | "symbol": "🧊", 530 | "title": "Кубик льда", 531 | "code": "1F9CA" 532 | }, 533 | { 534 | "symbol": "🏔", 535 | "title": "Гора, покрытая снегом", 536 | "code": "1F3D4" 537 | }, 538 | { 539 | "symbol": "🏠", 540 | "title": "Дом", 541 | "code": "1F3E0" 542 | }, 543 | { 544 | "symbol": "🧱", 545 | "title": "Кирпич", 546 | "code": "1F9F1" 547 | }, 548 | { 549 | "symbol": "⛲", 550 | "title": "Фонтан", 551 | "code": "26F2" 552 | }, 553 | { 554 | "symbol": "🚲", 555 | "title": "Велосипед", 556 | "code": "1F6B2" 557 | }, 558 | { 559 | "symbol": "🚜", 560 | "title": "Трактор", 561 | "code": "1F69C" 562 | }, 563 | { 564 | "symbol": "🚗", 565 | "title": "Автомобиль", 566 | "code": "1F697" 567 | }, 568 | { 569 | "symbol": "🚦", 570 | "title": "Вертикальный светофор", 571 | "code": "1F6A6" 572 | }, 573 | { 574 | "symbol": "🚢", 575 | "title": "Корабль", 576 | "code": "1F6A2" 577 | }, 578 | { 579 | "symbol": "⚓", 580 | "title": "Якорь", 581 | "code": "2693" 582 | }, 583 | { 584 | "symbol": "🚀", 585 | "title": "Ракета", 586 | "code": "1F680" 587 | }, 588 | { 589 | "symbol": "⏰", 590 | "title": "Будильник", 591 | "code": "23F0" 592 | }, 593 | { 594 | "symbol": "🔥", 595 | "title": "Огонь", 596 | "code": "1F525" 597 | }, 598 | { 599 | "symbol": "⭐", 600 | "title": "Звезда", 601 | "code": "2B50" 602 | }, 603 | { 604 | "symbol": "💧", 605 | "title": "Падающая капля", 606 | "code": "1F4A7" 607 | }, 608 | { 609 | "symbol": "🌈", 610 | "title": "Радуга", 611 | "code": "1F308" 612 | }, 613 | { 614 | "symbol": "🎈", 615 | "title": "Воздушный шарик", 616 | "code": "1F388" 617 | }, 618 | { 619 | "symbol": "🥇", 620 | "title": "Золотая медаль", 621 | "code": "1F947" 622 | }, 623 | { 624 | "symbol": "👑", 625 | "title": "Корона", 626 | "code": "1F451" 627 | }, 628 | { 629 | "symbol": "🔔", 630 | "title": "Колокольчик", 631 | "code": "1F514" 632 | }, 633 | { 634 | "symbol": "📷", 635 | "title": "Фотоаппарат", 636 | "code": "1F4F7" 637 | }, 638 | { 639 | "symbol": "🔫", 640 | "title": "Пистолет", 641 | "code": "1F52B" 642 | }, 643 | { 644 | "symbol": "🟪", 645 | "title": "Фиолетовый квадрат", 646 | "code": "1F7EA" 647 | }, 648 | { 649 | "symbol": "🟩", 650 | "title": "Зеленый квадрат", 651 | "code": "1F7E9" 652 | }, 653 | { 654 | "symbol": "🟡", 655 | "title": "Желтый круг", 656 | "code": "1F7E1" 657 | }, 658 | { 659 | "symbol": "🟠", 660 | "title": "Оранжевый круг", 661 | "code": "1F7E0" 662 | }, 663 | { 664 | "symbol": "🟨", 665 | "title": "Желтый квадрат", 666 | "code": "1F7E8" 667 | }, 668 | { 669 | "symbol": "🟤", 670 | "title": "Коричневый круг", 671 | "code": "1F7E4" 672 | }, 673 | { 674 | "symbol": "🟥", 675 | "title": "Красный квадрат", 676 | "code": "1F7E5" 677 | }, 678 | { 679 | "symbol": "🟫", 680 | "title": "Коричневый квадрат", 681 | "code": "1F7EB" 682 | }, 683 | { 684 | "symbol": "🟧", 685 | "title": "Оранжевый квадрат", 686 | "code": "1F7E7" 687 | }, 688 | { 689 | "symbol": "🟣", 690 | "title": "Фиолетовый круг", 691 | "code": "1F7E3" 692 | }, 693 | { 694 | "symbol": "🟢", 695 | "title": "Зеленый круг", 696 | "code": "1F7E2" 697 | }, 698 | { 699 | "symbol": "🟦", 700 | "title": "Синий квадрат", 701 | "code": "1F7E6" 702 | }, 703 | { 704 | "symbol": "⬛", 705 | "title": "Черный квадрат", 706 | "code": "2B1B" 707 | } 708 | ] 709 | } -------------------------------------------------------------------------------- /config_example.ini: -------------------------------------------------------------------------------- 1 | [bot] 2 | token = 123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ 3 | privacy_policy_link = https://privacy-policy.link 4 | 5 | [webhook] 6 | host = 7 | path = 8 | 9 | [webapp] 10 | host = 0.0.0.0 11 | port = 8080 12 | 13 | [redis] 14 | host = redis 15 | port = 6379 16 | db = 0 17 | password = SET THIS PASSWORD IN redis.conf TOO 18 | 19 | [captcha] 20 | duration = 120 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | bot: 5 | container_name: "simplecaptcha-bot" 6 | build: 7 | context: . 8 | working_dir: "/usr/src/simplecaptcha-bot" 9 | stop_signal: SIGINT 10 | restart: unless-stopped 11 | command: "python -m app" 12 | ports: 13 | - "8080:8080" 14 | redis: 15 | container_name: "simplecaptcha-redis" 16 | image: redis:7.0-rc-alpine 17 | hostname: redis 18 | volumes: 19 | - "./redis.conf:/usr/local/etc/redis/redis.conf" 20 | command: "redis-server /usr/local/etc/redis/redis.conf" 21 | restart: "unless-stopped" 22 | worker: 23 | container_name: "simplecaptcha-worker" 24 | build: 25 | context: . 26 | working_dir: "/usr/src/simplecaptcha-bot" 27 | volumes: 28 | - .:/usr/src/simplecaptcha-bot 29 | command: "python -m worker" 30 | restart: unless-stopped 31 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "aiofiles" 5 | version = "0.8.0" 6 | description = "File support for asyncio." 7 | optional = false 8 | python-versions = ">=3.6,<4.0" 9 | files = [ 10 | {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, 11 | {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, 12 | ] 13 | 14 | [[package]] 15 | name = "aiogram" 16 | version = "3.0.0b3" 17 | description = "Modern and fully asynchronous framework for Telegram Bot API" 18 | optional = false 19 | python-versions = ">=3.8,<4.0" 20 | files = [ 21 | {file = "aiogram-3.0.0b3-py3-none-any.whl", hash = "sha256:78c20c0dc16fc6b39f9de41df8c3bb48170b2566dd60484414d995418811be7d"}, 22 | {file = "aiogram-3.0.0b3.tar.gz", hash = "sha256:6dc1f0fc829603b6defc93b861e098a2beef1f2e8409be3fe7438c29efc78b2e"}, 23 | ] 24 | 25 | [package.dependencies] 26 | aiofiles = ">=0.8.0,<0.9.0" 27 | aiohttp = ">=3.8.1,<4.0.0" 28 | magic-filter = ">=1.0.6,<2.0.0" 29 | pydantic = ">=1.9.0,<2.0.0" 30 | Pygments = ">=2.11.2,<3.0.0" 31 | 32 | [package.extras] 33 | docs = ["Sphinx (>=4.2.0,<5.0.0)", "Sphinx-Substitution-Extensions (>=2020.9.30,<2021.0.0)", "furo (>=2022.4.7,<2023.0.0)", "markdown-include (>=0.6,<0.7)", "pygments (>=2.4,<3.0)", "pymdown-extensions (>=9.3,<10.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.5.0,<0.6.0)", "sphinx-intl (>=2.0.1,<3.0.0)", "sphinx-prompt (>=1.5.0,<2.0.0)", "towncrier (>=21.9.0,<22.0.0)"] 34 | fast = ["uvloop (>=0.16.0,<0.17.0)"] 35 | i18n = ["Babel (>=2.9.1,<3.0.0)"] 36 | proxy = ["aiohttp-socks (>=0.7.1,<0.8.0)"] 37 | redis = ["redis (>=4.2.2,<5.0.0)"] 38 | 39 | [[package]] 40 | name = "aiohttp" 41 | version = "3.9.5" 42 | description = "Async http client/server framework (asyncio)" 43 | optional = false 44 | python-versions = ">=3.8" 45 | files = [ 46 | {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, 47 | {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, 48 | {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, 49 | {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, 50 | {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, 51 | {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, 52 | {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, 53 | {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, 54 | {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, 55 | {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, 56 | {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, 57 | {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, 58 | {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, 59 | {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, 60 | {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, 61 | {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, 62 | {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, 63 | {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, 64 | {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, 65 | {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, 66 | {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, 67 | {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, 68 | {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, 69 | {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, 70 | {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, 71 | {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, 72 | {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, 73 | {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, 74 | {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, 75 | {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, 76 | {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, 77 | {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, 78 | {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, 79 | {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, 80 | {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, 81 | {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, 82 | {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, 83 | {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, 84 | {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, 85 | {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, 86 | {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, 87 | {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, 88 | {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, 89 | {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, 90 | {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, 91 | {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, 92 | {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, 93 | {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, 94 | {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, 95 | {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, 96 | {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, 97 | {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, 98 | {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, 99 | {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, 100 | {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, 101 | {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, 102 | {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, 103 | {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, 104 | {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, 105 | {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, 106 | {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, 107 | {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, 108 | {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, 109 | {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, 110 | {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, 111 | {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, 112 | {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, 113 | {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, 114 | {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, 115 | {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, 116 | {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, 117 | {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, 118 | {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, 119 | {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, 120 | {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, 121 | {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, 122 | ] 123 | 124 | [package.dependencies] 125 | aiosignal = ">=1.1.2" 126 | async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} 127 | attrs = ">=17.3.0" 128 | frozenlist = ">=1.1.1" 129 | multidict = ">=4.5,<7.0" 130 | yarl = ">=1.0,<2.0" 131 | 132 | [package.extras] 133 | speedups = ["Brotli", "aiodns", "brotlicffi"] 134 | 135 | [[package]] 136 | name = "aiosignal" 137 | version = "1.3.1" 138 | description = "aiosignal: a list of registered asynchronous callbacks" 139 | optional = false 140 | python-versions = ">=3.7" 141 | files = [ 142 | {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, 143 | {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, 144 | ] 145 | 146 | [package.dependencies] 147 | frozenlist = ">=1.1.0" 148 | 149 | [[package]] 150 | name = "arq" 151 | version = "0.26.0" 152 | description = "Job queues in python with asyncio and redis" 153 | optional = false 154 | python-versions = ">=3.8" 155 | files = [ 156 | {file = "arq-0.26.0-py3-none-any.whl", hash = "sha256:6884bc56b88565243e59a2615c04aad56bba6de23432adecfb653d79ec9d35e9"}, 157 | {file = "arq-0.26.0.tar.gz", hash = "sha256:73867aeaa366033c7c7f071821bddca734bcf5735f04ae277de477f0ed3938a0"}, 158 | ] 159 | 160 | [package.dependencies] 161 | click = ">=8.0" 162 | redis = {version = ">=4.2.0,<5", extras = ["hiredis"]} 163 | 164 | [package.extras] 165 | watch = ["watchfiles (>=0.16)"] 166 | 167 | [[package]] 168 | name = "async-timeout" 169 | version = "4.0.3" 170 | description = "Timeout context manager for asyncio programs" 171 | optional = false 172 | python-versions = ">=3.7" 173 | files = [ 174 | {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, 175 | {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, 176 | ] 177 | 178 | [[package]] 179 | name = "attrs" 180 | version = "23.2.0" 181 | description = "Classes Without Boilerplate" 182 | optional = false 183 | python-versions = ">=3.7" 184 | files = [ 185 | {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, 186 | {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, 187 | ] 188 | 189 | [package.extras] 190 | cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] 191 | dev = ["attrs[tests]", "pre-commit"] 192 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] 193 | tests = ["attrs[tests-no-zope]", "zope-interface"] 194 | tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] 195 | tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] 196 | 197 | [[package]] 198 | name = "black" 199 | version = "22.12.0" 200 | description = "The uncompromising code formatter." 201 | optional = false 202 | python-versions = ">=3.7" 203 | files = [ 204 | {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, 205 | {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, 206 | {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, 207 | {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, 208 | {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, 209 | {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, 210 | {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, 211 | {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, 212 | {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, 213 | {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, 214 | {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, 215 | {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, 216 | ] 217 | 218 | [package.dependencies] 219 | click = ">=8.0.0" 220 | mypy-extensions = ">=0.4.3" 221 | pathspec = ">=0.9.0" 222 | platformdirs = ">=2" 223 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} 224 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 225 | 226 | [package.extras] 227 | colorama = ["colorama (>=0.4.3)"] 228 | d = ["aiohttp (>=3.7.4)"] 229 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 230 | uvloop = ["uvloop (>=0.15.2)"] 231 | 232 | [[package]] 233 | name = "click" 234 | version = "8.1.7" 235 | description = "Composable command line interface toolkit" 236 | optional = false 237 | python-versions = ">=3.7" 238 | files = [ 239 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 240 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 241 | ] 242 | 243 | [package.dependencies] 244 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 245 | 246 | [[package]] 247 | name = "colorama" 248 | version = "0.4.6" 249 | description = "Cross-platform colored terminal text." 250 | optional = false 251 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 252 | files = [ 253 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 254 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 255 | ] 256 | 257 | [[package]] 258 | name = "flake8" 259 | version = "4.0.1" 260 | description = "the modular source code checker: pep8 pyflakes and co" 261 | optional = false 262 | python-versions = ">=3.6" 263 | files = [ 264 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 265 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 266 | ] 267 | 268 | [package.dependencies] 269 | mccabe = ">=0.6.0,<0.7.0" 270 | pycodestyle = ">=2.8.0,<2.9.0" 271 | pyflakes = ">=2.4.0,<2.5.0" 272 | 273 | [[package]] 274 | name = "frozenlist" 275 | version = "1.4.1" 276 | description = "A list-like structure which implements collections.abc.MutableSequence" 277 | optional = false 278 | python-versions = ">=3.8" 279 | files = [ 280 | {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, 281 | {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, 282 | {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, 283 | {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, 284 | {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, 285 | {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, 286 | {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, 287 | {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, 288 | {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, 289 | {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, 290 | {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, 291 | {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, 292 | {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, 293 | {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, 294 | {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, 295 | {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, 296 | {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, 297 | {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, 298 | {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, 299 | {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, 300 | {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, 301 | {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, 302 | {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, 303 | {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, 304 | {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, 305 | {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, 306 | {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, 307 | {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, 308 | {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, 309 | {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, 310 | {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, 311 | {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, 312 | {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, 313 | {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, 314 | {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, 315 | {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, 316 | {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, 317 | {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, 318 | {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, 319 | {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, 320 | {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, 321 | {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, 322 | {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, 323 | {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, 324 | {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, 325 | {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, 326 | {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, 327 | {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, 328 | {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, 329 | {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, 330 | {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, 331 | {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, 332 | {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, 333 | {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, 334 | {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, 335 | {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, 336 | {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, 337 | {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, 338 | {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, 339 | {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, 340 | {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, 341 | {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, 342 | {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, 343 | {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, 344 | {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, 345 | {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, 346 | {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, 347 | {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, 348 | {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, 349 | {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, 350 | {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, 351 | {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, 352 | {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, 353 | {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, 354 | {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, 355 | {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, 356 | {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, 357 | ] 358 | 359 | [[package]] 360 | name = "hiredis" 361 | version = "2.3.2" 362 | description = "Python wrapper for hiredis" 363 | optional = false 364 | python-versions = ">=3.7" 365 | files = [ 366 | {file = "hiredis-2.3.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:742093f33d374098aa21c1696ac6e4874b52658c870513a297a89265a4d08fe5"}, 367 | {file = "hiredis-2.3.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9e14fb70ca4f7efa924f508975199353bf653f452e4ef0a1e47549e208f943d7"}, 368 | {file = "hiredis-2.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d7302b4b17fcc1cc727ce84ded7f6be4655701e8d58744f73b09cb9ed2b13df"}, 369 | {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed63e8b75c193c5e5a8288d9d7b011da076cc314fafc3bfd59ec1d8a750d48c8"}, 370 | {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b4edee59dc089bc3948f4f6fba309f51aa2ccce63902364900aa0a553a85e97"}, 371 | {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6481c3b7673a86276220140456c2a6fbfe8d1fb5c613b4728293c8634134824"}, 372 | {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684840b014ce83541a087fcf2d48227196576f56ae3e944d4dfe14c0a3e0ccb7"}, 373 | {file = "hiredis-2.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c4c0bcf786f0eac9593367b6279e9b89534e008edbf116dcd0de956524702c8"}, 374 | {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66ab949424ac6504d823cba45c4c4854af5c59306a1531edb43b4dd22e17c102"}, 375 | {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:322c668ee1c12d6c5750a4b1057e6b4feee2a75b3d25d630922a463cfe5e7478"}, 376 | {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa73e3f163c6e8b2ec26f22285d717a5f77ab2120c97a2605d8f48b26950dac"}, 377 | {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7f39f28ffc65de577c3bc0c7615f149e35bc927802a0f56e612db9b530f316f9"}, 378 | {file = "hiredis-2.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:55ce31bf4711da879b96d511208efb65a6165da4ba91cb3a96d86d5a8d9d23e6"}, 379 | {file = "hiredis-2.3.2-cp310-cp310-win32.whl", hash = "sha256:3dd63d0bbbe75797b743f35d37a4cca7ca7ba35423a0de742ae2985752f20c6d"}, 380 | {file = "hiredis-2.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea002656a8d974daaf6089863ab0a306962c8b715db6b10879f98b781a2a5bf5"}, 381 | {file = "hiredis-2.3.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:adfbf2e9c38b77d0db2fb32c3bdaea638fa76b4e75847283cd707521ad2475ef"}, 382 | {file = "hiredis-2.3.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:80b02d27864ebaf9b153d4b99015342382eeaed651f5591ce6f07e840307c56d"}, 383 | {file = "hiredis-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd40d2e2f82a483de0d0a6dfd8c3895a02e55e5c9949610ecbded18188fd0a56"}, 384 | {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfa904045d7cebfb0f01dad51352551cce1d873d7c3f80c7ded7d42f8cac8f89"}, 385 | {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28bd184b33e0dd6d65816c16521a4ba1ffbe9ff07d66873c42ea4049a62fed83"}, 386 | {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f70481213373d44614148f0f2e38e7905be3f021902ae5167289413196de4ba4"}, 387 | {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8797b528c1ff81eef06713623562b36db3dafa106b59f83a6468df788ff0d1"}, 388 | {file = "hiredis-2.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02fc71c8333586871602db4774d3a3e403b4ccf6446dc4603ec12df563127cee"}, 389 | {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0da56915bda1e0a49157191b54d3e27689b70960f0685fdd5c415dacdee2fbed"}, 390 | {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e2674a5a3168349435b08fa0b82998ed2536eb9acccf7087efe26e4cd088a525"}, 391 | {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:dc1c3fd49930494a67dcec37d0558d99d84eca8eb3f03b17198424538f2608d7"}, 392 | {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:14c7b43205e515f538a9defb4e411e0f0576caaeeda76bb9993ed505486f7562"}, 393 | {file = "hiredis-2.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7bac7e02915b970c3723a7a7c5df4ba7a11a3426d2a3f181e041aa506a1ff028"}, 394 | {file = "hiredis-2.3.2-cp311-cp311-win32.whl", hash = "sha256:63a090761ddc3c1f7db5e67aa4e247b4b3bb9890080bdcdadd1b5200b8b89ac4"}, 395 | {file = "hiredis-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:70d226ab0306a5b8d408235cabe51d4bf3554c9e8a72d53ce0b3c5c84cf78881"}, 396 | {file = "hiredis-2.3.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5c614552c6bd1d0d907f448f75550f6b24fb56cbfce80c094908b7990cad9702"}, 397 | {file = "hiredis-2.3.2-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:9c431431abf55b64347ddc8df68b3ef840269cb0aa5bc2d26ad9506eb4b1b866"}, 398 | {file = "hiredis-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a45857e87e9d2b005e81ddac9d815a33efd26ec67032c366629f023fe64fb415"}, 399 | {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e138d141ec5a6ec800b6d01ddc3e5561ce1c940215e0eb9960876bfde7186aae"}, 400 | {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:387f655444d912a963ab68abf64bf6e178a13c8e4aa945cb27388fd01a02e6f1"}, 401 | {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4852f4bf88f0e2d9bdf91279892f5740ed22ae368335a37a52b92a5c88691140"}, 402 | {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d711c107e83117129b7f8bd08e9820c43ceec6204fff072a001fd82f6d13db9f"}, 403 | {file = "hiredis-2.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92830c16885f29163e1c2da1f3c1edb226df1210ec7e8711aaabba3dd0d5470a"}, 404 | {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:16b01d9ceae265d4ab9547be0cd628ecaff14b3360357a9d30c029e5ae8b7e7f"}, 405 | {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5986fb5f380169270a0293bebebd95466a1c85010b4f1afc2727e4d17c452512"}, 406 | {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:49532d7939cc51f8e99efc326090c54acf5437ed88b9c904cc8015b3c4eda9c9"}, 407 | {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8f34801b251ca43ad70691fb08b606a2e55f06b9c9fb1fc18fd9402b19d70f7b"}, 408 | {file = "hiredis-2.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7298562a49d95570ab1c7fc4051e72824c6a80e907993a21a41ba204223e7334"}, 409 | {file = "hiredis-2.3.2-cp312-cp312-win32.whl", hash = "sha256:e1d86b75de787481b04d112067a4033e1ecfda2a060e50318a74e4e1c9b2948c"}, 410 | {file = "hiredis-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:6dbfe1887ffa5cf3030451a56a8f965a9da2fa82b7149357752b67a335a05fc6"}, 411 | {file = "hiredis-2.3.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:4fc242e9da4af48714199216eb535b61e8f8d66552c8819e33fc7806bd465a09"}, 412 | {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e81aa4e9a1fcf604c8c4b51aa5d258e195a6ba81efe1da82dea3204443eba01c"}, 413 | {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419780f8583ddb544ffa86f9d44a7fcc183cd826101af4e5ffe535b6765f5f6b"}, 414 | {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6871306d8b98a15e53a5f289ec1106a3a1d43e7ab6f4d785f95fcef9a7bd9504"}, 415 | {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb0b35b63717ef1e41d62f4f8717166f7c6245064957907cfe177cc144357c"}, 416 | {file = "hiredis-2.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c490191fa1218851f8a80c5a21a05a6f680ac5aebc2e688b71cbfe592f8fec6"}, 417 | {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4baf4b579b108062e91bd2a991dc98b9dc3dc06e6288db2d98895eea8acbac22"}, 418 | {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e627d8ef5e100556e09fb44c9571a432b10e11596d3c4043500080ca9944a91a"}, 419 | {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:ba3dc0af0def8c21ce7d903c59ea1e8ec4cb073f25ece9edaec7f92a286cd219"}, 420 | {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:56e9b7d6051688ca94e68c0c8a54a243f8db841911b683cedf89a29d4de91509"}, 421 | {file = "hiredis-2.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:380e029bb4b1d34cf560fcc8950bf6b57c2ef0c9c8b7c7ac20b7c524a730fadd"}, 422 | {file = "hiredis-2.3.2-cp37-cp37m-win32.whl", hash = "sha256:948d9f2ca7841794dd9b204644963a4bcd69ced4e959b0d4ecf1b8ce994a6daa"}, 423 | {file = "hiredis-2.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cfa67afe2269b2d203cd1389c00c5bc35a287cd57860441fb0e53b371ea6a029"}, 424 | {file = "hiredis-2.3.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bcbe47da0aebc00a7cfe3ebdcff0373b86ce2b1856251c003e3d69c9db44b5a7"}, 425 | {file = "hiredis-2.3.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f2c9c0d910dd3f7df92f0638e7f65d8edd7f442203caf89c62fc79f11b0b73f8"}, 426 | {file = "hiredis-2.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:01b6c24c0840ac7afafbc4db236fd55f56a9a0919a215c25a238f051781f4772"}, 427 | {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1f567489f422d40c21e53212a73bef4638d9f21043848150f8544ef1f3a6ad1"}, 428 | {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28adecb308293e705e44087a1c2d557a816f032430d8a2a9bb7873902a1c6d48"}, 429 | {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27e9619847e9dc70b14b1ad2d0fb4889e7ca18996585c3463cff6c951fd6b10b"}, 430 | {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a0026cfbf29f07649b0e34509091a2a6016ff8844b127de150efce1c3aff60b"}, 431 | {file = "hiredis-2.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9de7586522e5da6bee83c9cf0dcccac0857a43249cb4d721a2e312d98a684d1"}, 432 | {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e58494f282215fc461b06709e9a195a24c12ba09570f25bdf9efb036acc05101"}, 433 | {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3a32b4b76d46f1eb42b24a918d51d8ca52411a381748196241d59a895f7c5c"}, 434 | {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1979334ccab21a49c544cd1b8d784ffb2747f99a51cb0bd0976eebb517628382"}, 435 | {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0c0773266e1c38a06e7593bd08870ac1503f5f0ce0f5c63f2b4134b090b5d6a4"}, 436 | {file = "hiredis-2.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bd1cee053416183adcc8e6134704c46c60c3f66b8faaf9e65bf76191ca59a2f7"}, 437 | {file = "hiredis-2.3.2-cp38-cp38-win32.whl", hash = "sha256:5341ce3d01ef3c7418a72e370bf028c7aeb16895e79e115fe4c954fff990489e"}, 438 | {file = "hiredis-2.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8fc7197ff33047ce43a67851ccf190acb5b05c52fd4a001bb55766358f04da68"}, 439 | {file = "hiredis-2.3.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:f47775e27388b58ce52f4f972f80e45b13c65113e9e6b6bf60148f893871dc9b"}, 440 | {file = "hiredis-2.3.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:9412a06b8a8e09abd6313d96864b6d7713c6003a365995a5c70cfb9209df1570"}, 441 | {file = "hiredis-2.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3020b60e3fc96d08c2a9b011f1c2e2a6bdcc09cb55df93c509b88be5cb791df"}, 442 | {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53d0f2c59bce399b8010a21bc779b4f8c32d0f582b2284ac8c98dc7578b27bc4"}, 443 | {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57c0d0c7e308ed5280a4900d4468bbfec51f0e1b4cde1deae7d4e639bc6b7766"}, 444 | {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d63318ca189fddc7e75f6a4af8eae9c0545863619fb38cfba5f43e81280b286"}, 445 | {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e741ffe4e2db78a1b9dd6e5d29678ce37fbaaf65dfe132e5b82a794413302ef1"}, 446 | {file = "hiredis-2.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb98038ccd368e0d88bd92ee575c58cfaf33e77f788c36b2a89a84ee1936dc6b"}, 447 | {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:eae62ed60d53b3561148bcd8c2383e430af38c0deab9f2dd15f8874888ffd26f"}, 448 | {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca33c175c1cf60222d9c6d01c38fc17ec3a484f32294af781de30226b003e00f"}, 449 | {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c5f6972d2bdee3cd301d5c5438e31195cf1cabf6fd9274491674d4ceb46914d"}, 450 | {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a6b54dabfaa5dbaa92f796f0c32819b4636e66aa8e9106c3d421624bd2a2d676"}, 451 | {file = "hiredis-2.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e96cd35df012a17c87ae276196ea8f215e77d6eeca90709eb03999e2d5e3fd8a"}, 452 | {file = "hiredis-2.3.2-cp39-cp39-win32.whl", hash = "sha256:63b99b5ea9fe4f21469fb06a16ca5244307678636f11917359e3223aaeca0b67"}, 453 | {file = "hiredis-2.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a50c8af811b35b8a43b1590cf890b61ff2233225257a3cad32f43b3ec7ff1b9f"}, 454 | {file = "hiredis-2.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e8bf4444b09419b77ce671088db9f875b26720b5872d97778e2545cd87dba4a"}, 455 | {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd42d0d45ea47a2f96babd82a659fbc60612ab9423a68e4a8191e538b85542a"}, 456 | {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80441b55edbef868e2563842f5030982b04349408396e5ac2b32025fb06b5212"}, 457 | {file = "hiredis-2.3.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec444ab8f27562a363672d6a7372bc0700a1bdc9764563c57c5f9efa0e592b5f"}, 458 | {file = "hiredis-2.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f9f606e810858207d4b4287b4ef0dc622c2aa469548bf02b59dcc616f134f811"}, 459 | {file = "hiredis-2.3.2-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c3dde4ca00fe9eee3b76209711f1941bb86db42b8a75d7f2249ff9dfc026ab0e"}, 460 | {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4dd676107a1d3c724a56a9d9db38166ad4cf44f924ee701414751bd18a784a0"}, 461 | {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce42649e2676ad783186264d5ffc788a7612ecd7f9effb62d51c30d413a3eefe"}, 462 | {file = "hiredis-2.3.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e3f8b1733078ac663dad57e20060e16389a60ab542f18a97931f3a2a2dd64a4"}, 463 | {file = "hiredis-2.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:532a84a82156a82529ec401d1c25d677c6543c791e54a263aa139541c363995f"}, 464 | {file = "hiredis-2.3.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4d59f88c4daa36b8c38e59ac7bffed6f5d7f68eaccad471484bf587b28ccc478"}, 465 | {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91a14dd95e24dc078204b18b0199226ee44644974c645dc54ee7b00c3157330"}, 466 | {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb777a38797c8c7df0444533119570be18d1a4ce5478dffc00c875684df7bfcb"}, 467 | {file = "hiredis-2.3.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d47c915897a99d0d34a39fad4be97b4b709ab3d0d3b779ebccf2b6024a8c681e"}, 468 | {file = "hiredis-2.3.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:333b5e04866758b11bda5f5315b4e671d15755fc6ed3b7969721bc6311d0ee36"}, 469 | {file = "hiredis-2.3.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c8937f1100435698c18e4da086968c4b5d70e86ea718376f833475ab3277c9aa"}, 470 | {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa45f7d771094b8145af10db74704ab0f698adb682fbf3721d8090f90e42cc49"}, 471 | {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33d5ebc93c39aed4b5bc769f8ce0819bc50e74bb95d57a35f838f1c4378978e0"}, 472 | {file = "hiredis-2.3.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a797d8c7df9944314d309b0d9e1b354e2fa4430a05bb7604da13b6ad291bf959"}, 473 | {file = "hiredis-2.3.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e15a408f71a6c8c87b364f1f15a6cd9c1baca12bbc47a326ac8ab99ec7ad3c64"}, 474 | {file = "hiredis-2.3.2.tar.gz", hash = "sha256:733e2456b68f3f126ddaf2cd500a33b25146c3676b97ea843665717bda0c5d43"}, 475 | ] 476 | 477 | [[package]] 478 | name = "idna" 479 | version = "3.7" 480 | description = "Internationalized Domain Names in Applications (IDNA)" 481 | optional = false 482 | python-versions = ">=3.5" 483 | files = [ 484 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, 485 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, 486 | ] 487 | 488 | [[package]] 489 | name = "isort" 490 | version = "5.13.2" 491 | description = "A Python utility / library to sort Python imports." 492 | optional = false 493 | python-versions = ">=3.8.0" 494 | files = [ 495 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 496 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 497 | ] 498 | 499 | [package.extras] 500 | colors = ["colorama (>=0.4.6)"] 501 | 502 | [[package]] 503 | name = "magic-filter" 504 | version = "1.0.12" 505 | description = "" 506 | optional = false 507 | python-versions = ">=3.7" 508 | files = [ 509 | {file = "magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6"}, 510 | {file = "magic_filter-1.0.12.tar.gz", hash = "sha256:4751d0b579a5045d1dc250625c4c508c18c3def5ea6afaf3957cb4530d03f7f9"}, 511 | ] 512 | 513 | [package.extras] 514 | dev = ["black (>=22.8.0,<22.9.0)", "flake8 (>=5.0.4,<5.1.0)", "isort (>=5.11.5,<5.12.0)", "mypy (>=1.4.1,<1.5.0)", "pre-commit (>=2.20.0,<2.21.0)", "pytest (>=7.1.3,<7.2.0)", "pytest-cov (>=3.0.0,<3.1.0)", "pytest-html (>=3.1.1,<3.2.0)", "types-setuptools (>=65.3.0,<65.4.0)"] 515 | 516 | [[package]] 517 | name = "mccabe" 518 | version = "0.6.1" 519 | description = "McCabe checker, plugin for flake8" 520 | optional = false 521 | python-versions = "*" 522 | files = [ 523 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 524 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 525 | ] 526 | 527 | [[package]] 528 | name = "multidict" 529 | version = "6.0.5" 530 | description = "multidict implementation" 531 | optional = false 532 | python-versions = ">=3.7" 533 | files = [ 534 | {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, 535 | {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, 536 | {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, 537 | {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, 538 | {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, 539 | {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, 540 | {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, 541 | {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, 542 | {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, 543 | {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, 544 | {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, 545 | {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, 546 | {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, 547 | {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, 548 | {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, 549 | {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, 550 | {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, 551 | {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, 552 | {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, 553 | {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, 554 | {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, 555 | {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, 556 | {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, 557 | {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, 558 | {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, 559 | {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, 560 | {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, 561 | {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, 562 | {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, 563 | {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, 564 | {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, 565 | {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, 566 | {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, 567 | {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, 568 | {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, 569 | {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, 570 | {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, 571 | {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, 572 | {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, 573 | {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, 574 | {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, 575 | {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, 576 | {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, 577 | {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, 578 | {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, 579 | {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, 580 | {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, 581 | {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, 582 | {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, 583 | {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, 584 | {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, 585 | {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, 586 | {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, 587 | {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, 588 | {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, 589 | {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, 590 | {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, 591 | {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, 592 | {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, 593 | {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, 594 | {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, 595 | {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, 596 | {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, 597 | {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, 598 | {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, 599 | {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, 600 | {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, 601 | {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, 602 | {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, 603 | {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, 604 | {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, 605 | {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, 606 | {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, 607 | {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, 608 | {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, 609 | {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, 610 | {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, 611 | {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, 612 | {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, 613 | {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, 614 | {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, 615 | {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, 616 | {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, 617 | {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, 618 | {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, 619 | {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, 620 | {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, 621 | {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, 622 | {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, 623 | {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, 624 | ] 625 | 626 | [[package]] 627 | name = "mypy-extensions" 628 | version = "1.0.0" 629 | description = "Type system extensions for programs checked with the mypy type checker." 630 | optional = false 631 | python-versions = ">=3.5" 632 | files = [ 633 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 634 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 635 | ] 636 | 637 | [[package]] 638 | name = "pathspec" 639 | version = "0.12.1" 640 | description = "Utility library for gitignore style pattern matching of file paths." 641 | optional = false 642 | python-versions = ">=3.8" 643 | files = [ 644 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 645 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 646 | ] 647 | 648 | [[package]] 649 | name = "platformdirs" 650 | version = "4.2.1" 651 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 652 | optional = false 653 | python-versions = ">=3.8" 654 | files = [ 655 | {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, 656 | {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, 657 | ] 658 | 659 | [package.extras] 660 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 661 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] 662 | type = ["mypy (>=1.8)"] 663 | 664 | [[package]] 665 | name = "pycodestyle" 666 | version = "2.8.0" 667 | description = "Python style guide checker" 668 | optional = false 669 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 670 | files = [ 671 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 672 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 673 | ] 674 | 675 | [[package]] 676 | name = "pydantic" 677 | version = "1.10.15" 678 | description = "Data validation and settings management using python type hints" 679 | optional = false 680 | python-versions = ">=3.7" 681 | files = [ 682 | {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, 683 | {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, 684 | {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, 685 | {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, 686 | {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, 687 | {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, 688 | {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, 689 | {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, 690 | {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, 691 | {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, 692 | {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, 693 | {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, 694 | {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, 695 | {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, 696 | {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, 697 | {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, 698 | {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, 699 | {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, 700 | {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, 701 | {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, 702 | {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, 703 | {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, 704 | {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, 705 | {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, 706 | {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, 707 | {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, 708 | {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, 709 | {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, 710 | {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, 711 | {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, 712 | {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, 713 | {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, 714 | {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, 715 | {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, 716 | {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, 717 | {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, 718 | ] 719 | 720 | [package.dependencies] 721 | typing-extensions = ">=4.2.0" 722 | 723 | [package.extras] 724 | dotenv = ["python-dotenv (>=0.10.4)"] 725 | email = ["email-validator (>=1.0.3)"] 726 | 727 | [[package]] 728 | name = "pyflakes" 729 | version = "2.4.0" 730 | description = "passive checker of Python programs" 731 | optional = false 732 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 733 | files = [ 734 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 735 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 736 | ] 737 | 738 | [[package]] 739 | name = "pygments" 740 | version = "2.17.2" 741 | description = "Pygments is a syntax highlighting package written in Python." 742 | optional = false 743 | python-versions = ">=3.7" 744 | files = [ 745 | {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, 746 | {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, 747 | ] 748 | 749 | [package.extras] 750 | plugins = ["importlib-metadata"] 751 | windows-terminal = ["colorama (>=0.4.6)"] 752 | 753 | [[package]] 754 | name = "redis" 755 | version = "4.6.0" 756 | description = "Python client for Redis database and key-value store" 757 | optional = false 758 | python-versions = ">=3.7" 759 | files = [ 760 | {file = "redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, 761 | {file = "redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, 762 | ] 763 | 764 | [package.dependencies] 765 | async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} 766 | hiredis = {version = ">=1.0.0", optional = true, markers = "extra == \"hiredis\""} 767 | 768 | [package.extras] 769 | hiredis = ["hiredis (>=1.0.0)"] 770 | ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] 771 | 772 | [[package]] 773 | name = "tomli" 774 | version = "2.0.1" 775 | description = "A lil' TOML parser" 776 | optional = false 777 | python-versions = ">=3.7" 778 | files = [ 779 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 780 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 781 | ] 782 | 783 | [[package]] 784 | name = "typing-extensions" 785 | version = "4.11.0" 786 | description = "Backported and Experimental Type Hints for Python 3.8+" 787 | optional = false 788 | python-versions = ">=3.8" 789 | files = [ 790 | {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, 791 | {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, 792 | ] 793 | 794 | [[package]] 795 | name = "yarl" 796 | version = "1.9.4" 797 | description = "Yet another URL library" 798 | optional = false 799 | python-versions = ">=3.7" 800 | files = [ 801 | {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, 802 | {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, 803 | {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, 804 | {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, 805 | {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, 806 | {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, 807 | {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, 808 | {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, 809 | {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, 810 | {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, 811 | {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, 812 | {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, 813 | {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, 814 | {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, 815 | {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, 816 | {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, 817 | {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, 818 | {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, 819 | {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, 820 | {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, 821 | {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, 822 | {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, 823 | {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, 824 | {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, 825 | {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, 826 | {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, 827 | {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, 828 | {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, 829 | {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, 830 | {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, 831 | {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, 832 | {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, 833 | {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, 834 | {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, 835 | {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, 836 | {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, 837 | {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, 838 | {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, 839 | {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, 840 | {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, 841 | {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, 842 | {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, 843 | {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, 844 | {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, 845 | {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, 846 | {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, 847 | {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, 848 | {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, 849 | {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, 850 | {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, 851 | {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, 852 | {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, 853 | {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, 854 | {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, 855 | {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, 856 | {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, 857 | {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, 858 | {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, 859 | {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, 860 | {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, 861 | {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, 862 | {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, 863 | {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, 864 | {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, 865 | {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, 866 | {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, 867 | {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, 868 | {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, 869 | {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, 870 | {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, 871 | {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, 872 | {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, 873 | {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, 874 | {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, 875 | {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, 876 | {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, 877 | {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, 878 | {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, 879 | {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, 880 | {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, 881 | {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, 882 | {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, 883 | {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, 884 | {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, 885 | {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, 886 | {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, 887 | {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, 888 | {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, 889 | {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, 890 | {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, 891 | ] 892 | 893 | [package.dependencies] 894 | idna = ">=2.0" 895 | multidict = ">=4.0" 896 | 897 | [metadata] 898 | lock-version = "2.0" 899 | python-versions = "^3.9" 900 | content-hash = "ebf5e032ae7bb3f0e5cfaae12b826c3cff8f6abf930a1d3dae2556c43463bcb7" 901 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "captchabot" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Mikhail Smolnikov "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" 9 | aiogram = "3.0.0b3" 10 | arq = "^0.26.0" 11 | 12 | [tool.poetry.dev-dependencies] 13 | black = "^22.6.0" 14 | isort = "^5.10.1" 15 | flake8 = "^4.0.1" 16 | 17 | [build-system] 18 | requires = ["poetry-core>=1.0.0"] 19 | build-backend = "poetry.core.masonry.api" 20 | -------------------------------------------------------------------------------- /redis.conf: -------------------------------------------------------------------------------- 1 | port 6379 2 | save 600 1 3 | dbfilename redis_dump.rdb 4 | requirepass SET THE PASSWORD FROM config.ini -------------------------------------------------------------------------------- /screenshot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/screenshot.jpeg -------------------------------------------------------------------------------- /worker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/worker/__init__.py -------------------------------------------------------------------------------- /worker/__main__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Dict, cast 3 | 4 | from aiogram import Bot 5 | from arq import run_worker 6 | from arq.connections import RedisSettings 7 | from arq.typing import WorkerSettingsType 8 | 9 | from app.misc.settings_reader import Settings 10 | from app.services.lock_user import LockUserService 11 | from worker.tasks.join_expired import join_expired_task 12 | 13 | settings = Settings() 14 | 15 | 16 | def configure_logging() -> None: 17 | logging.basicConfig( 18 | level=logging.INFO, 19 | format="%(filename)s:%(lineno)d #%(levelname)-8s [%(asctime)s] - %(name)s - %(message)s", 20 | ) 21 | 22 | 23 | async def startup(ctx: Dict[str, Any]): 24 | ctx["bot"] = Bot(token=settings.bot.token, parse_mode="html") 25 | ctx["lock_user_service"] = LockUserService( 26 | connection_uri=settings.redis.connection_uri, 27 | ) 28 | 29 | 30 | async def shutdown(ctx: Dict[str, Any]): 31 | bot: Bot = ctx.pop("bot") 32 | await bot.session.close() 33 | 34 | 35 | class WorkerSettings: 36 | on_startup = startup 37 | on_shutdown = shutdown 38 | functions = [join_expired_task] 39 | allow_abort_jobs = True 40 | 41 | 42 | if __name__ == "__main__": 43 | configure_logging() 44 | redis_settings = RedisSettings.from_dsn(settings.redis.connection_uri) 45 | settings_cls = cast(WorkerSettingsType, WorkerSettings) 46 | run_worker(settings_cls, redis_settings=redis_settings) 47 | -------------------------------------------------------------------------------- /worker/tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prostmich/simplecaptcha-bot/d046f05ab353173c54349a0073da6265192918f7/worker/tasks/__init__.py -------------------------------------------------------------------------------- /worker/tasks/join_expired.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from aiogram import Bot 4 | from aiogram.exceptions import TelegramAPIError 5 | 6 | from app.misc.loggers import arq_logger as logger 7 | from app.services.lock_user import LockUserService 8 | 9 | 10 | async def join_expired_task( 11 | ctx: Dict[str, Any], chat_id: int, user_id: int, salt: str 12 | ) -> None: 13 | bot: Bot = ctx["bot"] 14 | lock_user: LockUserService = ctx["lock_user_service"] 15 | logger.info( 16 | "Checking if the user {user} in chat {chat} passed captcha".format( 17 | user=user_id, chat=chat_id 18 | ) 19 | ) 20 | is_captcha_passed = not await lock_user.is_captcha_target(chat_id, user_id, salt) 21 | if is_captcha_passed: 22 | logger.info( 23 | "The user user {user} in chat {chat} already pass captcha".format( 24 | user=user_id, chat=chat_id 25 | ) 26 | ) 27 | return 28 | try: 29 | await bot.decline_chat_join_request(chat_id, user_id) 30 | await lock_user.delete_correct_answer(chat_id, user_id, salt) 31 | except TelegramAPIError as e: 32 | logger.error( 33 | "Error while declining chat join request " 34 | "for user {user} in chat {chat}: {error}".format( 35 | user=user_id, chat=chat_id, error=e 36 | ) 37 | ) 38 | return 39 | logger.info( 40 | "The user ({user}) join request to chat ({chat}) " 41 | "was declined because of a captcha timeout".format(user=user_id, chat=chat_id) 42 | ) 43 | --------------------------------------------------------------------------------