From ff61069049225f0133622e078e4e6f98a5106e29 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 1 Jun 2025 16:11:39 +0800 Subject: [PATCH 001/112] =?UTF-8?q?=E6=94=B9=E6=88=90=E7=A1=AC=E7=BC=96?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 +++++- src/recv_handler.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 33af0c5..83ba67c 100644 --- a/main.py +++ b/main.py @@ -14,7 +14,11 @@ async def message_recv(server_connection: Server.ServerConnection): recv_handler.server_connection = server_connection send_handler.server_connection = server_connection async for raw_message in server_connection: - logger.debug(f"{raw_message[:100]}..." if len(raw_message) > 100 else raw_message) + logger.debug( + f"{raw_message[:100]}..." + if (len(raw_message) > 100 and global_config.debug_level != "DEBUG") + else raw_message + ) decoded_raw_message: dict = json.loads(raw_message) post_type = decoded_raw_message.get("post_type") if post_type in ["meta_event", "message", "notice"]: diff --git a/src/recv_handler.py b/src/recv_handler.py index 4d5c397..5d3cf39 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -335,7 +335,7 @@ async def handle_text_message(self, raw_message: dict) -> Seg: """ message_data: dict = raw_message.get("data") plain_text: str = message_data.get("text") - return Seg(type=RealMessageType.text, data=plain_text) + return Seg(type="text", data=plain_text) async def handle_face_message(self, raw_message: dict) -> Seg | None: """ From 87649614184908d135c7eb7ae18d66bfba02d018 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 1 Jun 2025 16:31:38 +0800 Subject: [PATCH 002/112] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler.py b/src/recv_handler.py index 5d3cf39..5dcc074 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -441,7 +441,7 @@ async def get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: return None return response_data.get("messages") - async def handle_reply_message(self, raw_message: dict) -> Seg | None: + async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: # sourcery skip: move-assign-in-block, use-named-expression """ 处理回复消息 From b7d3c68b92810c97ac74ae87462d890bf85a39f1 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 3 Jun 2025 09:34:07 +0800 Subject: [PATCH 003/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dnotice=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=84=8F=E5=A4=96=E7=9A=84=E7=BE=A4notice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler.py | 5 +++++ src/send_handler.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/recv_handler.py b/src/recv_handler.py index 5dcc074..2c02c54 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -480,6 +480,11 @@ async def handle_notice(self, raw_message: dict) -> None: group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") + + if not self.check_allow_to_chat(user_id, group_id): + logger.warning("notice消息被丢弃") + return None + handled_message: Seg = None match notice_type: diff --git a/src/send_handler.py b/src/send_handler.py index 88b33cf..87ca64d 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -269,7 +269,7 @@ def handle_whole_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) "enable": enable, }, ) - + def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: """处理群成员踢出命令 @@ -294,7 +294,7 @@ def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu "reject_add_request": False, # 不拒绝加群请求 }, ) - + async def send_message_to_napcat(self, action: str, params: dict) -> dict: request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) From d69bdfabbc487151628f872aa96b5806c63b7b34 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 3 Jun 2025 09:35:13 +0800 Subject: [PATCH 004/112] =?UTF-8?q?=E5=B0=8F=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3cf0553..0fedfb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.2.5" +version = "0.2.6" description = "A MaiBot adapter for Napcat" [tool.ruff] From c365f2277f0e1e3f6d7ea6742578cdcd7522eaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=A5=E6=B2=B3=E6=99=B4?= Date: Wed, 4 Jun 2025 14:40:25 +0900 Subject: [PATCH 005/112] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=B0=86message?= =?UTF-8?q?=5Fqueue=E6=9B=BF=E6=8D=A2=E4=B8=BAresponse=5Fpool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- main.py | 4 +++- src/recv_handler.py | 2 +- src/{message_queue.py => response_pool.py} | 2 -- src/send_handler.py | 2 +- src/utils.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename src/{message_queue.py => response_pool.py} (97%) diff --git a/main.py b/main.py index 83ba67c..50cf968 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,9 @@ from src.send_handler import send_handler from src.config import global_config from src.mmc_com_layer import mmc_start_com, mmc_stop_com, router -from src.message_queue import message_queue, put_response, check_timeout_response +from src.response_pool import put_response, check_timeout_response + +message_queue = asyncio.Queue() async def message_recv(server_connection: Server.ServerConnection): diff --git a/src/recv_handler.py b/src/recv_handler.py index 2c02c54..7e031ec 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -28,7 +28,7 @@ get_stranger_info, get_message_detail, ) -from .message_queue import get_response +from .response_pool import get_response class RecvHandler: diff --git a/src/message_queue.py b/src/response_pool.py similarity index 97% rename from src/message_queue.py rename to src/response_pool.py index 3720590..1fa592c 100644 --- a/src/message_queue.py +++ b/src/response_pool.py @@ -6,8 +6,6 @@ response_dict: Dict = {} response_time_dict: Dict = {} -message_queue = asyncio.Queue() - async def get_response(request_id: str) -> dict: retry_count = 0 diff --git a/src/send_handler.py b/src/send_handler.py index 87ca64d..baf6fe7 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -12,7 +12,7 @@ from . import CommandType from .config import global_config -from .message_queue import get_response +from .response_pool import get_response from .logger import logger from .utils import get_image_format, convert_image_to_gif diff --git a/src/utils.py b/src/utils.py index f85ad9a..fb0b5b4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,7 +3,7 @@ import base64 import uuid from .logger import logger -from .message_queue import get_response +from .response_pool import get_response import urllib3 import ssl From 5974b51754fecacdbbbe58da383439787a793179 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 4 Jun 2025 13:45:30 +0800 Subject: [PATCH 006/112] ruff --- src/response_pool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_pool.py b/src/response_pool.py index 1fa592c..66ded1d 100644 --- a/src/response_pool.py +++ b/src/response_pool.py @@ -7,6 +7,7 @@ response_dict: Dict = {} response_time_dict: Dict = {} + async def get_response(request_id: str) -> dict: retry_count = 0 max_retries = 50 # 10秒超时 From 36305f226c2f8610b1bce3738864c1e0f2923055 Mon Sep 17 00:00:00 2001 From: Oct-autumn Date: Wed, 4 Jun 2025 21:16:06 +0800 Subject: [PATCH 007/112] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84config?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.py | 93 ---------------------- src/config/__init__.py | 5 ++ src/config/config.py | 140 +++++++++++++++++++++++++++++++++ src/config/config_base.py | 128 ++++++++++++++++++++++++++++++ src/config/official_configs.py | 77 ++++++++++++++++++ src/logger.py | 2 +- src/mmc_com_layer.py | 4 +- src/recv_handler.py | 34 ++++---- src/response_pool.py | 4 +- src/send_handler.py | 2 +- template/template_config.toml | 35 +++++---- 11 files changed, 392 insertions(+), 132 deletions(-) delete mode 100644 src/config.py create mode 100644 src/config/__init__.py create mode 100644 src/config/config.py create mode 100644 src/config/config_base.py create mode 100644 src/config/official_configs.py diff --git a/src/config.py b/src/config.py deleted file mode 100644 index ee13c98..0000000 --- a/src/config.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -import sys -import tomli -import shutil -from .logger import logger -from typing import Optional - - -class Config: - platform: str = "qq" - nickname: Optional[str] = None - server_host: str = "localhost" - server_port: int = 8095 - napcat_heartbeat_interval: int = 30 - - def __init__(self): - self._get_config_path() - - def _get_config_path(self): - current_file_path = os.path.abspath(__file__) - src_path = os.path.dirname(current_file_path) - self.root_path = os.path.join(src_path, "..") - self.config_path = os.path.join(self.root_path, "config.toml") - - def load_config(self): # sourcery skip: extract-method, move-assign - include_configs = ["Napcat_Server", "MaiBot_Server", "Chat", "Voice", "Debug"] - if not os.path.exists(self.config_path): - logger.error("配置文件不存在!") - logger.info("正在创建配置文件...") - shutil.copy( - os.path.join(self.root_path, "template", "template_config.toml"), - os.path.join(self.root_path, "config.toml"), - ) - logger.info("配置文件创建成功,请修改配置文件后重启程序。") - sys.exit(1) - with open(self.config_path, "rb") as f: - try: - raw_config = tomli.load(f) - except tomli.TOMLDecodeError as e: - logger.critical(f"配置文件bot_config.toml填写有误,请检查第{e.lineno}行第{e.colno}处:{e.msg}") - sys.exit(1) - for key in include_configs: - if key not in raw_config: - logger.error(f"配置文件中缺少必需的字段: '{key}'") - logger.error("你的配置文件可能过时,请尝试手动更新配置文件。") - sys.exit(1) - - self.server_host = raw_config["Napcat_Server"].get("host", "localhost") - self.server_port = raw_config["Napcat_Server"].get("port", 8095) - self.napcat_heartbeat_interval = raw_config["Napcat_Server"].get("heartbeat", 30) - - self.mai_host = raw_config["MaiBot_Server"].get("host", "localhost") - self.mai_port = raw_config["MaiBot_Server"].get("port", 8000) - self.platform = raw_config["MaiBot_Server"].get("platform_name") - if not self.platform: - logger.critical("请在配置文件中指定平台") - sys.exit(1) - - self.group_list_type: str = raw_config["Chat"].get("group_list_type") - self.group_list: list = raw_config["Chat"].get("group_list", []) - self.private_list_type: str = raw_config["Chat"].get("private_list_type") - self.private_list: list = raw_config["Chat"].get("private_list", []) - self.ban_user_id: list = raw_config["Chat"].get("ban_user_id", []) - self.enable_poke: bool = raw_config["Chat"].get("enable_poke", True) - if self.group_list_type not in ["whitelist", "blacklist"]: - logger.critical("请在配置文件中指定group_list_type或group_list_type填写错误") - sys.exit(1) - if self.private_list_type not in ["whitelist", "blacklist"]: - logger.critical("请在配置文件中指定private_list_type或private_list_type填写错误") - sys.exit(1) - - self.use_tts = raw_config["Voice"].get("use_tts", False) - - self.debug_level = raw_config["Debug"].get("level", "INFO") - if self.debug_level == "DEBUG": - logger.debug("原始配置文件内容:") - logger.debug(raw_config) - logger.debug("读取到的配置内容:") - logger.debug(f"平台: {self.platform}") - logger.debug(f"MaiBot服务器地址: {self.mai_host}:{self.mai_port}") - logger.debug(f"Napcat服务器地址: {self.server_host}:{self.server_port}") - logger.debug(f"心跳间隔: {self.napcat_heartbeat_interval}秒") - logger.debug(f"群聊列表类型: {self.group_list_type}") - logger.debug(f"群聊列表: {self.group_list}") - logger.debug(f"私聊列表类型: {self.private_list_type}") - logger.debug(f"私聊列表: {self.private_list}") - logger.debug(f"禁用用户ID列表: {self.ban_user_id}") - logger.debug(f"是否启用TTS: {self.use_tts}") - logger.debug(f"调试级别: {self.debug_level}") - - -global_config = Config() -global_config.load_config() diff --git a/src/config/__init__.py b/src/config/__init__.py new file mode 100644 index 0000000..40ba89a --- /dev/null +++ b/src/config/__init__.py @@ -0,0 +1,5 @@ +from .config import global_config + +__all__ = [ + "global_config", +] diff --git a/src/config/config.py b/src/config/config.py new file mode 100644 index 0000000..a219078 --- /dev/null +++ b/src/config/config.py @@ -0,0 +1,140 @@ +import os +from dataclasses import dataclass + +import tomlkit +import shutil + +from tomlkit import TOMLDocument +from tomlkit.items import Table +from ..logger import logger +from rich.traceback import install + +from src.config.config_base import ConfigBase +from src.config.official_configs import ( + ChatConfig, + DebugConfig, + MaiBotServerConfig, + NapcatServerConfig, + NicknameConfig, + VoiceConfig, +) + +install(extra_lines=3) + +TEMPLATE_DIR = "template" + + +def update_config(): + # 定义文件路径 + template_path = f"{TEMPLATE_DIR}/template_config.toml" + old_config_path = "config.toml" + new_config_path = "config.toml" + + # 检查配置文件是否存在 + if not os.path.exists(old_config_path): + logger.info("配置文件不存在,从模板创建新配置") + shutil.copy2(template_path, old_config_path) # 复制模板文件 + logger.info(f"已创建新配置文件,请填写后重新运行: {old_config_path}") + # 如果是新创建的配置文件,直接返回 + quit() + + # 读取旧配置文件和模板文件 + with open(old_config_path, "r", encoding="utf-8") as f: + old_config = tomlkit.load(f) + with open(template_path, "r", encoding="utf-8") as f: + new_config = tomlkit.load(f) + + # 检查version是否相同 + if old_config and "inner" in old_config and "inner" in new_config: + old_version = old_config["inner"].get("version") + new_version = new_config["inner"].get("version") + if old_version and new_version and old_version == new_version: + logger.info(f"检测到配置文件版本号相同 (v{old_version}),跳过更新") + return + else: + logger.info(f"检测到版本号不同: 旧版本 v{old_version} -> 新版本 v{new_version}") + else: + logger.info("已有配置文件未检测到版本号,可能是旧版本。将进行更新") + + # 备份文件名 + old_backup_path = "config.toml.back" + + # 备份旧配置文件 + shutil.move(old_config_path, old_backup_path) + logger.info(f"已备份旧配置文件到: {old_backup_path}") + + # 复制模板文件到配置目录 + shutil.copy2(template_path, new_config_path) + logger.info(f"已创建新配置文件: {new_config_path}") + + def update_dict(target: TOMLDocument | dict, source: TOMLDocument | dict): + """ + 将source字典的值更新到target字典中(如果target中存在相同的键) + """ + for key, value in source.items(): + # 跳过version字段的更新 + if key == "version": + continue + if key in target: + if isinstance(value, dict) and isinstance(target[key], (dict, Table)): + update_dict(target[key], value) + else: + try: + # 对数组类型进行特殊处理 + if isinstance(value, list): + # 如果是空数组,确保它保持为空数组 + target[key] = tomlkit.array(str(value)) if value else tomlkit.array() + else: + # 其他类型使用item方法创建新值 + target[key] = tomlkit.item(value) + except (TypeError, ValueError): + # 如果转换失败,直接赋值 + target[key] = value + + # 将旧配置的值更新到新配置中 + logger.info("开始合并新旧配置...") + update_dict(new_config, old_config) + + # 保存更新后的配置(保留注释和格式) + with open(new_config_path, "w", encoding="utf-8") as f: + f.write(tomlkit.dumps(new_config)) + logger.info("配置文件更新完成,建议检查新配置文件中的内容,以免丢失重要信息") + quit() + + +@dataclass +class Config(ConfigBase): + """总配置类""" + + nickname: NicknameConfig + napcat_server: NapcatServerConfig + maibot_server: MaiBotServerConfig + chat: ChatConfig + voice: VoiceConfig + debug: DebugConfig + + +def load_config(config_path: str) -> Config: + """ + 加载配置文件 + :param config_path: 配置文件路径 + :return: Config对象 + """ + # 读取配置文件 + with open(config_path, "r", encoding="utf-8") as f: + config_data = tomlkit.load(f) + + # 创建Config对象 + try: + return Config.from_dict(config_data) + except Exception as e: + logger.critical("配置文件解析失败") + raise e + + +# 更新配置 +update_config() + +logger.info("正在品鉴配置文件...") +global_config = load_config(config_path="config.toml") +logger.info("非常的新鲜,非常的美味!") diff --git a/src/config/config_base.py b/src/config/config_base.py new file mode 100644 index 0000000..fbd3dd9 --- /dev/null +++ b/src/config/config_base.py @@ -0,0 +1,128 @@ +from dataclasses import dataclass, fields, MISSING +from typing import TypeVar, Type, Any, get_origin, get_args, Literal + +T = TypeVar("T", bound="ConfigBase") + +TOML_DICT_TYPE = { + int, + float, + str, + bool, + list, + dict, +} + + +@dataclass +class ConfigBase: + """配置类的基类""" + + @classmethod + def from_dict(cls: Type[T], data: dict[str, Any]) -> T: + """从字典加载配置字段""" + if not isinstance(data, dict): + raise TypeError(f"Expected a dictionary, got {type(data).__name__}") + + init_args: dict[str, Any] = {} + + for f in fields(cls): + field_name = f.name + + if field_name.startswith("_"): + # 跳过以 _ 开头的字段 + continue + + if field_name not in data: + if f.default is not MISSING or f.default_factory is not MISSING: + # 跳过未提供且有默认值/默认构造方法的字段 + continue + else: + raise ValueError(f"Missing required field: '{field_name}'") + + value = data[field_name] + field_type = f.type + + try: + init_args[field_name] = cls._convert_field(value, field_type) + except TypeError as e: + raise TypeError(f"Field '{field_name}' has a type error: {e}") from e + except Exception as e: + raise RuntimeError(f"Failed to convert field '{field_name}' to target type: {e}") from e + + return cls(**init_args) + + @classmethod + def _convert_field(cls, value: Any, field_type: Type[Any]) -> Any: + """ + 转换字段值为指定类型 + + 1. 对于嵌套的 dataclass,递归调用相应的 from_dict 方法 + 2. 对于泛型集合类型(list, set, tuple),递归转换每个元素 + 3. 对于基础类型(int, str, float, bool),直接转换 + 4. 对于其他类型,尝试直接转换,如果失败则抛出异常 + """ + + # 如果是嵌套的 dataclass,递归调用 from_dict 方法 + if isinstance(field_type, type) and issubclass(field_type, ConfigBase): + if not isinstance(value, dict): + raise TypeError(f"Expected a dictionary for {field_type.__name__}, got {type(value).__name__}") + return field_type.from_dict(value) + + # 处理泛型集合类型(list, set, tuple) + field_origin_type = get_origin(field_type) + field_type_args = get_args(field_type) + + if field_origin_type in {list, set, tuple}: + # 检查提供的value是否为list + if not isinstance(value, list): + raise TypeError(f"Expected an list for {field_type.__name__}, got {type(value).__name__}") + + if field_origin_type is list: + return [cls._convert_field(item, field_type_args[0]) for item in value] + elif field_origin_type is set: + return {cls._convert_field(item, field_type_args[0]) for item in value} + elif field_origin_type is tuple: + # 检查提供的value长度是否与类型参数一致 + if len(value) != len(field_type_args): + raise TypeError( + f"Expected {len(field_type_args)} items for {field_type.__name__}, got {len(value)}" + ) + return tuple(cls._convert_field(item, arg) for item, arg in zip(value, field_type_args)) + + if field_origin_type is dict: + # 检查提供的value是否为dict + if not isinstance(value, dict): + raise TypeError(f"Expected a dictionary for {field_type.__name__}, got {type(value).__name__}") + + # 检查字典的键值类型 + if len(field_type_args) != 2: + raise TypeError(f"Expected a dictionary with two type arguments for {field_type.__name__}") + key_type, value_type = field_type_args + + return {cls._convert_field(k, key_type): cls._convert_field(v, value_type) for k, v in value.items()} + + # 处理基础类型,例如 int, str 等 + if field_origin_type is type(None) and value is None: # 处理Optional类型 + return None + + # 处理Literal类型 + if field_origin_type is Literal or get_origin(field_type) is Literal: + # 获取Literal的允许值 + allowed_values = get_args(field_type) + if value in allowed_values: + return value + else: + raise TypeError(f"Value '{value}' is not in allowed values {allowed_values} for Literal type") + + if field_type is Any or isinstance(value, field_type): + return value + + # 其他类型,尝试直接转换 + try: + return field_type(value) + except (ValueError, TypeError) as e: + raise TypeError(f"Cannot convert {type(value).__name__} to {field_type.__name__}") from e + + def __str__(self): + """返回配置类的字符串表示""" + return f"{self.__class__.__name__}({', '.join(f'{f.name}={getattr(self, f.name)}' for f in fields(self))})" diff --git a/src/config/official_configs.py b/src/config/official_configs.py new file mode 100644 index 0000000..3119ffb --- /dev/null +++ b/src/config/official_configs.py @@ -0,0 +1,77 @@ +from dataclasses import dataclass, field +from typing import Literal + +from src.config.config_base import ConfigBase + +""" +须知: +1. 本文件中记录了所有的配置项 +2. 所有新增的class都需要继承自ConfigBase +3. 所有新增的class都应在config.py中的Config类中添加字段 +4. 对于新增的字段,若为可选项,则应在其后添加field()并设置default_factory或default +""" + +ADAPTER_PLATFORM = "qq" + + +@dataclass +class NicknameConfig(ConfigBase): + nickname: str + """机器人昵称""" + + +@dataclass +class NapcatServerConfig(ConfigBase): + host: str = "localhost" + """Napcat服务端的主机地址""" + + port: int = 8095 + """Napcat服务端的端口号""" + + heartbeat_interval: int = 30 + """Napcat心跳间隔时间,单位为秒""" + + +@dataclass +class MaiBotServerConfig(ConfigBase): + platform_name: str = field(default=ADAPTER_PLATFORM, init=False) + """平台名称,“qq”""" + + host: str = "localhost" + """MaiMCore的主机地址""" + + port: int = 8000 + """MaiMCore的端口号""" + + +@dataclass +class ChatConfig(ConfigBase): + group_list_type: Literal["whitelist", "blacklist"] = "whitelist" + """群聊列表类型 白名单/黑名单""" + + group_list: list[str] = field(default_factory=[]) + """群聊列表""" + + private_list_type: Literal["whitelist", "blacklist"] = "whitelist" + """私聊列表类型 白名单/黑名单""" + + private_list: list[str] = field(default_factory=[]) + """私聊列表""" + + ban_user_id: list[str] = field(default_factory=[]) + """被封禁的用户ID列表,封禁后将无法与其进行交互""" + + enable_poke: bool = True + """是否启用戳一戳功能""" + + +@dataclass +class VoiceConfig(ConfigBase): + use_tts: bool = False + """是否启用TTS功能""" + + +@dataclass +class DebugConfig(ConfigBase): + level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO" + """日志级别,默认为INFO""" diff --git a/src/logger.py b/src/logger.py index 3acba4f..8071ff7 100644 --- a/src/logger.py +++ b/src/logger.py @@ -5,6 +5,6 @@ logger.remove() logger.add( sys.stderr, - level=global_config.debug_level, + level=global_config.debug.level, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", ) diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index 174ef1f..ab50cca 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -5,8 +5,8 @@ route_config = RouteConfig( route_config={ - global_config.platform: TargetConfig( - url=f"ws://{global_config.mai_host}:{global_config.mai_port}/ws", + global_config.maibot_server.platform_name: TargetConfig( + url=f"ws://{global_config.maibot_server.host}:{global_config.maibot_server.port}/ws", token=None, ) } diff --git a/src/recv_handler.py b/src/recv_handler.py index 7e031ec..21ff56f 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -36,7 +36,7 @@ class RecvHandler: def __init__(self): self.server_connection: Server.ServerConnection = None - self.interval = global_config.napcat_heartbeat_interval + self.interval = global_config.napcat_server.heartbeat_interval async def handle_meta_event(self, message: dict) -> None: event_type = message.get("meta_event_type") @@ -77,20 +77,20 @@ def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: """ logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") if group_id: - if global_config.group_list_type == "whitelist" and group_id not in global_config.group_list: + if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: logger.warning("群聊不在聊天白名单中,消息被丢弃") return False - elif global_config.group_list_type == "blacklist" and group_id in global_config.group_list: + elif global_config.chat.group_list_type == "blacklist" and group_id in global_config.chat.group_list: logger.warning("群聊在聊天黑名单中,消息被丢弃") return False else: - if global_config.private_list_type == "whitelist" and user_id not in global_config.private_list: + if global_config.chat.private_list_type == "whitelist" and user_id not in global_config.chat.private_list: logger.warning("私聊不在聊天白名单中,消息被丢弃") return False - elif global_config.private_list_type == "blacklist" and user_id in global_config.private_list: + elif global_config.chat.private_list_type == "blacklist" and user_id in global_config.chat.private_list: logger.warning("私聊在聊天黑名单中,消息被丢弃") return False - if user_id in global_config.ban_user_id: + if user_id in global_config.chat.ban_user_id: logger.warning("用户在全局黑名单中,消息被丢弃") return False return True @@ -123,7 +123,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: # 发送者用户信息 user_info: UserInfo = UserInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, user_id=sender_info.get("user_id"), user_nickname=sender_info.get("nickname"), user_cardname=sender_info.get("card"), @@ -149,7 +149,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: nickname = fetched_member_info.get("nickname") if fetched_member_info else None # 发送者用户信息 user_info: UserInfo = UserInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, user_id=sender_info.get("user_id"), user_nickname=nickname, user_cardname=None, @@ -164,7 +164,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: group_name = fetched_group_info.get("group_name") group_info: GroupInfo = GroupInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, group_id=raw_message.get("group_id"), group_name=group_name, ) @@ -182,7 +182,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: # 发送者用户信息 user_info: UserInfo = UserInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, user_id=sender_info.get("user_id"), user_nickname=sender_info.get("nickname"), user_cardname=sender_info.get("card"), @@ -195,7 +195,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: group_name = fetched_group_info.get("group_name") group_info: GroupInfo = GroupInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, group_id=raw_message.get("group_id"), group_name=group_name, ) @@ -205,12 +205,12 @@ async def handle_raw_message(self, raw_message: dict) -> None: return None additional_config: dict = {} - if global_config.use_tts: + if global_config.voice.use_tts: additional_config["allow_tts"] = True # 消息信息 message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, message_id=message_id, time=message_time, user_info=user_info, @@ -500,7 +500,7 @@ async def handle_notice(self, raw_message: dict) -> None: sub_type = raw_message.get("sub_type") match sub_type: case NoticeType.Notify.poke: - if global_config.enable_poke: + if global_config.chat.enable_poke: handled_message: Seg = await self.handle_poke_notify(raw_message) else: logger.warning("戳一戳消息被禁用,取消戳一戳处理") @@ -532,7 +532,7 @@ async def handle_notice(self, raw_message: dict) -> None: source_name = "QQ用户" user_info: UserInfo = UserInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, user_id=user_id, user_nickname=source_name, user_cardname=source_cardname, @@ -547,13 +547,13 @@ async def handle_notice(self, raw_message: dict) -> None: else: logger.warning("无法获取戳一戳消息所在群的名称") group_info = GroupInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, group_id=group_id, group_name=group_name, ) message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.platform, + platform=global_config.maibot_server.platform_name, message_id="notice", time=message_time, user_info=user_info, diff --git a/src/response_pool.py b/src/response_pool.py index 66ded1d..c41ed7f 100644 --- a/src/response_pool.py +++ b/src/response_pool.py @@ -35,10 +35,10 @@ async def check_timeout_response() -> None: cleaned_message_count: int = 0 now_time = time.time() for echo_id, response_time in list(response_time_dict.items()): - if now_time - response_time > global_config.napcat_heartbeat_interval: + if now_time - response_time > global_config.napcat_server.heartbeat_interval: cleaned_message_count += 1 response_dict.pop(echo_id) response_time_dict.pop(echo_id) logger.warning(f"响应消息 {echo_id} 超时,已删除") logger.info(f"已删除 {cleaned_message_count} 条超时响应消息") - await asyncio.sleep(global_config.napcat_heartbeat_interval) + await asyncio.sleep(global_config.napcat_server.heartbeat_interval) diff --git a/src/send_handler.py b/src/send_handler.py index baf6fe7..74646b6 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -209,7 +209,7 @@ def handle_emoji_message(self, encoded_emoji: str) -> dict: def handle_voice_message(self, encoded_voice: str) -> dict: """处理语音消息""" - if not global_config.use_tts: + if not global_config.voice.use_tts: logger.warning("未启用语音消息处理") return {} if not encoded_voice: diff --git a/template/template_config.toml b/template/template_config.toml index 1d0d830..b4cdce0 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -1,30 +1,33 @@ -[Nickname] # 现在没用 +[inner] +version = "0.1.0" # 版本号 +# 请勿修改版本号,除非你知道自己在做什么 + +[nickname] # 现在没用 nickname = "" -[Napcat_Server] # Napcat连接的ws服务设置 -host = "localhost" # Napcat设定的主机地址 -port = 8095 # Napcat设定的端口 -heartbeat = 30 # 与Napcat设置的心跳相同(按秒计) +[napcat_server] # Napcat连接的ws服务设置 +host = "localhost" # Napcat设定的主机地址 +port = 8095 # Napcat设定的端口 +heartbeat_interval = 30 # 与Napcat设置的心跳相同(按秒计) -[MaiBot_Server] # 连接麦麦的ws服务设置 -platform_name = "qq" # 标识adapter的名称(必填) -host = "localhost" # 麦麦在.env文件中设置的主机地址,即HOST字段 -port = 8000 # 麦麦在.env文件中设置的端口,即PORT字段 +[maibot_server] # 连接麦麦的ws服务设置 +host = "localhost" # 麦麦在.env文件中设置的主机地址,即HOST字段 +port = 8000 # 麦麦在.env文件中设置的端口,即PORT字段 -[Chat] # 黑白名单功能 +[chat] # 黑白名单功能 group_list_type = "whitelist" # 群组名单类型,可选为:whitelist, blacklist -group_list = [] # 群组名单 +group_list = [] # 群组名单 # 当group_list_type为whitelist时,只有群组名单中的群组可以聊天 # 当group_list_type为blacklist时,群组名单中的任何群组无法聊天 private_list_type = "whitelist" # 私聊名单类型,可选为:whitelist, blacklist -private_list = [] # 私聊名单 +private_list = [] # 私聊名单 # 当private_list_type为whitelist时,只有私聊名单中的用户可以聊天 # 当private_list_type为blacklist时,私聊名单中的任何用户无法聊天 -ban_user_id = [] # 全局禁止名单(全局禁止名单中的用户无法进行任何聊天) +ban_user_id = [] # 全局禁止名单(全局禁止名单中的用户无法进行任何聊天) enable_poke = true # 是否启用戳一戳功能 -[Voice] # 发送语音设置 +[voice] # 发送语音设置 use_tts = false # 是否使用tts语音(请确保你配置了tts并有对应的adapter) -[Debug] -level = "INFO" # 日志等级(DEBUG, INFO, WARNING, ERROR) +[debug] +level = "INFO" # 日志等级(DEBUG, INFO, WARNING, ERROR, CRITICAL) From d64670a930b9e81614cc3de3d77a1aaac3043e20 Mon Sep 17 00:00:00 2001 From: Oct-autumn Date: Wed, 4 Jun 2025 23:34:11 +0800 Subject: [PATCH 008/112] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E4=B8=8A?= =?UTF-8?q?=E4=B8=AA=E6=8F=90=E4=BA=A4=E6=BC=8F=E6=8E=89=E7=9A=84=E5=87=A0?= =?UTF-8?q?=E5=A4=84global=5Fconfig=E7=9A=84=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + main.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6d6652c..267374b 100644 --- a/.gitignore +++ b/.gitignore @@ -270,4 +270,5 @@ $RECYCLE.BIN/ *.lnk config.toml +config.toml.back test \ No newline at end of file diff --git a/main.py b/main.py index 50cf968..c3c6cdb 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ async def message_recv(server_connection: Server.ServerConnection): async for raw_message in server_connection: logger.debug( f"{raw_message[:100]}..." - if (len(raw_message) > 100 and global_config.debug_level != "DEBUG") + if (len(raw_message) > 100 and global_config.debug.level != "DEBUG") else raw_message ) decoded_raw_message: dict = json.loads(raw_message) @@ -52,8 +52,10 @@ async def main(): async def napcat_server(): logger.info("正在启动adapter...") - async with Server.serve(message_recv, global_config.server_host, global_config.server_port) as server: - logger.info(f"Adapter已启动,监听地址: ws://{global_config.server_host}:{global_config.server_port}") + async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port) as server: + logger.info( + f"Adapter已启动,监听地址: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" + ) await server.serve_forever() From 7373e75c75ae28f46400ccf120dbb8572e2f5628 Mon Sep 17 00:00:00 2001 From: xuqian13 <1334431750@qq.com> Date: Sat, 14 Jun 2025 08:04:33 +0000 Subject: [PATCH 009/112] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=8F=91?= =?UTF-8?q?=E9=80=81=E8=AF=AD=E9=9F=B3=E5=8F=AF=E9=80=89=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=92=8C=E7=BD=91=E7=BB=9C=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index baf6fe7..f2a0c83 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -207,16 +207,27 @@ def handle_emoji_message(self, encoded_emoji: str) -> dict: }, } - def handle_voice_message(self, encoded_voice: str) -> dict: + + + + def handle_voice_message(self, voice_data_or_path: str) -> dict: """处理语音消息""" - if not global_config.use_tts: - logger.warning("未启用语音消息处理") - return {} - if not encoded_voice: + + if not voice_data_or_path: return {} + + if voice_data_or_path.startswith("file://") or voice_data_or_path.startswith("http://") or voice_data_or_path.startswith("https://"): + file_value = voice_data_or_path + logger.debug(f"识别到语音数据为路径/URL: {file_value}") + elif voice_data_or_path.startswith("base64://"): + file_value = voice_data_or_path + logger.debug(f"识别到语音数据为 Base64 (带前缀): {file_value[:50]}...") + else: + file_value = f"base64://{voice_data_or_path}" + logger.debug(f"识别到语音数据为 Base64 (无前缀): {voice_data_or_path[:50]}...") return { "type": "record", - "data": {"file": f"base64://{encoded_voice}"}, + "data": {"file": file_value}, } def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: @@ -296,16 +307,25 @@ def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu ) async def send_message_to_napcat(self, action: str, params: dict) -> dict: + if not self.server_connection: + logger.error("Adapter 未连接到平台 API,无法发送消息!") + return {"status": "error", "message": "adapter not connected to platform api"} + request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) - await self.server_connection.send(payload) + try: + await self.server_connection.send(payload) + logger.debug(f"发送平台 API 命令: action={action}, echo={request_uuid}") + response = await get_response(request_uuid) + logger.debug(f"收到平台 API 响应: status={response.get('status')}, echo={response.get('echo')}") + except TimeoutError: - logger.error("发送消息超时,未收到响应") + logger.error(f"发送平台 API 命令 '{action}' 超时,未收到响应 (echo: {request_uuid})") return {"status": "error", "message": "timeout"} except Exception as e: - logger.error(f"发送消息失败: {e}") + logger.error(f"发送平台 API 命令 '{action}' 失败 (echo: {request_uuid}): {e}") return {"status": "error", "message": str(e)} return response From 81a71af4aa1af278fdf1cc74dc4f4b05dd773783 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 15 Jun 2025 16:40:25 +0800 Subject: [PATCH 010/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8DConfig=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=B2=A1=E8=BD=AC=E6=8D=A2=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 16 +++++---- src/config/config_base.py | 66 +++++++++++++++++++--------------- src/config/official_configs.py | 6 ++-- src/recv_handler.py | 2 +- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/main.py b/main.py index 50cf968..2c71ef1 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ async def message_recv(server_connection: Server.ServerConnection): async for raw_message in server_connection: logger.debug( f"{raw_message[:100]}..." - if (len(raw_message) > 100 and global_config.debug_level != "DEBUG") + if (len(raw_message) > 100 and global_config.debug.level != "DEBUG") else raw_message ) decoded_raw_message: dict = json.loads(raw_message) @@ -52,19 +52,23 @@ async def main(): async def napcat_server(): logger.info("正在启动adapter...") - async with Server.serve(message_recv, global_config.server_host, global_config.server_port) as server: - logger.info(f"Adapter已启动,监听地址: ws://{global_config.server_host}:{global_config.server_port}") + async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port) as server: + logger.info( + f"Adapter已启动,监听地址: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" + ) await server.serve_forever() async def graceful_shutdown(): try: logger.info("正在关闭adapter...") - await mmc_stop_com() tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] for task in tasks: - task.cancel() - await asyncio.gather(*tasks, return_exceptions=True) + if not task.done(): + task.cancel() + await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), 15) + await mmc_stop_com() # 后置避免神秘exception + logger.info("Adapter已成功关闭") except Exception as e: logger.error(f"Adapter关闭中出现错误: {e}") diff --git a/src/config/config_base.py b/src/config/config_base.py index fbd3dd9..518f99c 100644 --- a/src/config/config_base.py +++ b/src/config/config_base.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, fields, MISSING -from typing import TypeVar, Type, Any, get_origin, get_args, Literal +from typing import TypeVar, Type, Any, get_origin, get_args, Literal, Dict, List, Set, Tuple, Union T = TypeVar("T", bound="ConfigBase") @@ -18,16 +18,16 @@ class ConfigBase: """配置类的基类""" @classmethod - def from_dict(cls: Type[T], data: dict[str, Any]) -> T: + def from_dict(cls: Type[T], data: Dict[str, Any]) -> T: """从字典加载配置字段""" if not isinstance(data, dict): raise TypeError(f"Expected a dictionary, got {type(data).__name__}") - init_args: dict[str, Any] = {} + init_args: Dict[str, Any] = {} for f in fields(cls): field_name = f.name - + field_type = f.type if field_name.startswith("_"): # 跳过以 _ 开头的字段 continue @@ -40,14 +40,12 @@ def from_dict(cls: Type[T], data: dict[str, Any]) -> T: raise ValueError(f"Missing required field: '{field_name}'") value = data[field_name] - field_type = f.type - try: init_args[field_name] = cls._convert_field(value, field_type) except TypeError as e: - raise TypeError(f"Field '{field_name}' has a type error: {e}") from e + raise TypeError(f"字段 '{field_name}' 出现类型错误: {e}") from e except Exception as e: - raise RuntimeError(f"Failed to convert field '{field_name}' to target type: {e}") from e + raise RuntimeError(f"无法将字段 '{field_name}' 转换为目标类型,出现错误: {e}") from e return cls(**init_args) @@ -61,33 +59,30 @@ def _convert_field(cls, value: Any, field_type: Type[Any]) -> Any: 3. 对于基础类型(int, str, float, bool),直接转换 4. 对于其他类型,尝试直接转换,如果失败则抛出异常 """ - # 如果是嵌套的 dataclass,递归调用 from_dict 方法 if isinstance(field_type, type) and issubclass(field_type, ConfigBase): - if not isinstance(value, dict): - raise TypeError(f"Expected a dictionary for {field_type.__name__}, got {type(value).__name__}") return field_type.from_dict(value) - # 处理泛型集合类型(list, set, tuple) field_origin_type = get_origin(field_type) - field_type_args = get_args(field_type) + field_args_type = get_args(field_type) + # 处理泛型集合类型(list, set, tuple) if field_origin_type in {list, set, tuple}: # 检查提供的value是否为list if not isinstance(value, list): raise TypeError(f"Expected an list for {field_type.__name__}, got {type(value).__name__}") if field_origin_type is list: - return [cls._convert_field(item, field_type_args[0]) for item in value] - elif field_origin_type is set: - return {cls._convert_field(item, field_type_args[0]) for item in value} - elif field_origin_type is tuple: + return [cls._convert_field(item, field_args_type[0]) for item in value] + if field_origin_type is set: + return {cls._convert_field(item, field_args_type[0]) for item in value} + if field_origin_type is tuple: # 检查提供的value长度是否与类型参数一致 - if len(value) != len(field_type_args): + if len(value) != len(field_args_type): raise TypeError( - f"Expected {len(field_type_args)} items for {field_type.__name__}, got {len(value)}" + f"Expected {len(field_args_type)} items for {field_type.__name__}, got {len(value)}" ) - return tuple(cls._convert_field(item, arg) for item, arg in zip(value, field_type_args)) + return tuple(cls._convert_field(item, arg_type) for item, arg_type in zip(value, field_args_type)) if field_origin_type is dict: # 检查提供的value是否为dict @@ -95,18 +90,30 @@ def _convert_field(cls, value: Any, field_type: Type[Any]) -> Any: raise TypeError(f"Expected a dictionary for {field_type.__name__}, got {type(value).__name__}") # 检查字典的键值类型 - if len(field_type_args) != 2: + if len(field_args_type) != 2: raise TypeError(f"Expected a dictionary with two type arguments for {field_type.__name__}") - key_type, value_type = field_type_args + key_type, value_type = field_args_type return {cls._convert_field(k, key_type): cls._convert_field(v, value_type) for k, v in value.items()} - # 处理基础类型,例如 int, str 等 - if field_origin_type is type(None) and value is None: # 处理Optional类型 - return None + # 处理Optional类型 + if field_origin_type is Union: # assert get_origin(Optional[Any]) is Union + if value is None: + return None + # 如果有数据,检查实际类型 + if type(value) not in field_args_type: + raise TypeError(f"Expected {field_args_type} for {field_type.__name__}, got {type(value).__name__}") + return cls._convert_field(value, field_args_type[0]) + + # 处理int, str, float, bool等基础类型 + if field_origin_type is None: + if isinstance(value, field_type): + return field_type(value) + else: + raise TypeError(f"Expected {field_type.__name__}, got {type(value).__name__}") # 处理Literal类型 - if field_origin_type is Literal or get_origin(field_type) is Literal: + if field_origin_type is Literal: # 获取Literal的允许值 allowed_values = get_args(field_type) if value in allowed_values: @@ -114,14 +121,15 @@ def _convert_field(cls, value: Any, field_type: Type[Any]) -> Any: else: raise TypeError(f"Value '{value}' is not in allowed values {allowed_values} for Literal type") - if field_type is Any or isinstance(value, field_type): + # 处理其他类型 + if field_type is Any: return value - # 其他类型,尝试直接转换 + # 其他类型直接转换 try: return field_type(value) except (ValueError, TypeError) as e: - raise TypeError(f"Cannot convert {type(value).__name__} to {field_type.__name__}") from e + raise TypeError(f"无法将 {type(value).__name__} 转换为 {field_type.__name__}") from e def __str__(self): """返回配置类的字符串表示""" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 3119ffb..d8928a8 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -49,16 +49,16 @@ class ChatConfig(ConfigBase): group_list_type: Literal["whitelist", "blacklist"] = "whitelist" """群聊列表类型 白名单/黑名单""" - group_list: list[str] = field(default_factory=[]) + group_list: list[int] = field(default_factory=[]) """群聊列表""" private_list_type: Literal["whitelist", "blacklist"] = "whitelist" """私聊列表类型 白名单/黑名单""" - private_list: list[str] = field(default_factory=[]) + private_list: list[int] = field(default_factory=[]) """私聊列表""" - ban_user_id: list[str] = field(default_factory=[]) + ban_user_id: list[int] = field(default_factory=[]) """被封禁的用户ID列表,封禁后将无法与其进行交互""" enable_poke: bool = True diff --git a/src/recv_handler.py b/src/recv_handler.py index 21ff56f..7cc0a07 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -697,7 +697,7 @@ async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple user_nickname: str = sender_info.get("nickname", "QQ用户") user_nickname_str = f"【{user_nickname}】:" break_seg = Seg(type="text", data="\n") - message_of_sub_message_list: dict = sub_message.get("message") + message_of_sub_message_list: List[Dict[str, Any]] = sub_message.get("message") if not message_of_sub_message_list: logger.warning("转发消息内容为空") continue From e1ab7b69568988fe7a881ecb911af7536e9321a6 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 15 Jun 2025 16:46:13 +0800 Subject: [PATCH 011/112] ruff --- src/config/config_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config_base.py b/src/config/config_base.py index 518f99c..87cb079 100644 --- a/src/config/config_base.py +++ b/src/config/config_base.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, fields, MISSING -from typing import TypeVar, Type, Any, get_origin, get_args, Literal, Dict, List, Set, Tuple, Union +from typing import TypeVar, Type, Any, get_origin, get_args, Literal, Dict, Union T = TypeVar("T", bound="ConfigBase") From d72082989af57ec74119153bd6dac04935b79034 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 17 Jun 2025 16:31:27 +0800 Subject: [PATCH 012/112] =?UTF-8?q?=E6=97=B6=E5=B0=9A=E5=B0=8F=E5=9E=83?= =?UTF-8?q?=E5=9C=BE=E4=B9=8B=E7=B3=BB=E7=BB=9F=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/maimai.ico | Bin 0 -> 67715 bytes src/recv_handler.py | 23 ++++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 assets/maimai.ico diff --git a/assets/maimai.ico b/assets/maimai.ico new file mode 100644 index 0000000000000000000000000000000000000000..578b11cdd0c88dc8f590668a718303d939754f67 GIT binary patch literal 67715 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

9q=wX1$>M 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⁢M5M6l!#t$4Iq9u3gs}Q_@_7Zo*5SE}Y^vaGH+ky#ka*McEbX+p0En4|w zJv}*^)jKx$3xBcXbAs;$lLegB94I^%4(4Ouyaezy35Mg#AvWc?FabI#9RRVY@CO%g zs}v_U?N`&dk`R-gHo65uH*zg+J*qrTrN`5mr53jZbD}tat?24fkzu^;2mptc&n~-_ zu#^AlBbnTt0Lm2rKaN#nbCt5NL(?6ybTSRCZX?ApIte1csD2uNdkRBi8{Syz4cj(0fpLIRiJR}Qvl-hQIH#R|C<~$G)%PvALv*kF zf7x~UCq6U%lf8qnPb3y{a;|xBn)QL14cY^GR&wqDhP%*REGgA+Q>6EJzYHd%hg@d%epovrA(zKoXD#1j!?nP<6J4aKuF1)oJ{(KtipOC= z&Nh_*+uMPoRF9YJ_V3qq7bqAFJKb?Y6R7yGkc0k}uM7Zrkh1`Bf585`+oiFTgos}k zltM1oOD-tSbKOpb^rY`0?t=cECd}qSuykTn}IkC^lS}6s8BGT&9JLS3nQ@6c8 z>tI0mv)k^(>T{VY6FrveuS_ z;`HF3-`|6OXhv8Z4oh2DNG?9nmyg=O)3DCa0k4zIo{m9)d0FzMJL{r$p}pX)J)q(m z97J{IHnj9o(xU)?S3O*k1%yCY0roc?fLFskXiICXpl$Z1hS(su0v+$Nnk2sj8aqc_ zOxAVe$e`MBtD{t<^mHA*VZ4&WU>+f(!f{Ya#x%I)b|< zveT>*`6GmugX9Riw(iTBzi_u%cE%tdHf_k<1_xh;UMQoZYk>67R41XXc&_1$1041G z=4eo;$b|rTe+iZ7G+UGv-^U`Z=2{anE?1Y0;t&+qi3nw~HT|T?DMlxC?xqF2OG>4- zS737imSE+7rYgG+1XNMYuFpB z_yB9c@Cu0uWXZ*VOj4OEM-Hjx2;J-(n^p(4P0O6~{@!M}gq`@_2d6XWngeOxVLfrw zj)!8MQrH;2{mqw&lbe?nc(*PNya4MrBwN3{+3sYW#>H0Qxu91>kjsK!RO2ILSS{8QS@p5�Mr;^wE$g-l5zBebV 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 None: event_type = message.get("meta_event_type") @@ -49,6 +52,8 @@ async def handle_meta_event(self, message: dict) -> None: asyncio.create_task(self.check_heartbeat(self_id)) elif event_type == MetaEventType.heartbeat: if message["status"].get("online") and message["status"].get("good"): + if not self._interval_checking: + asyncio.create_task(self.check_heartbeat()) self.last_heart_beat = time.time() self.interval = message.get("interval") / 1000 else: @@ -56,10 +61,20 @@ async def handle_meta_event(self, message: dict) -> None: logger.warning(f"Bot {self_id} Napcat 端异常!") async def check_heartbeat(self, id: int) -> None: + self._interval_checking = True while True: now_time = time.time() - if now_time - self.last_heart_beat > self.interval + 3: - logger.warning(f"Bot {id} 连接已断开") + if now_time - self.last_heart_beat > self.interval * 2: + logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") + current_dir = os.path.dirname(__file__) + icon_path = os.path.join(current_dir, "..", "assets", "maimai.ico") + notification.notify( + title="警告", + message=f"Bot {id} 连接已断开,被下线,或者Napcat卡死!", + app_name="MaiBot Napcat Adapter", + timeout=10, + app_icon=icon_path, + ) break else: logger.debug("心跳正常") @@ -769,7 +784,9 @@ async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple async def message_process(self, message_base: MessageBase) -> None: try: - await self.maibot_router.send_message(message_base) + send_status = await self.maibot_router.send_message(message_base) + if not send_status: + raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") except Exception as e: logger.error(f"发送消息失败: {str(e)}") logger.error("请检查与MaiBot之间的连接") From 79ef02f19378696dde49ccbba89af2821b67ac9b Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 17 Jun 2025 16:31:56 +0800 Subject: [PATCH 013/112] =?UTF-8?q?=E5=8F=88=E5=BF=98=E4=BA=86ruff?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler.py b/src/recv_handler.py index 6189009..7b11fbd 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -7,7 +7,7 @@ import websockets as Server from typing import List, Tuple, Optional, Dict, Any import uuid -from plyer import notification, facades +from plyer import notification import os from . import MetaEventType, RealMessageType, MessageType, NoticeType From 51cbb2b227c9056255ca8d576710464adbe83441 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 17 Jun 2025 16:33:04 +0800 Subject: [PATCH 014/112] =?UTF-8?q?requirements.txt=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 41e8eb1..e5964ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ requests maim_message loguru pillow -tomli \ No newline at end of file +tomli +plyer \ No newline at end of file From 7c78027d4b8f090d2691d5541a7f860d01600c10 Mon Sep 17 00:00:00 2001 From: 1334431750 <1334431750@qq.com> Date: Sat, 21 Jun 2025 09:40:40 +0000 Subject: [PATCH 015/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8F=91?= =?UTF-8?q?=E9=80=81=E9=9F=B3=E4=B9=90=E5=8D=A1=E7=89=87=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 59 ++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index f2a0c83..f506d00 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -156,6 +156,12 @@ def process_message_by_type(self, seg: Seg, payload: list) -> list: elif seg.type == "voice": voice = seg.data new_payload = self.build_payload(payload, self.handle_voice_message(voice), False) + elif seg.type == "voiceurl": + voice = seg.data + new_payload = self.build_payload(payload, self.handle_voiceurl_message(voice), False) + elif seg.type == "music": + music = seg.data + new_payload = self.build_payload(payload, self.handle_music_message(music), False) return new_payload def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: @@ -207,27 +213,33 @@ def handle_emoji_message(self, encoded_emoji: str) -> dict: }, } - - - - def handle_voice_message(self, voice_data_or_path: str) -> dict: + def handle_voice_message(self, encoded_voice: str) -> dict: """处理语音消息""" - - if not voice_data_or_path: + if not global_config.use_tts: + logger.warning("未启用语音消息处理") return {} + if not encoded_voice: + return {} + return { + "type": "record", + "data": {"file": f"base64://{encoded_voice}"}, + } - if voice_data_or_path.startswith("file://") or voice_data_or_path.startswith("http://") or voice_data_or_path.startswith("https://"): - file_value = voice_data_or_path - logger.debug(f"识别到语音数据为路径/URL: {file_value}") - elif voice_data_or_path.startswith("base64://"): - file_value = voice_data_or_path - logger.debug(f"识别到语音数据为 Base64 (带前缀): {file_value[:50]}...") - else: - file_value = f"base64://{voice_data_or_path}" - logger.debug(f"识别到语音数据为 Base64 (无前缀): {voice_data_or_path[:50]}...") + def handle_voiceurl_message(self, voice_url: str) -> dict: + """处理语音链接消息""" return { "type": "record", - "data": {"file": file_value}, + "data": {"file": voice_url}, + } + + def handle_music_message(self, song_id: str) -> dict: + """处理音乐消息""" + return { + "type": "music", + "data": { + "type": "163", + "id": song_id + }, } def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: @@ -307,25 +319,16 @@ def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu ) async def send_message_to_napcat(self, action: str, params: dict) -> dict: - if not self.server_connection: - logger.error("Adapter 未连接到平台 API,无法发送消息!") - return {"status": "error", "message": "adapter not connected to platform api"} - request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) - + await self.server_connection.send(payload) try: - await self.server_connection.send(payload) - logger.debug(f"发送平台 API 命令: action={action}, echo={request_uuid}") - response = await get_response(request_uuid) - logger.debug(f"收到平台 API 响应: status={response.get('status')}, echo={response.get('echo')}") - except TimeoutError: - logger.error(f"发送平台 API 命令 '{action}' 超时,未收到响应 (echo: {request_uuid})") + logger.error("发送消息超时,未收到响应") return {"status": "error", "message": "timeout"} except Exception as e: - logger.error(f"发送平台 API 命令 '{action}' 失败 (echo: {request_uuid}): {e}") + logger.error(f"发送消息失败: {e}") return {"status": "error", "message": str(e)} return response From bfb91702365d2f0fd3cb7a34b1b9f1a16069e480 Mon Sep 17 00:00:00 2001 From: 1334431750 <1334431750@qq.com> Date: Sat, 21 Jun 2025 09:50:20 +0000 Subject: [PATCH 016/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8F=91?= =?UTF-8?q?=E9=80=81=E9=9F=B3=E4=B9=90=E5=8D=A1=E7=89=87=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index f506d00..b763d96 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -157,11 +157,11 @@ def process_message_by_type(self, seg: Seg, payload: list) -> list: voice = seg.data new_payload = self.build_payload(payload, self.handle_voice_message(voice), False) elif seg.type == "voiceurl": - voice = seg.data - new_payload = self.build_payload(payload, self.handle_voiceurl_message(voice), False) + voice_url = seg.data + new_payload = self.build_payload(payload, self.handle_voiceurl_message(voice_url), False) elif seg.type == "music": - music = seg.data - new_payload = self.build_payload(payload, self.handle_music_message(music), False) + song_id = seg.data + new_payload = self.build_payload(payload, self.handle_music_message(song_id), False) return new_payload def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: From b5e7316b948305161da2a2224e31af15b4bb6833 Mon Sep 17 00:00:00 2001 From: A0000Xz <122650088+A0000Xz@users.noreply.github.com> Date: Sun, 22 Jun 2025 01:59:42 +0800 Subject: [PATCH 017/112] Add files via upload --- src/__init__.py | 155 +++++------ src/send_handler.py | 655 +++++++++++++++++++++++--------------------- 2 files changed, 420 insertions(+), 390 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index a35ff0e..e12aede 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,77 +1,78 @@ -from enum import Enum - - -class MetaEventType: - lifecycle = "lifecycle" # 生命周期 - - class Lifecycle: - connect = "connect" # 生命周期 - WebSocket 连接成功 - - heartbeat = "heartbeat" # 心跳 - - -class MessageType: # 接受消息大类 - private = "private" # 私聊消息 - - class Private: - friend = "friend" # 私聊消息 - 好友 - group = "group" # 私聊消息 - 群临时 - group_self = "group_self" # 私聊消息 - 群中自身发送 - other = "other" # 私聊消息 - 其他 - - group = "group" # 群聊消息 - - class Group: - normal = "normal" # 群聊消息 - 普通 - anonymous = "anonymous" # 群聊消息 - 匿名消息 - notice = "notice" # 群聊消息 - 系统提示 - - -class NoticeType: # 通知事件 - friend_recall = "friend_recall" # 私聊消息撤回 - group_recall = "group_recall" # 群聊消息撤回 - notify = "notify" - - class Notify: - poke = "poke" # 戳一戳 - - -class RealMessageType: # 实际消息分类 - text = "text" # 纯文本 - face = "face" # qq表情 - image = "image" # 图片 - record = "record" # 语音 - video = "video" # 视频 - at = "at" # @某人 - rps = "rps" # 猜拳魔法表情 - dice = "dice" # 骰子 - shake = "shake" # 私聊窗口抖动(只收) - poke = "poke" # 群聊戳一戳 - share = "share" # 链接分享(json形式) - reply = "reply" # 回复消息 - forward = "forward" # 转发消息 - node = "node" # 转发消息节点 - - -class MessageSentType: - private = "private" - - class Private: - friend = "friend" - group = "group" - - group = "group" - - class Group: - normal = "normal" - - -class CommandType(Enum): - """命令类型""" - - GROUP_BAN = "set_group_ban" # 禁言用户 - GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 - GROUP_KICK = "set_group_kick" # 踢出群聊 - - def __str__(self) -> str: - return self.value +from enum import Enum + + +class MetaEventType: + lifecycle = "lifecycle" # 生命周期 + + class Lifecycle: + connect = "connect" # 生命周期 - WebSocket 连接成功 + + heartbeat = "heartbeat" # 心跳 + + +class MessageType: # 接受消息大类 + private = "private" # 私聊消息 + + class Private: + friend = "friend" # 私聊消息 - 好友 + group = "group" # 私聊消息 - 群临时 + group_self = "group_self" # 私聊消息 - 群中自身发送 + other = "other" # 私聊消息 - 其他 + + group = "group" # 群聊消息 + + class Group: + normal = "normal" # 群聊消息 - 普通 + anonymous = "anonymous" # 群聊消息 - 匿名消息 + notice = "notice" # 群聊消息 - 系统提示 + + +class NoticeType: # 通知事件 + friend_recall = "friend_recall" # 私聊消息撤回 + group_recall = "group_recall" # 群聊消息撤回 + notify = "notify" + + class Notify: + poke = "poke" # 戳一戳 + + +class RealMessageType: # 实际消息分类 + text = "text" # 纯文本 + face = "face" # qq表情 + image = "image" # 图片 + record = "record" # 语音 + video = "video" # 视频 + at = "at" # @某人 + rps = "rps" # 猜拳魔法表情 + dice = "dice" # 骰子 + shake = "shake" # 私聊窗口抖动(只收) + poke = "poke" # 群聊戳一戳 + share = "share" # 链接分享(json形式) + reply = "reply" # 回复消息 + forward = "forward" # 转发消息 + node = "node" # 转发消息节点 + + +class MessageSentType: + private = "private" + + class Private: + friend = "friend" + group = "group" + + group = "group" + + class Group: + normal = "normal" + + +class CommandType(Enum): + """命令类型""" + + GROUP_BAN = "set_group_ban" # 禁言用户 + GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 + GROUP_KICK = "set_group_kick" # 踢出群聊 + SEND_POKE = "send_poke" # 戳一戳 + + def __str__(self) -> str: + return self.value diff --git a/src/send_handler.py b/src/send_handler.py index 74646b6..8aae094 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -1,313 +1,342 @@ -import json -import websockets as Server -import uuid -from maim_message import ( - UserInfo, - GroupInfo, - Seg, - BaseMessageInfo, - MessageBase, -) -from typing import Dict, Any, Tuple - -from . import CommandType -from .config import global_config -from .response_pool import get_response -from .logger import logger -from .utils import get_image_format, convert_image_to_gif - - -class SendHandler: - def __init__(self): - self.server_connection: Server.ServerConnection = None - - async def handle_message(self, raw_message_base_dict: dict) -> None: - raw_message_base: MessageBase = MessageBase.from_dict(raw_message_base_dict) - message_segment: Seg = raw_message_base.message_segment - logger.info("接收到来自MaiBot的消息,处理中") - if message_segment.type == "command": - return await self.send_command(raw_message_base) - else: - return await self.send_normal_message(raw_message_base) - - async def send_normal_message(self, raw_message_base: MessageBase) -> None: - """ - 处理普通消息发送 - """ - logger.info("处理普通信息中") - message_info: BaseMessageInfo = raw_message_base.message_info - message_segment: Seg = raw_message_base.message_segment - group_info: GroupInfo = message_info.group_info - user_info: UserInfo = message_info.user_info - target_id: int = None - action: str = None - id_name: str = None - processed_message: list = [] - try: - processed_message = await self.handle_seg_recursive(message_segment) - except Exception as e: - logger.error(f"处理消息时发生错误: {e}") - return - - if not processed_message: - logger.critical("现在暂时不支持解析此回复!") - return None - - if group_info and user_info: - logger.debug("发送群聊消息") - target_id = group_info.group_id - action = "send_group_msg" - id_name = "group_id" - elif user_info: - logger.debug("发送私聊消息") - target_id = user_info.user_id - action = "send_private_msg" - id_name = "user_id" - else: - logger.error("无法识别的消息类型") - return - logger.info("尝试发送到napcat") - response = await self.send_message_to_napcat( - action, - { - id_name: target_id, - "message": processed_message, - }, - ) - if response.get("status") == "ok": - logger.info("消息发送成功") - else: - logger.warning(f"消息发送失败,napcat返回:{str(response)}") - - async def send_command(self, raw_message_base: MessageBase) -> None: - """ - 处理命令类 - """ - logger.info("处理命令中") - message_info: BaseMessageInfo = raw_message_base.message_info - message_segment: Seg = raw_message_base.message_segment - group_info: GroupInfo = message_info.group_info - seg_data: Dict[str, Any] = message_segment.data - command_name: str = seg_data.get("name") - try: - match command_name: - case CommandType.GROUP_BAN.name: - command, args_dict = self.handle_ban_command(seg_data.get("args"), group_info) - case CommandType.GROUP_WHOLE_BAN.name: - command, args_dict = self.handle_whole_ban_command(seg_data.get("args"), group_info) - case CommandType.GROUP_KICK.name: - command, args_dict = self.handle_kick_command(seg_data.get("args"), group_info) - case _: - logger.error(f"未知命令: {command_name}") - return - except Exception as e: - logger.error(f"处理命令时发生错误: {e}") - return None - - if not command or not args_dict: - logger.error("命令或参数缺失") - return None - - response = await self.send_message_to_napcat(command, args_dict) - if response.get("status") == "ok": - logger.info(f"命令 {command_name} 执行成功") - else: - logger.warning(f"命令 {command_name} 执行失败,napcat返回:{str(response)}") - - def get_level(self, seg_data: Seg) -> int: - if seg_data.type == "seglist": - return 1 + max(self.get_level(seg) for seg in seg_data.data) - else: - return 1 - - async def handle_seg_recursive(self, seg_data: Seg) -> list: - payload: list = [] - if seg_data.type == "seglist": - # level = self.get_level(seg_data) # 给以后可能的多层嵌套做准备,此处不使用 - if not seg_data.data: - return [] - for seg in seg_data.data: - payload = self.process_message_by_type(seg, payload) - else: - payload = self.process_message_by_type(seg_data, payload) - return payload - - def process_message_by_type(self, seg: Seg, payload: list) -> list: - # sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression - new_payload = payload - if seg.type == "reply": - target_id = seg.data - if target_id == "notice": - return payload - new_payload = self.build_payload(payload, self.handle_reply_message(target_id), True) - elif seg.type == "text": - text = seg.data - if not text: - return payload - new_payload = self.build_payload(payload, self.handle_text_message(text), False) - elif seg.type == "face": - logger.warning("MaiBot 发送了qq原生表情,暂时不支持") - elif seg.type == "image": - image = seg.data - new_payload = self.build_payload(payload, self.handle_image_message(image), False) - elif seg.type == "emoji": - emoji = seg.data - new_payload = self.build_payload(payload, self.handle_emoji_message(emoji), False) - elif seg.type == "voice": - voice = seg.data - new_payload = self.build_payload(payload, self.handle_voice_message(voice), False) - return new_payload - - def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: - # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator - """构建发送的消息体""" - if is_reply: - temp_list = [] - temp_list.append(addon) - for i in payload: - if i.get("type") == "reply": - logger.debug("检测到多个回复,使用最新的回复") - continue - temp_list.append(i) - return temp_list - else: - payload.append(addon) - return payload - - def handle_reply_message(self, id: str) -> dict: - """处理回复消息""" - return {"type": "reply", "data": {"id": id}} - - def handle_text_message(self, message: str) -> dict: - """处理文本消息""" - return {"type": "text", "data": {"text": message}} - - def handle_image_message(self, encoded_image: str) -> dict: - """处理图片消息""" - return { - "type": "image", - "data": { - "file": f"base64://{encoded_image}", - "subtype": 0, - }, - } # base64 编码的图片 - - def handle_emoji_message(self, encoded_emoji: str) -> dict: - """处理表情消息""" - encoded_image = encoded_emoji - image_format = get_image_format(encoded_emoji) - if image_format != "gif": - encoded_image = convert_image_to_gif(encoded_emoji) - return { - "type": "image", - "data": { - "file": f"base64://{encoded_image}", - "subtype": 1, - "summary": "[动画表情]", - }, - } - - def handle_voice_message(self, encoded_voice: str) -> dict: - """处理语音消息""" - if not global_config.voice.use_tts: - logger.warning("未启用语音消息处理") - return {} - if not encoded_voice: - return {} - return { - "type": "record", - "data": {"file": f"base64://{encoded_voice}"}, - } - - def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理封禁命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - duration: int = int(args["duration"]) - user_id: int = int(args["qq_id"]) - group_id: int = int(group_info.group_id) - if duration <= 0: - raise ValueError("封禁时间必须大于0") - if not user_id or not group_id: - raise ValueError("封禁命令缺少必要参数") - if duration > 2592000: - raise ValueError("封禁时间不能超过30天") - return ( - CommandType.GROUP_BAN.value, - { - "group_id": group_id, - "user_id": user_id, - "duration": duration, - }, - ) - - def handle_whole_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理全体禁言命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - enable = args["enable"] - assert isinstance(enable, bool), "enable参数必须是布尔值" - group_id: int = int(group_info.group_id) - if group_id <= 0: - raise ValueError("群组ID无效") - return ( - CommandType.GROUP_WHOLE_BAN.value, - { - "group_id": group_id, - "enable": enable, - }, - ) - - def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理群成员踢出命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - user_id: int = int(args["qq_id"]) - group_id: int = int(group_info.group_id) - if group_id <= 0: - raise ValueError("群组ID无效") - if user_id <= 0: - raise ValueError("用户ID无效") - return ( - CommandType.GROUP_KICK.value, - { - "group_id": group_id, - "user_id": user_id, - "reject_add_request": False, # 不拒绝加群请求 - }, - ) - - async def send_message_to_napcat(self, action: str, params: dict) -> dict: - request_uuid = str(uuid.uuid4()) - payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) - await self.server_connection.send(payload) - try: - response = await get_response(request_uuid) - except TimeoutError: - logger.error("发送消息超时,未收到响应") - return {"status": "error", "message": "timeout"} - except Exception as e: - logger.error(f"发送消息失败: {e}") - return {"status": "error", "message": str(e)} - return response - - -send_handler = SendHandler() +import json +import websockets as Server +import uuid +from maim_message import ( + UserInfo, + GroupInfo, + Seg, + BaseMessageInfo, + MessageBase, +) +from typing import Dict, Any, Tuple + +from . import CommandType +from .config import global_config +from .response_pool import get_response +from .logger import logger +from .utils import get_image_format, convert_image_to_gif + + +class SendHandler: + def __init__(self): + self.server_connection: Server.ServerConnection = None + + async def handle_message(self, raw_message_base_dict: dict) -> None: + raw_message_base: MessageBase = MessageBase.from_dict(raw_message_base_dict) + message_segment: Seg = raw_message_base.message_segment + logger.info("接收到来自MaiBot的消息,处理中") + if message_segment.type == "command": + return await self.send_command(raw_message_base) + else: + return await self.send_normal_message(raw_message_base) + + async def send_normal_message(self, raw_message_base: MessageBase) -> None: + """ + 处理普通消息发送 + """ + logger.info("处理普通信息中") + message_info: BaseMessageInfo = raw_message_base.message_info + message_segment: Seg = raw_message_base.message_segment + group_info: GroupInfo = message_info.group_info + user_info: UserInfo = message_info.user_info + target_id: int = None + action: str = None + id_name: str = None + processed_message: list = [] + try: + processed_message = await self.handle_seg_recursive(message_segment) + except Exception as e: + logger.error(f"处理消息时发生错误: {e}") + return + + if not processed_message: + logger.critical("现在暂时不支持解析此回复!") + return None + + if group_info and user_info: + logger.debug("发送群聊消息") + target_id = group_info.group_id + action = "send_group_msg" + id_name = "group_id" + elif user_info: + logger.debug("发送私聊消息") + target_id = user_info.user_id + action = "send_private_msg" + id_name = "user_id" + else: + logger.error("无法识别的消息类型") + return + logger.info("尝试发送到napcat") + response = await self.send_message_to_napcat( + action, + { + id_name: target_id, + "message": processed_message, + }, + ) + if response.get("status") == "ok": + logger.info("消息发送成功") + else: + logger.warning(f"消息发送失败,napcat返回:{str(response)}") + + async def send_command(self, raw_message_base: MessageBase) -> None: + """ + 处理命令类 + """ + logger.info("处理命令中") + message_info: BaseMessageInfo = raw_message_base.message_info + message_segment: Seg = raw_message_base.message_segment + group_info: GroupInfo = message_info.group_info + seg_data: Dict[str, Any] = message_segment.data + command_name: str = seg_data.get("name") + try: + match command_name: + case CommandType.GROUP_BAN.name: + command, args_dict = self.handle_ban_command(seg_data.get("args"), group_info) + case CommandType.GROUP_WHOLE_BAN.name: + command, args_dict = self.handle_whole_ban_command(seg_data.get("args"), group_info) + case CommandType.GROUP_KICK.name: + command, args_dict = self.handle_kick_command(seg_data.get("args"), group_info) + case CommandType.SEND_POKE.name: + command, args_dict = self.handle_poke_command(seg_data.get("args"), group_info) + case _: + logger.error(f"未知命令: {command_name}") + return + except Exception as e: + logger.error(f"处理命令时发生错误: {e}") + return None + + if not command or not args_dict: + logger.error("命令或参数缺失") + return None + + response = await self.send_message_to_napcat(command, args_dict) + if response.get("status") == "ok": + logger.info(f"命令 {command_name} 执行成功") + else: + logger.warning(f"命令 {command_name} 执行失败,napcat返回:{str(response)}") + + def get_level(self, seg_data: Seg) -> int: + if seg_data.type == "seglist": + return 1 + max(self.get_level(seg) for seg in seg_data.data) + else: + return 1 + + async def handle_seg_recursive(self, seg_data: Seg) -> list: + payload: list = [] + if seg_data.type == "seglist": + # level = self.get_level(seg_data) # 给以后可能的多层嵌套做准备,此处不使用 + if not seg_data.data: + return [] + for seg in seg_data.data: + payload = self.process_message_by_type(seg, payload) + else: + payload = self.process_message_by_type(seg_data, payload) + return payload + + def process_message_by_type(self, seg: Seg, payload: list) -> list: + # sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression + new_payload = payload + if seg.type == "reply": + target_id = seg.data + if target_id == "notice": + return payload + new_payload = self.build_payload(payload, self.handle_reply_message(target_id), True) + elif seg.type == "text": + text = seg.data + if not text: + return payload + new_payload = self.build_payload(payload, self.handle_text_message(text), False) + elif seg.type == "face": + logger.warning("MaiBot 发送了qq原生表情,暂时不支持") + elif seg.type == "image": + image = seg.data + new_payload = self.build_payload(payload, self.handle_image_message(image), False) + elif seg.type == "emoji": + emoji = seg.data + new_payload = self.build_payload(payload, self.handle_emoji_message(emoji), False) + elif seg.type == "voice": + voice = seg.data + new_payload = self.build_payload(payload, self.handle_voice_message(voice), False) + return new_payload + + def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: + # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator + """构建发送的消息体""" + if is_reply: + temp_list = [] + temp_list.append(addon) + for i in payload: + if i.get("type") == "reply": + logger.debug("检测到多个回复,使用最新的回复") + continue + temp_list.append(i) + return temp_list + else: + payload.append(addon) + return payload + + def handle_reply_message(self, id: str) -> dict: + """处理回复消息""" + return {"type": "reply", "data": {"id": id}} + + def handle_text_message(self, message: str) -> dict: + """处理文本消息""" + return {"type": "text", "data": {"text": message}} + + def handle_image_message(self, encoded_image: str) -> dict: + """处理图片消息""" + return { + "type": "image", + "data": { + "file": f"base64://{encoded_image}", + "subtype": 0, + }, + } # base64 编码的图片 + + def handle_emoji_message(self, encoded_emoji: str) -> dict: + """处理表情消息""" + encoded_image = encoded_emoji + image_format = get_image_format(encoded_emoji) + if image_format != "gif": + encoded_image = convert_image_to_gif(encoded_emoji) + return { + "type": "image", + "data": { + "file": f"base64://{encoded_image}", + "subtype": 1, + "summary": "[动画表情]", + }, + } + + def handle_voice_message(self, encoded_voice: str) -> dict: + """处理语音消息""" + if not global_config.voice.use_tts: + logger.warning("未启用语音消息处理") + return {} + if not encoded_voice: + return {} + return { + "type": "record", + "data": {"file": f"base64://{encoded_voice}"}, + } + + def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理封禁命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + duration: int = int(args["duration"]) + user_id: int = int(args["qq_id"]) + group_id: int = int(group_info.group_id) + if duration <= 0: + raise ValueError("封禁时间必须大于0") + if not user_id or not group_id: + raise ValueError("封禁命令缺少必要参数") + if duration > 2592000: + raise ValueError("封禁时间不能超过30天") + return ( + CommandType.GROUP_BAN.value, + { + "group_id": group_id, + "user_id": user_id, + "duration": duration, + }, + ) + + def handle_whole_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理全体禁言命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + enable = args["enable"] + assert isinstance(enable, bool), "enable参数必须是布尔值" + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + return ( + CommandType.GROUP_WHOLE_BAN.value, + { + "group_id": group_id, + "enable": enable, + }, + ) + + def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理群成员踢出命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + user_id: int = int(args["qq_id"]) + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + if user_id <= 0: + raise ValueError("用户ID无效") + return ( + CommandType.GROUP_KICK.value, + { + "group_id": group_id, + "user_id": user_id, + "reject_add_request": False, # 不拒绝加群请求 + }, + ) + + def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理戳一戳命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + user_id: int = int(args["qq_id"]) + if group_info == None: + group_id = None + else: + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + if user_id <= 0: + raise ValueError("用户ID无效") + return ( + CommandType.SEND_POKE.value, + { + "group_id": group_id, + "user_id": user_id, + }, + ) + + async def send_message_to_napcat(self, action: str, params: dict) -> dict: + request_uuid = str(uuid.uuid4()) + payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) + await self.server_connection.send(payload) + try: + response = await get_response(request_uuid) + except TimeoutError: + logger.error("发送消息超时,未收到响应") + return {"status": "error", "message": "timeout"} + except Exception as e: + logger.error(f"发送消息失败: {e}") + return {"status": "error", "message": str(e)} + return response + + +send_handler = SendHandler() From 9ef9dff4a9a223bc744318fbbe31a22f6270d7cf Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 22 Jun 2025 09:49:23 +0800 Subject: [PATCH 018/112] ruff --- src/__init__.py | 4 ++-- src/send_handler.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index e12aede..bfae081 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -72,7 +72,7 @@ class CommandType(Enum): GROUP_BAN = "set_group_ban" # 禁言用户 GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 GROUP_KICK = "set_group_kick" # 踢出群聊 - SEND_POKE = "send_poke" # 戳一戳 - + SEND_POKE = "send_poke" # 戳一戳 + def __str__(self) -> str: return self.value diff --git a/src/send_handler.py b/src/send_handler.py index 8aae094..9085fed 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -296,7 +296,7 @@ def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu "reject_add_request": False, # 不拒绝加群请求 }, ) - + def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: """处理戳一戳命令 @@ -310,7 +310,7 @@ def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu user_id: int = int(args["qq_id"]) if group_info == None: group_id = None - else: + else: group_id: int = int(group_info.group_id) if group_id <= 0: raise ValueError("群组ID无效") From 3711b2892de6dcc97aefe50ca826250ba6483482 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 22 Jun 2025 10:03:07 +0800 Subject: [PATCH 019/112] command update --- command_args.md | 18 ++++++++++++++---- src/send_handler.py | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/command_args.md b/command_args.md index 8a73651..cbbb582 100644 --- a/command_args.md +++ b/command_args.md @@ -5,7 +5,7 @@ Seg.type = "command" ## 群聊禁言 ```python Seg.data: Dict[str, Any] = { - "name": "GROUP_BAN" + "name": "GROUP_BAN", "args": { "qq_id": "用户QQ号", "duration": "禁言时长(秒)" @@ -16,7 +16,7 @@ Seg.data: Dict[str, Any] = { ## 群聊全体禁言 ```python Seg.data: Dict[str, Any] = { - "name": "GROUP_WHOLE_BAN" + "name": "GROUP_WHOLE_BAN", "args": { "enable": "是否开启全体禁言(True/False)" }, @@ -28,10 +28,20 @@ Seg.data: Dict[str, Any] = { ## 群聊踢人 ```python Seg.data: Dict[str, Any] = { - "name": "GROUP_KICK" + "name": "GROUP_KICK", "args": { "qq_id": "用户QQ号", }, } ``` -其中,群聊ID将会通过Group_Info.group_id自动获取。 \ No newline at end of file +其中,群聊ID将会通过Group_Info.group_id自动获取。 + +## 戳一戳 +```python +Seg,.data: Dict[str, Any] = { + "name": "SEND_POKE", + "args": { + "qq_id": "目标QQ号" + } +} +``` \ No newline at end of file diff --git a/src/send_handler.py b/src/send_handler.py index 9085fed..37788b8 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -308,7 +308,7 @@ def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu Tuple[CommandType, Dict[str, Any]] """ user_id: int = int(args["qq_id"]) - if group_info == None: + if group_info is None: group_id = None else: group_id: int = int(group_info.group_id) From 29111bd9215146cd5c3779912f7f2ca939b21cda Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 22 Jun 2025 10:17:28 +0800 Subject: [PATCH 020/112] doc update --- command_args.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command_args.md b/command_args.md index cbbb582..8dff207 100644 --- a/command_args.md +++ b/command_args.md @@ -38,7 +38,7 @@ Seg.data: Dict[str, Any] = { ## 戳一戳 ```python -Seg,.data: Dict[str, Any] = { +Seg.data: Dict[str, Any] = { "name": "SEND_POKE", "args": { "qq_id": "目标QQ号" From e757196fe1f07015f009423d533edb27cdc70c1f Mon Sep 17 00:00:00 2001 From: 1334431750 <1334431750@qq.com> Date: Sun, 22 Jun 2025 02:22:50 +0000 Subject: [PATCH 021/112] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index b763d96..2a6709e 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -97,6 +97,8 @@ async def send_command(self, raw_message_base: MessageBase) -> None: command, args_dict = self.handle_whole_ban_command(seg_data.get("args"), group_info) case CommandType.GROUP_KICK.name: command, args_dict = self.handle_kick_command(seg_data.get("args"), group_info) + case CommandType.SEND_POKE.name: + command, args_dict = self.handle_poke_command(seg_data.get("args"), group_info) case _: logger.error(f"未知命令: {command_name}") return @@ -215,7 +217,7 @@ def handle_emoji_message(self, encoded_emoji: str) -> dict: def handle_voice_message(self, encoded_voice: str) -> dict: """处理语音消息""" - if not global_config.use_tts: + if not global_config.voice.use_tts: logger.warning("未启用语音消息处理") return {} if not encoded_voice: @@ -236,10 +238,7 @@ def handle_music_message(self, song_id: str) -> dict: """处理音乐消息""" return { "type": "music", - "data": { - "type": "163", - "id": song_id - }, + "data": {"type": "163", "id": song_id}, } def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: @@ -318,6 +317,33 @@ def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu }, ) + def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理戳一戳命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + user_id: int = int(args["qq_id"]) + if group_info is None: + group_id = None + else: + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + if user_id <= 0: + raise ValueError("用户ID无效") + return ( + CommandType.SEND_POKE.value, + { + "group_id": group_id, + "user_id": user_id, + }, + ) + async def send_message_to_napcat(self, action: str, params: dict) -> dict: request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) From a76e8b8ef4e986023e0ccc455e8e5b43fdcfcabb Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 22 Jun 2025 11:05:09 +0800 Subject: [PATCH 022/112] =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E9=98=B2=E6=AD=A2=E5=9C=A8=E6=97=A0GUI?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E4=B8=AD=E5=87=BA=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 3 +-- src/recv_handler.py | 11 ----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index e5964ec..41e8eb1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,4 @@ requests maim_message loguru pillow -tomli -plyer \ No newline at end of file +tomli \ No newline at end of file diff --git a/src/recv_handler.py b/src/recv_handler.py index 7b11fbd..d07cb78 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -7,8 +7,6 @@ import websockets as Server from typing import List, Tuple, Optional, Dict, Any import uuid -from plyer import notification -import os from . import MetaEventType, RealMessageType, MessageType, NoticeType from maim_message import ( @@ -66,15 +64,6 @@ async def check_heartbeat(self, id: int) -> None: now_time = time.time() if now_time - self.last_heart_beat > self.interval * 2: logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") - current_dir = os.path.dirname(__file__) - icon_path = os.path.join(current_dir, "..", "assets", "maimai.ico") - notification.notify( - title="警告", - message=f"Bot {id} 连接已断开,被下线,或者Napcat卡死!", - app_name="MaiBot Napcat Adapter", - timeout=10, - app_icon=icon_path, - ) break else: logger.debug("心跳正常") From 409f9f6b0718ac0d4ffb5bda2e308254df22e108 Mon Sep 17 00:00:00 2001 From: A0000Xz <122650088+A0000Xz@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:02:20 +0800 Subject: [PATCH 023/112] =?UTF-8?q?=E4=BD=BF=E5=85=B6=E4=BB=96=E4=BA=BA?= =?UTF-8?q?=E7=9A=84=E6=88=B3=E4=B8=80=E6=88=B3=E8=83=BD=E5=A4=9F=E8=A2=AB?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=EF=BC=8C=E5=B9=B6=E5=9C=A8addtional=5Fconfig?= =?UTF-8?q?=E9=99=84=E5=8A=A0=E8=A2=AB=E6=88=B3=E4=BA=BA=E7=9A=84ID?= =?UTF-8?q?=E4=BB=A5=E6=96=B9=E4=BE=BF=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler.py | 1580 ++++++++++++++++++++++--------------------- 1 file changed, 795 insertions(+), 785 deletions(-) diff --git a/src/recv_handler.py b/src/recv_handler.py index d07cb78..46b9bd7 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -1,785 +1,795 @@ -from .logger import logger -from .config import global_config -from .qq_emoji_list import qq_face -import time -import asyncio -import json -import websockets as Server -from typing import List, Tuple, Optional, Dict, Any -import uuid - -from . import MetaEventType, RealMessageType, MessageType, NoticeType -from maim_message import ( - UserInfo, - GroupInfo, - Seg, - BaseMessageInfo, - MessageBase, - TemplateInfo, - FormatInfo, - Router, -) - -from .utils import ( - get_group_info, - get_member_info, - get_image_base64, - get_self_info, - get_stranger_info, - get_message_detail, -) -from .response_pool import get_response - - -class RecvHandler: - maibot_router: Router = None - - def __init__(self): - self.server_connection: Server.ServerConnection = None - self.interval = global_config.napcat_server.heartbeat_interval - self._interval_checking = False - - async def handle_meta_event(self, message: dict) -> None: - event_type = message.get("meta_event_type") - if event_type == MetaEventType.lifecycle: - sub_type = message.get("sub_type") - if sub_type == MetaEventType.Lifecycle.connect: - self_id = message.get("self_id") - self.last_heart_beat = time.time() - logger.info(f"Bot {self_id} 连接成功") - asyncio.create_task(self.check_heartbeat(self_id)) - elif event_type == MetaEventType.heartbeat: - if message["status"].get("online") and message["status"].get("good"): - if not self._interval_checking: - asyncio.create_task(self.check_heartbeat()) - self.last_heart_beat = time.time() - self.interval = message.get("interval") / 1000 - else: - self_id = message.get("self_id") - logger.warning(f"Bot {self_id} Napcat 端异常!") - - async def check_heartbeat(self, id: int) -> None: - self._interval_checking = True - while True: - now_time = time.time() - if now_time - self.last_heart_beat > self.interval * 2: - logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") - break - else: - logger.debug("心跳正常") - await asyncio.sleep(self.interval) - - def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: - # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif - """ - 检查是否允许聊天 - Parameters: - user_id: int: 用户ID - group_id: int: 群ID - Returns: - bool: 是否允许聊天 - """ - logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") - if group_id: - if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: - logger.warning("群聊不在聊天白名单中,消息被丢弃") - return False - elif global_config.chat.group_list_type == "blacklist" and group_id in global_config.chat.group_list: - logger.warning("群聊在聊天黑名单中,消息被丢弃") - return False - else: - if global_config.chat.private_list_type == "whitelist" and user_id not in global_config.chat.private_list: - logger.warning("私聊不在聊天白名单中,消息被丢弃") - return False - elif global_config.chat.private_list_type == "blacklist" and user_id in global_config.chat.private_list: - logger.warning("私聊在聊天黑名单中,消息被丢弃") - return False - if user_id in global_config.chat.ban_user_id: - logger.warning("用户在全局黑名单中,消息被丢弃") - return False - return True - - async def handle_raw_message(self, raw_message: dict) -> None: - # sourcery skip: low-code-quality, remove-unreachable-code - """ - 从Napcat接受的原始消息处理 - - Parameters: - raw_message: dict: 原始消息 - """ - message_type: str = raw_message.get("message_type") - message_id: int = raw_message.get("message_id") - # message_time: int = raw_message.get("time") - message_time: float = time.time() # 应可乐要求,现在是float了 - - template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 - format_info: FormatInfo = FormatInfo( - content_format=["text", "image", "emoji"], - accept_format=["text", "image", "emoji", "reply", "voice", "command"], - ) # 格式化信息 - if message_type == MessageType.private: - sub_type = raw_message.get("sub_type") - if sub_type == MessageType.Private.friend: - sender_info: dict = raw_message.get("sender") - - if not self.check_allow_to_chat(sender_info.get("user_id"), None): - return None - - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=sender_info.get("nickname"), - user_cardname=sender_info.get("card"), - ) - - # 不存在群信息 - group_info: GroupInfo = None - elif sub_type == MessageType.Private.group: - """ - 本部分暂时不做支持,先放着 - """ - logger.warning("群临时消息类型不支持") - return None - - sender_info: dict = raw_message.get("sender") - - # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取 - fetched_member_info: dict = await get_member_info( - self.server_connection, - raw_message.get("group_id"), - sender_info.get("user_id"), - ) - nickname = fetched_member_info.get("nickname") if fetched_member_info else None - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=nickname, - user_cardname=None, - ) - - # -------------------这里需要群信息吗?------------------- - - # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 - fetched_group_info: dict = await get_group_info(self.server_connection, raw_message.get("group_id")) - group_name = "" - if fetched_group_info.get("group_name"): - group_name = fetched_group_info.get("group_name") - - group_info: GroupInfo = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=raw_message.get("group_id"), - group_name=group_name, - ) - - else: - logger.warning(f"私聊消息类型 {sub_type} 不支持") - return None - elif message_type == MessageType.group: - sub_type = raw_message.get("sub_type") - if sub_type == MessageType.Group.normal: - sender_info: dict = raw_message.get("sender") - - if not self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")): - return None - - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=sender_info.get("nickname"), - user_cardname=sender_info.get("card"), - ) - - # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 - fetched_group_info = await get_group_info(self.server_connection, raw_message.get("group_id")) - group_name: str = None - if fetched_group_info: - group_name = fetched_group_info.get("group_name") - - group_info: GroupInfo = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=raw_message.get("group_id"), - group_name=group_name, - ) - - else: - logger.warning(f"群聊消息类型 {sub_type} 不支持") - return None - - additional_config: dict = {} - if global_config.voice.use_tts: - additional_config["allow_tts"] = True - - # 消息信息 - message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.maibot_server.platform_name, - message_id=message_id, - time=message_time, - user_info=user_info, - group_info=group_info, - template_info=template_info, - format_info=format_info, - additional_config=additional_config, - ) - - # 处理实际信息 - if not raw_message.get("message"): - logger.warning("原始消息内容为空") - return None - - # 获取Seg列表 - seg_message: List[Seg] = await self.handle_real_message(raw_message) - if not seg_message: - logger.warning("处理后消息内容为空") - return None - submit_seg: Seg = Seg( - type="seglist", - data=seg_message, - ) - # MessageBase创建 - message_base: MessageBase = MessageBase( - message_info=message_info, - message_segment=submit_seg, - raw_message=raw_message.get("raw_message"), - ) - - logger.info("发送到Maibot处理信息") - await self.message_process(message_base) - - async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None: - # sourcery skip: low-code-quality - """ - 处理实际消息 - Parameters: - real_message: dict: 实际消息 - Returns: - seg_message: list[Seg]: 处理后的消息段列表 - """ - real_message: list = raw_message.get("message") - if not real_message: - return None - seg_message: List[Seg] = [] - for sub_message in real_message: - sub_message: dict - sub_message_type = sub_message.get("type") - match sub_message_type: - case RealMessageType.text: - ret_seg = await self.handle_text_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("text处理失败") - case RealMessageType.face: - ret_seg = await self.handle_face_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("face处理失败或不支持") - case RealMessageType.reply: - if not in_reply: - ret_seg = await self.handle_reply_message(sub_message) - if ret_seg: - seg_message += ret_seg - else: - logger.warning("reply处理失败") - case RealMessageType.image: - ret_seg = await self.handle_image_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("image处理失败") - case RealMessageType.record: - logger.warning("不支持语音解析") - case RealMessageType.video: - logger.warning("不支持视频解析") - case RealMessageType.at: - ret_seg = await self.handle_at_message( - sub_message, - raw_message.get("self_id"), - raw_message.get("group_id"), - ) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("at处理失败") - case RealMessageType.rps: - logger.warning("暂时不支持猜拳魔法表情解析") - case RealMessageType.dice: - logger.warning("暂时不支持骰子表情解析") - case RealMessageType.shake: - # 预计等价于戳一戳 - logger.warning("暂时不支持窗口抖动解析") - case RealMessageType.share: - logger.warning("暂时不支持链接解析") - case RealMessageType.forward: - messages = await self.get_forward_message(sub_message) - if not messages: - logger.warning("转发消息内容为空或获取失败") - return None - ret_seg = await self.handle_forward_message(messages) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("转发消息处理失败") - case RealMessageType.node: - logger.warning("不支持转发消息节点解析") - case _: - logger.warning(f"未知消息类型: {sub_message_type}") - return seg_message - - async def handle_text_message(self, raw_message: dict) -> Seg: - """ - 处理纯文本信息 - Parameters: - raw_message: dict: 原始消息 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - plain_text: str = message_data.get("text") - return Seg(type="text", data=plain_text) - - async def handle_face_message(self, raw_message: dict) -> Seg | None: - """ - 处理表情消息 - Parameters: - raw_message: dict: 原始消息 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - face_raw_id: str = str(message_data.get("id")) - if face_raw_id in qq_face: - face_content: str = qq_face.get(face_raw_id) - return Seg(type="text", data=face_content) - else: - logger.warning(f"不支持的表情:{face_raw_id}") - return None - - async def handle_image_message(self, raw_message: dict) -> Seg | None: - """ - 处理图片消息与表情包消息 - Parameters: - raw_message: dict: 原始消息 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - image_sub_type = message_data.get("sub_type") - try: - image_base64 = await get_image_base64(message_data.get("url")) - except Exception as e: - logger.error(f"图片消息处理失败: {str(e)}") - return None - if image_sub_type == 0: - """这部分认为是图片""" - return Seg(type="image", data=image_base64) - elif image_sub_type == 1: - """这部分认为是表情包""" - return Seg(type="emoji", data=image_base64) - else: - logger.warning(f"不支持的图片子类型:{image_sub_type}") - return None - - async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int) -> Seg | None: - # sourcery skip: use-named-expression - """ - 处理at消息 - Parameters: - raw_message: dict: 原始消息 - self_id: int: 机器人QQ号 - group_id: int: 群号 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - if message_data: - qq_id = message_data.get("qq") - if str(self_id) == str(qq_id): - logger.debug("机器人被at") - self_info: dict = await get_self_info(self.server_connection) - if self_info: - return Seg(type="text", data=f"@<{self_info.get('nickname')}:{self_info.get('user_id')}>") - else: - return None - else: - member_info: dict = await get_member_info(self.server_connection, group_id=group_id, user_id=qq_id) - if member_info: - return Seg(type="text", data=f"@<{member_info.get('nickname')}:{member_info.get('user_id')}>") - else: - return None - - async def get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: - forward_message_data: Dict = raw_message.get("data") - if not forward_message_data: - logger.warning("转发消息内容为空") - return None - forward_message_id = forward_message_data.get("id") - request_uuid = str(uuid.uuid4()) - payload = json.dumps( - { - "action": "get_forward_msg", - "params": {"message_id": forward_message_id}, - "echo": request_uuid, - } - ) - try: - await self.server_connection.send(payload) - response: dict = await get_response(request_uuid) - except TimeoutError: - logger.error("获取转发消息超时") - return None - except Exception as e: - logger.error(f"获取转发消息失败: {str(e)}") - return None - logger.debug( - f"转发消息原始格式:{json.dumps(response)[:80]}..." - if len(json.dumps(response)) > 80 - else json.dumps(response) - ) - response_data: Dict = response.get("data") - if not response_data: - logger.warning("转发消息内容为空或获取失败") - return None - return response_data.get("messages") - - async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: - # sourcery skip: move-assign-in-block, use-named-expression - """ - 处理回复消息 - - """ - raw_message_data: dict = raw_message.get("data") - message_id: int = None - if raw_message_data: - message_id = raw_message_data.get("id") - else: - return None - message_detail: dict = await get_message_detail(self.server_connection, message_id) - if not message_detail: - logger.warning("获取被引用的消息详情失败") - return None - reply_message = await self.handle_real_message(message_detail, in_reply=True) - if reply_message is None: - reply_message = "(获取发言内容失败)" - sender_info: dict = message_detail.get("sender") - sender_nickname: str = sender_info.get("nickname") - sender_id: str = sender_info.get("user_id") - seg_message: List[Seg] = [] - if not sender_nickname: - logger.warning("无法获取被引用的人的昵称,返回默认值") - seg_message.append(Seg(type="text", data="[回复 未知用户:")) - else: - seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}:{sender_id}>:")) - seg_message += reply_message - seg_message.append(Seg(type="text", data="],说:")) - return seg_message - - async def handle_notice(self, raw_message: dict) -> None: - notice_type = raw_message.get("notice_type") - # message_time: int = raw_message.get("time") - message_time: float = time.time() # 应可乐要求,现在是float了 - - group_id = raw_message.get("group_id") - user_id = raw_message.get("user_id") - - if not self.check_allow_to_chat(user_id, group_id): - logger.warning("notice消息被丢弃") - return None - - handled_message: Seg = None - - match notice_type: - case NoticeType.friend_recall: - logger.info("好友撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") - case NoticeType.group_recall: - logger.info("群内用户撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") - case NoticeType.notify: - sub_type = raw_message.get("sub_type") - match sub_type: - case NoticeType.Notify.poke: - if global_config.chat.enable_poke: - handled_message: Seg = await self.handle_poke_notify(raw_message) - else: - logger.warning("戳一戳消息被禁用,取消戳一戳处理") - case _: - logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") - case _: - logger.warning(f"不支持的notice类型: {notice_type}") - return None - if not handled_message: - logger.warning("notice处理失败或不支持") - return None - - source_name: str = None - source_cardname: str = None - if group_id: - member_info: dict = await get_member_info(self.server_connection, group_id, user_id) - if member_info: - source_name = member_info.get("nickname") - source_cardname = member_info.get("card") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - else: - stranger_info = await get_stranger_info(self.server_connection, user_id) - if stranger_info: - source_name = stranger_info.get("nickname") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=user_id, - user_nickname=source_name, - user_cardname=source_cardname, - ) - - group_info: GroupInfo = None - if group_id: - fetched_group_info = await get_group_info(self.server_connection, group_id) - group_name: str = None - if fetched_group_info: - group_name = fetched_group_info.get("group_name") - else: - logger.warning("无法获取戳一戳消息所在群的名称") - group_info = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=group_id, - group_name=group_name, - ) - - message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.maibot_server.platform_name, - message_id="notice", - time=message_time, - user_info=user_info, - group_info=group_info, - template_info=None, - format_info=None, - ) - - message_base: MessageBase = MessageBase( - message_info=message_info, - message_segment=handled_message, - raw_message=json.dumps(raw_message), - ) - - logger.info("发送到Maibot处理通知信息") - await self.message_process(message_base) - - async def handle_poke_notify(self, raw_message: dict) -> Seg | None: - self_info: dict = await get_self_info(self.server_connection) - if not self_info: - logger.error("自身信息获取失败") - return None - self_id = raw_message.get("self_id") - target_id = raw_message.get("target_id") - target_name: str = None - raw_info: list = raw_message.get("raw_info") - # 计算Seg - if self_id == target_id: - target_name = self_info.get("nickname") - else: - return None - try: - first_txt = raw_info[2].get("txt", "戳了戳") - second_txt = raw_info[4].get("txt", "") - except Exception as e: - logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") - first_txt = "戳了戳" - second_txt = "" - """ - # 不启用戳其他人的处理 - else: - # 由于Napcat不支持获取昵称,所以需要单独获取 - group_id = raw_message.get("group_id") - fetched_member_info: dict = await get_member_info( - self.server_connection, group_id, target_id - ) - if fetched_member_info: - target_name = fetched_member_info.get("nickname") - """ - seg_data: Seg = Seg( - type="text", - data=f"{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", - ) - return seg_data - - async def handle_forward_message(self, message_list: list) -> Seg | None: - """ - 递归处理转发消息,并按照动态方式确定图片处理方式 - Parameters: - message_list: list: 转发消息列表 - """ - handled_message, image_count = await self._handle_forward_message(message_list, 0) - handled_message: Seg - image_count: int - if not handled_message: - return None - if image_count < 5 and image_count > 0: - # 处理图片数量小于5的情况,此时解析图片为base64 - logger.trace("图片数量小于5,开始解析图片为base64") - return await self._recursive_parse_image_seg(handled_message, True) - elif image_count > 0: - logger.trace("图片数量大于等于5,开始解析图片为占位符") - # 处理图片数量大于等于5的情况,此时解析图片为占位符 - return await self._recursive_parse_image_seg(handled_message, False) - else: - # 处理没有图片的情况,此时直接返回 - logger.trace("没有图片,直接返回") - return handled_message - - async def _recursive_parse_image_seg(self, seg_data: Seg, to_image: bool) -> Seg: - # sourcery skip: merge-else-if-into-elif - if to_image: - if seg_data.type == "seglist": - new_seg_list = [] - for i_seg in seg_data.data: - parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) - new_seg_list.append(parsed_seg) - return Seg(type="seglist", data=new_seg_list) - elif seg_data.type == "image": - image_url = seg_data.data - try: - encoded_image = await get_image_base64(image_url) - except Exception as e: - logger.error(f"图片处理失败: {str(e)}") - return Seg(type="text", data="[图片]") - return Seg(type="image", data=encoded_image) - elif seg_data.type == "emoji": - image_url = seg_data.data - try: - encoded_image = await get_image_base64(image_url) - except Exception as e: - logger.error(f"图片处理失败: {str(e)}") - return Seg(type="text", data="[表情包]") - return Seg(type="emoji", data=encoded_image) - else: - logger.trace(f"不处理类型: {seg_data.type}") - return seg_data - else: - if seg_data.type == "seglist": - new_seg_list = [] - for i_seg in seg_data.data: - parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) - new_seg_list.append(parsed_seg) - return Seg(type="seglist", data=new_seg_list) - elif seg_data.type == "image": - return Seg(type="text", data="[图片]") - elif seg_data.type == "emoji": - return Seg(type="text", data="[动画表情]") - else: - logger.trace(f"不处理类型: {seg_data.type}") - return seg_data - - async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[Seg, int] | Tuple[None, int]: - # sourcery skip: low-code-quality - """ - 递归处理实际转发消息 - Parameters: - message_list: list: 转发消息列表,首层对应messages字段,后面对应content字段 - layer: int: 当前层级 - Returns: - seg_data: Seg: 处理后的消息段 - image_count: int: 图片数量 - """ - seg_list: List[Seg] = [] - image_count = 0 - if message_list is None: - return None, 0 - for sub_message in message_list: - sub_message: dict - sender_info: dict = sub_message.get("sender") - user_nickname: str = sender_info.get("nickname", "QQ用户") - user_nickname_str = f"【{user_nickname}】:" - break_seg = Seg(type="text", data="\n") - message_of_sub_message_list: List[Dict[str, Any]] = sub_message.get("message") - if not message_of_sub_message_list: - logger.warning("转发消息内容为空") - continue - message_of_sub_message = message_of_sub_message_list[0] - if message_of_sub_message.get("type") == RealMessageType.forward: - if layer >= 3: - full_seg_data = Seg( - type="text", - data=("--" * layer) + f"【{user_nickname}】:【转发消息】\n", - ) - else: - sub_message_data = message_of_sub_message.get("data") - if not sub_message_data: - continue - contents = sub_message_data.get("content") - seg_data, count = await self._handle_forward_message(contents, layer + 1) - image_count += count - head_tip = Seg( - type="text", - data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n", - ) - full_seg_data = Seg(type="seglist", data=[head_tip, seg_data]) - seg_list.append(full_seg_data) - elif message_of_sub_message.get("type") == RealMessageType.text: - sub_message_data = message_of_sub_message.get("data") - if not sub_message_data: - continue - text_message = sub_message_data.get("text") - seg_data = Seg(type="text", data=text_message) - data_list: List[Any] = [] - if layer > 0: - data_list = [ - Seg(type="text", data=("--" * layer) + user_nickname_str), - seg_data, - break_seg, - ] - else: - data_list = [ - Seg(type="text", data=user_nickname_str), - seg_data, - break_seg, - ] - seg_list.append(Seg(type="seglist", data=data_list)) - elif message_of_sub_message.get("type") == RealMessageType.image: - image_count += 1 - image_data = message_of_sub_message.get("data") - sub_type = image_data.get("sub_type") - image_url = image_data.get("url") - data_list: List[Any] = [] - if sub_type == 0: - seg_data = Seg(type="image", data=image_url) - else: - seg_data = Seg(type="emoji", data=image_url) - if layer > 0: - data_list = [ - Seg(type="text", data=("--" * layer) + user_nickname_str), - seg_data, - break_seg, - ] - else: - data_list = [ - Seg(type="text", data=user_nickname_str), - seg_data, - break_seg, - ] - full_seg_data = Seg(type="seglist", data=data_list) - seg_list.append(full_seg_data) - return Seg(type="seglist", data=seg_list), image_count - - async def message_process(self, message_base: MessageBase) -> None: - try: - send_status = await self.maibot_router.send_message(message_base) - if not send_status: - raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") - except Exception as e: - logger.error(f"发送消息失败: {str(e)}") - logger.error("请检查与MaiBot之间的连接") - return None - - -recv_handler = RecvHandler() +from .logger import logger +from .config import global_config +from .qq_emoji_list import qq_face +import time +import asyncio +import json +import websockets as Server +from typing import List, Tuple, Optional, Dict, Any +import uuid + +from . import MetaEventType, RealMessageType, MessageType, NoticeType +from maim_message import ( + UserInfo, + GroupInfo, + Seg, + BaseMessageInfo, + MessageBase, + TemplateInfo, + FormatInfo, + Router, +) + +from .utils import ( + get_group_info, + get_member_info, + get_image_base64, + get_self_info, + get_stranger_info, + get_message_detail, +) +from .response_pool import get_response + + +class RecvHandler: + maibot_router: Router = None + + def __init__(self): + self.server_connection: Server.ServerConnection = None + self.interval = global_config.napcat_server.heartbeat_interval + self._interval_checking = False + + async def handle_meta_event(self, message: dict) -> None: + event_type = message.get("meta_event_type") + if event_type == MetaEventType.lifecycle: + sub_type = message.get("sub_type") + if sub_type == MetaEventType.Lifecycle.connect: + self_id = message.get("self_id") + self.last_heart_beat = time.time() + logger.info(f"Bot {self_id} 连接成功") + asyncio.create_task(self.check_heartbeat(self_id)) + elif event_type == MetaEventType.heartbeat: + if message["status"].get("online") and message["status"].get("good"): + if not self._interval_checking: + asyncio.create_task(self.check_heartbeat()) + self.last_heart_beat = time.time() + self.interval = message.get("interval") / 1000 + else: + self_id = message.get("self_id") + logger.warning(f"Bot {self_id} Napcat 端异常!") + + async def check_heartbeat(self, id: int) -> None: + self._interval_checking = True + while True: + now_time = time.time() + if now_time - self.last_heart_beat > self.interval * 2: + logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") + break + else: + logger.debug("心跳正常") + await asyncio.sleep(self.interval) + + def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: + # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif + """ + 检查是否允许聊天 + Parameters: + user_id: int: 用户ID + group_id: int: 群ID + Returns: + bool: 是否允许聊天 + """ + logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") + if group_id: + if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: + logger.warning("群聊不在聊天白名单中,消息被丢弃") + return False + elif global_config.chat.group_list_type == "blacklist" and group_id in global_config.chat.group_list: + logger.warning("群聊在聊天黑名单中,消息被丢弃") + return False + else: + if global_config.chat.private_list_type == "whitelist" and user_id not in global_config.chat.private_list: + logger.warning("私聊不在聊天白名单中,消息被丢弃") + return False + elif global_config.chat.private_list_type == "blacklist" and user_id in global_config.chat.private_list: + logger.warning("私聊在聊天黑名单中,消息被丢弃") + return False + if user_id in global_config.chat.ban_user_id: + logger.warning("用户在全局黑名单中,消息被丢弃") + return False + return True + + async def handle_raw_message(self, raw_message: dict) -> None: + # sourcery skip: low-code-quality, remove-unreachable-code + """ + 从Napcat接受的原始消息处理 + + Parameters: + raw_message: dict: 原始消息 + """ + message_type: str = raw_message.get("message_type") + message_id: int = raw_message.get("message_id") + # message_time: int = raw_message.get("time") + message_time: float = time.time() # 应可乐要求,现在是float了 + + template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 + format_info: FormatInfo = FormatInfo( + content_format=["text", "image", "emoji"], + accept_format=["text", "image", "emoji", "reply", "voice", "command"], + ) # 格式化信息 + if message_type == MessageType.private: + sub_type = raw_message.get("sub_type") + if sub_type == MessageType.Private.friend: + sender_info: dict = raw_message.get("sender") + + if not self.check_allow_to_chat(sender_info.get("user_id"), None): + return None + + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=sender_info.get("nickname"), + user_cardname=sender_info.get("card"), + ) + + # 不存在群信息 + group_info: GroupInfo = None + elif sub_type == MessageType.Private.group: + """ + 本部分暂时不做支持,先放着 + """ + logger.warning("群临时消息类型不支持") + return None + + sender_info: dict = raw_message.get("sender") + + # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取 + fetched_member_info: dict = await get_member_info( + self.server_connection, + raw_message.get("group_id"), + sender_info.get("user_id"), + ) + nickname = fetched_member_info.get("nickname") if fetched_member_info else None + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=nickname, + user_cardname=None, + ) + + # -------------------这里需要群信息吗?------------------- + + # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 + fetched_group_info: dict = await get_group_info(self.server_connection, raw_message.get("group_id")) + group_name = "" + if fetched_group_info.get("group_name"): + group_name = fetched_group_info.get("group_name") + + group_info: GroupInfo = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=raw_message.get("group_id"), + group_name=group_name, + ) + + else: + logger.warning(f"私聊消息类型 {sub_type} 不支持") + return None + elif message_type == MessageType.group: + sub_type = raw_message.get("sub_type") + if sub_type == MessageType.Group.normal: + sender_info: dict = raw_message.get("sender") + + if not self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")): + return None + + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=sender_info.get("nickname"), + user_cardname=sender_info.get("card"), + ) + + # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 + fetched_group_info = await get_group_info(self.server_connection, raw_message.get("group_id")) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + + group_info: GroupInfo = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=raw_message.get("group_id"), + group_name=group_name, + ) + + else: + logger.warning(f"群聊消息类型 {sub_type} 不支持") + return None + + additional_config: dict = {} + if global_config.voice.use_tts: + additional_config["allow_tts"] = True + + # 消息信息 + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id=message_id, + time=message_time, + user_info=user_info, + group_info=group_info, + template_info=template_info, + format_info=format_info, + additional_config=additional_config, + ) + + # 处理实际信息 + if not raw_message.get("message"): + logger.warning("原始消息内容为空") + return None + + # 获取Seg列表 + seg_message: List[Seg] = await self.handle_real_message(raw_message) + if not seg_message: + logger.warning("处理后消息内容为空") + return None + submit_seg: Seg = Seg( + type="seglist", + data=seg_message, + ) + # MessageBase创建 + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=submit_seg, + raw_message=raw_message.get("raw_message"), + ) + + logger.info("发送到Maibot处理信息") + await self.message_process(message_base) + + async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None: + # sourcery skip: low-code-quality + """ + 处理实际消息 + Parameters: + real_message: dict: 实际消息 + Returns: + seg_message: list[Seg]: 处理后的消息段列表 + """ + real_message: list = raw_message.get("message") + if not real_message: + return None + seg_message: List[Seg] = [] + for sub_message in real_message: + sub_message: dict + sub_message_type = sub_message.get("type") + match sub_message_type: + case RealMessageType.text: + ret_seg = await self.handle_text_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("text处理失败") + case RealMessageType.face: + ret_seg = await self.handle_face_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("face处理失败或不支持") + case RealMessageType.reply: + if not in_reply: + ret_seg = await self.handle_reply_message(sub_message) + if ret_seg: + seg_message += ret_seg + else: + logger.warning("reply处理失败") + case RealMessageType.image: + ret_seg = await self.handle_image_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("image处理失败") + case RealMessageType.record: + logger.warning("不支持语音解析") + case RealMessageType.video: + logger.warning("不支持视频解析") + case RealMessageType.at: + ret_seg = await self.handle_at_message( + sub_message, + raw_message.get("self_id"), + raw_message.get("group_id"), + ) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("at处理失败") + case RealMessageType.rps: + logger.warning("暂时不支持猜拳魔法表情解析") + case RealMessageType.dice: + logger.warning("暂时不支持骰子表情解析") + case RealMessageType.shake: + # 预计等价于戳一戳 + logger.warning("暂时不支持窗口抖动解析") + case RealMessageType.share: + logger.warning("暂时不支持链接解析") + case RealMessageType.forward: + messages = await self.get_forward_message(sub_message) + if not messages: + logger.warning("转发消息内容为空或获取失败") + return None + ret_seg = await self.handle_forward_message(messages) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("转发消息处理失败") + case RealMessageType.node: + logger.warning("不支持转发消息节点解析") + case _: + logger.warning(f"未知消息类型: {sub_message_type}") + return seg_message + + async def handle_text_message(self, raw_message: dict) -> Seg: + """ + 处理纯文本信息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + plain_text: str = message_data.get("text") + return Seg(type="text", data=plain_text) + + async def handle_face_message(self, raw_message: dict) -> Seg | None: + """ + 处理表情消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + face_raw_id: str = str(message_data.get("id")) + if face_raw_id in qq_face: + face_content: str = qq_face.get(face_raw_id) + return Seg(type="text", data=face_content) + else: + logger.warning(f"不支持的表情:{face_raw_id}") + return None + + async def handle_image_message(self, raw_message: dict) -> Seg | None: + """ + 处理图片消息与表情包消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + image_sub_type = message_data.get("sub_type") + try: + image_base64 = await get_image_base64(message_data.get("url")) + except Exception as e: + logger.error(f"图片消息处理失败: {str(e)}") + return None + if image_sub_type == 0: + """这部分认为是图片""" + return Seg(type="image", data=image_base64) + elif image_sub_type == 1: + """这部分认为是表情包""" + return Seg(type="emoji", data=image_base64) + else: + logger.warning(f"不支持的图片子类型:{image_sub_type}") + return None + + async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int) -> Seg | None: + # sourcery skip: use-named-expression + """ + 处理at消息 + Parameters: + raw_message: dict: 原始消息 + self_id: int: 机器人QQ号 + group_id: int: 群号 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + if message_data: + qq_id = message_data.get("qq") + if str(self_id) == str(qq_id): + logger.debug("机器人被at") + self_info: dict = await get_self_info(self.server_connection) + if self_info: + return Seg(type="text", data=f"@<{self_info.get('nickname')}:{self_info.get('user_id')}>") + else: + return None + else: + member_info: dict = await get_member_info(self.server_connection, group_id=group_id, user_id=qq_id) + if member_info: + return Seg(type="text", data=f"@<{member_info.get('nickname')}:{member_info.get('user_id')}>") + else: + return None + + async def get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: + forward_message_data: Dict = raw_message.get("data") + if not forward_message_data: + logger.warning("转发消息内容为空") + return None + forward_message_id = forward_message_data.get("id") + request_uuid = str(uuid.uuid4()) + payload = json.dumps( + { + "action": "get_forward_msg", + "params": {"message_id": forward_message_id}, + "echo": request_uuid, + } + ) + try: + await self.server_connection.send(payload) + response: dict = await get_response(request_uuid) + except TimeoutError: + logger.error("获取转发消息超时") + return None + except Exception as e: + logger.error(f"获取转发消息失败: {str(e)}") + return None + logger.debug( + f"转发消息原始格式:{json.dumps(response)[:80]}..." + if len(json.dumps(response)) > 80 + else json.dumps(response) + ) + response_data: Dict = response.get("data") + if not response_data: + logger.warning("转发消息内容为空或获取失败") + return None + return response_data.get("messages") + + async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: + # sourcery skip: move-assign-in-block, use-named-expression + """ + 处理回复消息 + + """ + raw_message_data: dict = raw_message.get("data") + message_id: int = None + if raw_message_data: + message_id = raw_message_data.get("id") + else: + return None + message_detail: dict = await get_message_detail(self.server_connection, message_id) + if not message_detail: + logger.warning("获取被引用的消息详情失败") + return None + reply_message = await self.handle_real_message(message_detail, in_reply=True) + if reply_message is None: + reply_message = "(获取发言内容失败)" + sender_info: dict = message_detail.get("sender") + sender_nickname: str = sender_info.get("nickname") + sender_id: str = sender_info.get("user_id") + seg_message: List[Seg] = [] + if not sender_nickname: + logger.warning("无法获取被引用的人的昵称,返回默认值") + seg_message.append(Seg(type="text", data="[回复 未知用户:")) + else: + seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}:{sender_id}>:")) + seg_message += reply_message + seg_message.append(Seg(type="text", data="],说:")) + return seg_message + + async def handle_notice(self, raw_message: dict) -> None: + notice_type = raw_message.get("notice_type") + # message_time: int = raw_message.get("time") + message_time: float = time.time() # 应可乐要求,现在是float了 + + group_id = raw_message.get("group_id") + user_id = raw_message.get("user_id") + target_id = raw_message.get("target_id") + + if not self.check_allow_to_chat(user_id, group_id): + logger.warning("notice消息被丢弃") + return None + + handled_message: Seg = None + + match notice_type: + case NoticeType.friend_recall: + logger.info("好友撤回一条消息") + logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") + logger.warning("暂时不支持撤回消息处理") + case NoticeType.group_recall: + logger.info("群内用户撤回一条消息") + logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") + logger.warning("暂时不支持撤回消息处理") + case NoticeType.notify: + sub_type = raw_message.get("sub_type") + match sub_type: + case NoticeType.Notify.poke: + if global_config.chat.enable_poke: + handled_message: Seg = await self.handle_poke_notify(raw_message) + else: + logger.warning("戳一戳消息被禁用,取消戳一戳处理") + case _: + logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") + case _: + logger.warning(f"不支持的notice类型: {notice_type}") + return None + if not handled_message: + logger.warning("notice处理失败或不支持") + return None + + source_name: str = None + source_cardname: str = None + if group_id: + member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if member_info: + source_name = member_info.get("nickname") + source_cardname = member_info.get("card") + else: + logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") + source_name = "QQ用户" + else: + stranger_info = await get_stranger_info(self.server_connection, user_id) + if stranger_info: + source_name = stranger_info.get("nickname") + else: + logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") + source_name = "QQ用户" + + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=source_name, + user_cardname=source_cardname, + ) + + group_info: GroupInfo = None + if group_id: + fetched_group_info = await get_group_info(self.server_connection, group_id) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + else: + logger.warning("无法获取戳一戳消息所在群的名称") + group_info = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=group_id, + group_name=group_name, + ) + + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id="notice", + time=message_time, + user_info=user_info, + group_info=group_info, + template_info=None, + format_info=None, + additional_config = {"target_id": target_id} + ) + + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=handled_message, + raw_message=json.dumps(raw_message), + ) + + logger.info("发送到Maibot处理通知信息") + await self.message_process(message_base) + + async def handle_poke_notify(self, raw_message: dict) -> Seg | None: + self_info: dict = await get_self_info(self.server_connection) + + if not self_info: + logger.error("自身信息获取失败") + return None + self_id = raw_message.get("self_id") + target_id = raw_message.get("target_id") + group_id = raw_message.get("group_id") + user_id = raw_message.get("user_id") + target_name: str = None + raw_info: list = raw_message.get("raw_info") + # 计算Seg + if self_id == target_id: + target_name = self_info.get("nickname") + user_name = "" + else: + if group_id: + user_info: dict = await get_member_info( + self.server_connection, group_id, user_id + ) + fetched_member_info: dict = await get_member_info( + self.server_connection, group_id, target_id + ) + if user_info: + user_name = user_info.get("nickname") + else: + user_name = "QQ用户" + if fetched_member_info: + target_name = fetched_member_info.get("nickname") + else: + target_name = "QQ用户" + else: + return None + try: + first_txt = raw_info[2].get("txt", "戳了戳") + except Exception as e: + logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") + first_txt = "戳了戳" + + seg_data: Seg = Seg( + type="text", + data=f"{user_name}{first_txt}{target_name}(这是QQ的一个功能,用于提及某人,但没那么明显)", + ) + return seg_data + + async def handle_forward_message(self, message_list: list) -> Seg | None: + """ + 递归处理转发消息,并按照动态方式确定图片处理方式 + Parameters: + message_list: list: 转发消息列表 + """ + handled_message, image_count = await self._handle_forward_message(message_list, 0) + handled_message: Seg + image_count: int + if not handled_message: + return None + if image_count < 5 and image_count > 0: + # 处理图片数量小于5的情况,此时解析图片为base64 + logger.trace("图片数量小于5,开始解析图片为base64") + return await self._recursive_parse_image_seg(handled_message, True) + elif image_count > 0: + logger.trace("图片数量大于等于5,开始解析图片为占位符") + # 处理图片数量大于等于5的情况,此时解析图片为占位符 + return await self._recursive_parse_image_seg(handled_message, False) + else: + # 处理没有图片的情况,此时直接返回 + logger.trace("没有图片,直接返回") + return handled_message + + async def _recursive_parse_image_seg(self, seg_data: Seg, to_image: bool) -> Seg: + # sourcery skip: merge-else-if-into-elif + if to_image: + if seg_data.type == "seglist": + new_seg_list = [] + for i_seg in seg_data.data: + parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) + new_seg_list.append(parsed_seg) + return Seg(type="seglist", data=new_seg_list) + elif seg_data.type == "image": + image_url = seg_data.data + try: + encoded_image = await get_image_base64(image_url) + except Exception as e: + logger.error(f"图片处理失败: {str(e)}") + return Seg(type="text", data="[图片]") + return Seg(type="image", data=encoded_image) + elif seg_data.type == "emoji": + image_url = seg_data.data + try: + encoded_image = await get_image_base64(image_url) + except Exception as e: + logger.error(f"图片处理失败: {str(e)}") + return Seg(type="text", data="[表情包]") + return Seg(type="emoji", data=encoded_image) + else: + logger.trace(f"不处理类型: {seg_data.type}") + return seg_data + else: + if seg_data.type == "seglist": + new_seg_list = [] + for i_seg in seg_data.data: + parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) + new_seg_list.append(parsed_seg) + return Seg(type="seglist", data=new_seg_list) + elif seg_data.type == "image": + return Seg(type="text", data="[图片]") + elif seg_data.type == "emoji": + return Seg(type="text", data="[动画表情]") + else: + logger.trace(f"不处理类型: {seg_data.type}") + return seg_data + + async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[Seg, int] | Tuple[None, int]: + # sourcery skip: low-code-quality + """ + 递归处理实际转发消息 + Parameters: + message_list: list: 转发消息列表,首层对应messages字段,后面对应content字段 + layer: int: 当前层级 + Returns: + seg_data: Seg: 处理后的消息段 + image_count: int: 图片数量 + """ + seg_list: List[Seg] = [] + image_count = 0 + if message_list is None: + return None, 0 + for sub_message in message_list: + sub_message: dict + sender_info: dict = sub_message.get("sender") + user_nickname: str = sender_info.get("nickname", "QQ用户") + user_nickname_str = f"【{user_nickname}】:" + break_seg = Seg(type="text", data="\n") + message_of_sub_message_list: List[Dict[str, Any]] = sub_message.get("message") + if not message_of_sub_message_list: + logger.warning("转发消息内容为空") + continue + message_of_sub_message = message_of_sub_message_list[0] + if message_of_sub_message.get("type") == RealMessageType.forward: + if layer >= 3: + full_seg_data = Seg( + type="text", + data=("--" * layer) + f"【{user_nickname}】:【转发消息】\n", + ) + else: + sub_message_data = message_of_sub_message.get("data") + if not sub_message_data: + continue + contents = sub_message_data.get("content") + seg_data, count = await self._handle_forward_message(contents, layer + 1) + image_count += count + head_tip = Seg( + type="text", + data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n", + ) + full_seg_data = Seg(type="seglist", data=[head_tip, seg_data]) + seg_list.append(full_seg_data) + elif message_of_sub_message.get("type") == RealMessageType.text: + sub_message_data = message_of_sub_message.get("data") + if not sub_message_data: + continue + text_message = sub_message_data.get("text") + seg_data = Seg(type="text", data=text_message) + data_list: List[Any] = [] + if layer > 0: + data_list = [ + Seg(type="text", data=("--" * layer) + user_nickname_str), + seg_data, + break_seg, + ] + else: + data_list = [ + Seg(type="text", data=user_nickname_str), + seg_data, + break_seg, + ] + seg_list.append(Seg(type="seglist", data=data_list)) + elif message_of_sub_message.get("type") == RealMessageType.image: + image_count += 1 + image_data = message_of_sub_message.get("data") + sub_type = image_data.get("sub_type") + image_url = image_data.get("url") + data_list: List[Any] = [] + if sub_type == 0: + seg_data = Seg(type="image", data=image_url) + else: + seg_data = Seg(type="emoji", data=image_url) + if layer > 0: + data_list = [ + Seg(type="text", data=("--" * layer) + user_nickname_str), + seg_data, + break_seg, + ] + else: + data_list = [ + Seg(type="text", data=user_nickname_str), + seg_data, + break_seg, + ] + full_seg_data = Seg(type="seglist", data=data_list) + seg_list.append(full_seg_data) + return Seg(type="seglist", data=seg_list), image_count + + async def message_process(self, message_base: MessageBase) -> None: + try: + send_status = await self.maibot_router.send_message(message_base) + if not send_status: + raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") + except Exception as e: + logger.error(f"发送消息失败: {str(e)}") + logger.error("请检查与MaiBot之间的连接") + return None + + +recv_handler = RecvHandler() From 4ae69e05092d07d11c556ab150cbdb8c285ea4cd Mon Sep 17 00:00:00 2001 From: Dreamwxz Date: Mon, 23 Jun 2025 16:55:25 +0800 Subject: [PATCH 024/112] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=E7=9A=84=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 水个pr( --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 41e8eb1..c0ad7e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,6 @@ requests maim_message loguru pillow -tomli \ No newline at end of file +tomli +tomlkit +rich \ No newline at end of file From 77ff2ff2575848ed7b04c090dcabc10a2322e7a9 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 24 Jun 2025 11:40:15 +0800 Subject: [PATCH 025/112] =?UTF-8?q?=E5=B1=8F=E8=94=BD=E5=AE=98=E6=96=B9?= =?UTF-8?q?=E6=9C=BA=E5=99=A8=E4=BA=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- assets/maimai.ico | Bin 67715 -> 0 bytes src/config/official_configs.py | 3 +++ src/recv_handler.py | 36 ++++++++++++++++++++++++--- src/utils.py | 44 ++++++++++++++++++++++++++++++++- template/template_config.toml | 1 + 6 files changed, 81 insertions(+), 6 deletions(-) delete mode 100644 assets/maimai.ico diff --git a/.gitignore b/.gitignore index 267374b..b2d679d 100644 --- a/.gitignore +++ b/.gitignore @@ -271,4 +271,5 @@ $RECYCLE.BIN/ config.toml config.toml.back -test \ No newline at end of file +test +data/qq_bot.json \ No newline at end of file diff --git a/assets/maimai.ico b/assets/maimai.ico deleted file mode 100644 index 578b11cdd0c88dc8f590668a718303d939754f67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67715 zcmW(+1z3|`AAL7^fOOXo1q2k3?go*R27?X>>5h$7P#UB`5J9?;9w5@)IS?eIOLE({ z|F=DR_U_r9?Y{5s@BGd=_gnw~VekLlKmZhQAp`-m*y9hn+G?Z_MhNzhRQ;K<-hc1@ z_aMZ_KE3iNvHS0ptB1A$Xsp0D69C|Vy0U_S-{O7_0o*`kvd_5JYjtJY-_B!npgg^c zS>Xl63wZ?(d>RhU6ev#^p_SrKTpk4vRpkQQ0fqsIcj=Fg%FM^6C#CIu>_6jO#GTyO zWgKs{hrGtz81IyR{c0U=9pAaLvFthKJw|69UA$~cvys?(eBn|D&z&}HubxQS%wI?&|bN!w5 z;$`Aw(2)42wu28)w@8e3%kH-v=(6!EcNKM-eW5GO)i(cR2sL+t z!4Egf%bcMnt>qDMjWj$4JAo4_bl%3$k}}K_gRjpLH!fT+GpwO*`|kTETrld#X2Oqj za;4RMSG?&KF-S?r*WNRR!SC8mezD(N;?f-2^BR_V+5H(zosz2x3+|4jdt+kWH_0gC zxZj4p*=tJSGxaEP#DH#c2C<)!x}JUMswIiO+DLx>`AM*I(*IAOJf2EhoSkUh$OYB{ z@shFycpYa-Q%`U9s-pkSlzsl@1uKGhvjn4*hr_LUV;<(f(AUl9$j9F0N0UWEp>Ns) zii91fx&NSVkB}8`L3Q)a%SjD>nTsf=ed-`@||^MvZH;{KLHfN?!T0>D?(F-7COh`P-VuT|C=wJ5_p|D+npN{j6-(dM7z~|!e%N}%*?xCAr0%lRsHI1m>2=h8 z*%G=zvL?=$!a#<4+_W5akZ0b2p(~y6{XUW>Y15w|5GLpTTUm_!Zyy`%RSfl`FeA3g zxq#?58x0Vfa2mj8&%c$m0Ghodg1H z?s^uptCqb=Pcsv`j;T?{vTtN1Jc}8f{#ANK&(+0iD?8CQU$OJ?;Y_I^?{}YrMRigp zQBB8{mIkTD$=|Pd?uu7o7?k~Vxv9{R5}AyFs#TRT zjgXi7bz_R#Jr15s@Gk6}dt5rwg9o9WH?OhkHTOb)Q0=U(fSY=tNxF|X*ZcX$j-HRr6W#%o@5b{h6+%W$rq$OJIw`tC}&%w0C^xZag^ zHp^pJixe@TA^#o)cPOb*(ByWy%fM;oK+uxoW~aNkPPF&@nPGu45&Q&K=Cj!#koE|{ zva{UmnfWwLAZ#B8fdx=@NOplREBQ11E}e;OgPf`HvoX0NO?&`8W)0^4^Lb1T^NM<3 zkA^#B{MNl@)Ho^RXkJm~U%v#zDN_?~V8R`j?Vy73!jI2qi}y{L@+B1F=q~YoELKQeJ7}BS7dDJ$dFaque#N^ zkF}3Ax@dhsRf$P)=+k5woV6HwtV8pWvL)Yj3U9^WyS)41ON4|gDqMUo*m#Xj;py`Yo5R7Pb?!9ZMPA(ESkydhBq z^ekmERu6M-FG4(t8PQ#L%hP}S2*Ojqr+A*(9ne-CX7a4cx)Z(1iK@5`c#PaZ^8a-* zyl5{7cmgvfmx_~s?~-S2yZ>9T=aSBQQGHj;+S-c#^+cw48jJ0b25V%3Am_DR%+*GG z5w`W6VmqP+vLkrK$pLmtA`3t2xYh2!f`=CXf4y_P8jiu4b;m+|;&HeD4KXvnq>o_p z?q=3rXS{wFrd5{CsH^jZhz-^2;iIysyizl}3dqG1mPb6B;sxD2<(4zKmZ_LvcY)}0T8kgRAc2{H?8O(<$tz9rL2NKq0g7i8=g5QqkhcaRDKzj3N%>fIG zH}-C&f>&8{*R9x*pUzvyH@_&>W_j^z9j*vF62qe(MZw=}S3$tZ7q0^T`mdBJ232MgbCs_*z836xjA1T`|mwU9Cbc5<)YsuC2S`!~n(2R*c>t?Q>H zkYD+@4Y#Gg8lcFtWGToIxCrO*Bgq3Y;Cy$AfLv<=$xsaFhVBsF68(qVlWctb*g9YC z{b^bsF(K)DsClzP)G|LdtpfW5QJ|tpVr@xR{oA>{w=G)QxLCHxYe>iMweZ$kSX40z zxc}b409PnYw2^`gaHfAYk7Q1^=2+w0B&>IoBoM~JohK(859Mucq?XvqIqW7|W7jV! zw-pD+1We{}12RV4y&g6yJta`s4C1FZZsSA@6(LS z+=MV@>e3y#4g$|~ys25)mEh!>g;TOKaYYM4lzy=MiBBMCKj)@2$9vA@ws;9Z;Mjk( z*CdLJ72R3))C!d5`h4dI^{w+ZD_1|gIx7>O$@{J#Oh#dQM?V;4ikH%Qp0N^W!@`2W zwvlFkOu3Y%E4iMG5nAi%XY{zWN=XuL>+E-6~^IcA$z zsX6qv!tPg%z#8Th=k_Q?U)$YMqQ2cx#QBcOK`TXu zjTo1V8vgq7SadczOx%x*oNe>>EPj1b&T&O3Q_lzze(uUBS>jSh3^Zh^co%b&A zvD_w_^##+^+o0DDcNrQ7H}`p#8@$~^O1m2kME#i=VfVgHI}5jB07 z{+@m58DR9}Ydn)ls;!o^u^RtAqI4)z|BB7#LvO+O1aZb3z z)ji|ZJ|-aJn#@9Mt4@5frxGt2UsZ*kN?x}P0wsLZu4DA&wXEA3qUyX3`Pyqjg$Y8- zM}I00EysB&Tj9Mo%Pum}-5z*JyM(d_fC~a#fB-Kep|<0-m3$su+3kMxi}Rt!$q zm>sQ8_wId91M^1~?;0f7(Ab7U@E2@a0l3NIN!n1ejx|n>c`dOrNpZt#m@OpMH>J$N z_tsnJ0VV*R;a)F~*os7QdB9D*haNZm(EU0ZXo5smI9vS2c4<^h*m>5?;?L7RLsf>1 zofXd#@84d8eA^Mv2ht`RU6#gU`8((vS75R@7F6Ww@>-d~M0=O_u+;vLN`!y(*F@m{ zi8;EuR%f$<-=rG2fweq2Zk5H7%n)_rj{8sD(8;V;2!GMedq4Sw+e?2t+>PId|}49iX? zKa`^Q|9&NV%}iXZlh;w@itJ#YVYU>O9Tm|C3#DZ`xW~DDCl)W4RFtlcM-}Q%ValEY z;dnvCu7W|EB3;Xmwv85HwW~||7a?hej7SZwmYLF(%bD-`Tor+rhn8|`z%3g9XGnNb z-Uur(sj(_c0WE6`p4L<#Fp=?Rqf%ooTz|Gs%l~bdYGsL5RQRk#T%T

9q=wX1$>M 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⁢M5M6l!#t$4Iq9u3gs}Q_@_7Zo*5SE}Y^vaGH+ky#ka*McEbX+p0En4|w zJv}*^)jKx$3xBcXbAs;$lLegB94I^%4(4Ouyaezy35Mg#AvWc?FabI#9RRVY@CO%g zs}v_U?N`&dk`R-gHo65uH*zg+J*qrTrN`5mr53jZbD}tat?24fkzu^;2mptc&n~-_ zu#^AlBbnTt0Lm2rKaN#nbCt5NL(?6ybTSRCZX?ApIte1csD2uNdkRBi8{Syz4cj(0fpLIRiJR}Qvl-hQIH#R|C<~$G)%PvALv*kF zf7x~UCq6U%lf8qnPb3y{a;|xBn)QL14cY^GR&wqDhP%*REGgA+Q>6EJzYHd%hg@d%epovrA(zKoXD#1j!?nP<6J4aKuF1)oJ{(KtipOC= z&Nh_*+uMPoRF9YJ_V3qq7bqAFJKb?Y6R7yGkc0k}uM7Zrkh1`Bf585`+oiFTgos}k zltM1oOD-tSbKOpb^rY`0?t=cECd}qSuykTn}IkC^lS}6s8BGT&9JLS3nQ@6c8 z>tI0mv)k^(>T{VY6FrveuS_ z;`HF3-`|6OXhv8Z4oh2DNG?9nmyg=O)3DCa0k4zIo{m9)d0FzMJL{r$p}pX)J)q(m z97J{IHnj9o(xU)?S3O*k1%yCY0roc?fLFskXiICXpl$Z1hS(su0v+$Nnk2sj8aqc_ zOxAVe$e`MBtD{t<^mHA*VZ4&WU>+f(!f{Ya#x%I)b|< zveT>*`6GmugX9Riw(iTBzi_u%cE%tdHf_k<1_xh;UMQoZYk>67R41XXc&_1$1041G z=4eo;$b|rTe+iZ7G+UGv-^U`Z=2{anE?1Y0;t&+qi3nw~HT|T?DMlxC?xqF2OG>4- zS737imSE+7rYgG+1XNMYuFpB z_yB9c@Cu0uWXZ*VOj4OEM-Hjx2;J-(n^p(4P0O6~{@!M}gq`@_2d6XWngeOxVLfrw zj)!8MQrH;2{mqw&lbe?nc(*PNya4MrBwN3{+3sYW#>H0Qxu91>kjsK!RO2ILSS{8QS@p5�Mr;^wE$g-l5zBebV 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 None: event_type = message.get("meta_event_type") @@ -69,7 +72,7 @@ async def check_heartbeat(self, id: int) -> None: logger.debug("心跳正常") await asyncio.sleep(self.interval) - def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: + async def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif """ 检查是否允许聊天 @@ -79,7 +82,32 @@ def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: Returns: bool: 是否允许聊天 """ + user_id = str(user_id) logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") + if global_config.chat.ban_qq_bot and group_id: + logger.debug("开始判断是否为机器人") + if not self.bot_id_list: + self.bot_id_list = read_bot_id() + if user_id in self.bot_id_list: + if self.bot_id_list[user_id]: + logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃") + return False + else: + member_info = await get_member_info(self.server_connection, group_id, user_id) + if member_info: + is_bot = member_info.get("is_robot") + if is_bot is None: + logger.warning("无法获取用户是否为机器人,默认为不是但是不进行更新") + else: + if is_bot: + logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃,新机器人加入拦截名单") + self.bot_id_list[user_id] = True + update_bot_id(self.bot_id_list) + return False + else: + self.bot_id_list[user_id] = False + update_bot_id(self.bot_id_list) + logger.debug("开始检查聊天白名单/黑名单") if group_id: if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: logger.warning("群聊不在聊天白名单中,消息被丢弃") @@ -122,7 +150,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: if sub_type == MessageType.Private.friend: sender_info: dict = raw_message.get("sender") - if not self.check_allow_to_chat(sender_info.get("user_id"), None): + if not await self.check_allow_to_chat(sender_info.get("user_id"), None): return None # 发送者用户信息 @@ -181,7 +209,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: if sub_type == MessageType.Group.normal: sender_info: dict = raw_message.get("sender") - if not self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")): + if not await self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")): return None # 发送者用户信息 @@ -485,7 +513,7 @@ async def handle_notice(self, raw_message: dict) -> None: group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") - if not self.check_allow_to_chat(user_id, group_id): + if not await self.check_allow_to_chat(user_id, group_id): logger.warning("notice消息被丢弃") return None diff --git a/src/utils.py b/src/utils.py index fb0b5b4..9e9941c 100644 --- a/src/utils.py +++ b/src/utils.py @@ -7,9 +7,10 @@ import urllib3 import ssl - +from pathlib import Path from PIL import Image import io +import os class SSLAdapter(urllib3.PoolManager): @@ -88,6 +89,7 @@ async def get_image_base64(url: str) -> str: def convert_image_to_gif(image_base64: str) -> str: + # sourcery skip: extract-method """ 将Base64编码的图片转换为GIF格式 Parameters: @@ -192,3 +194,43 @@ async def get_message_detail(websocket: Server.ServerConnection, message_id: str return None logger.debug(response) return response.get("data") + + +def update_bot_id(data: dict) -> None: + """ + 更新用户是否为机器人的字典到根目录下的data文件夹中的qq_bot.json。 + Parameters: + data: dict: 包含需要更新的信息。 + """ + json_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "qq_bot.json") + try: + with open(json_path, "w", encoding="utf-8") as json_file: + json.dump(data, json_file, ensure_ascii=False, indent=4) + logger.info(f"ID字典已更新到文件: {json_path}") + except Exception as e: + logger.error(f"更新ID字典失败: {e}") + + +def read_bot_id() -> dict: + """ + 从根目录下的data文件夹中的文件读取机器人ID。 + Returns: + list: 读取的机器人ID信息。 + """ + json_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "qq_bot.json") + try: + with open(json_path, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + logger.info(f"已读取机器人ID信息: {data}") + return data + except FileNotFoundError: + logger.warning(f"文件未找到: {json_path},正在自动创建文件") + json_path = Path(os.path.dirname(os.path.dirname(__file__))) / "data" / "qq_bot.json" + # 确保父目录存在 + json_path.parent.mkdir(parents=True, exist_ok=True) + # 创建空文件 + json_path.touch(exist_ok=True) + return {} + except Exception as e: + logger.error(f"读取机器人ID失败: {e}") + return {} diff --git a/template/template_config.toml b/template/template_config.toml index b4cdce0..8598260 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -24,6 +24,7 @@ private_list = [] # 私聊名单 # 当private_list_type为whitelist时,只有私聊名单中的用户可以聊天 # 当private_list_type为blacklist时,私聊名单中的任何用户无法聊天 ban_user_id = [] # 全局禁止名单(全局禁止名单中的用户无法进行任何聊天) +ban_qq_bot = false # 是否屏蔽QQ官方机器人 enable_poke = true # 是否启用戳一戳功能 [voice] # 发送语音设置 From 7eaaf32f0cecdb17346c7ae107830060fd3a1a90 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Tue, 24 Jun 2025 12:59:36 +0800 Subject: [PATCH 026/112] =?UTF-8?q?Revert=20"=E4=BD=BF=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E4=BA=BA=E7=9A=84=E6=88=B3=E4=B8=80=E6=88=B3=E8=83=BD=E5=A4=9F?= =?UTF-8?q?=E8=A2=AB=E8=A7=A3=E6=9E=90=EF=BC=8C=E5=B9=B6=E5=9C=A8addtional?= =?UTF-8?q?=5Fconfig=E9=99=84=E5=8A=A0=E8=A2=AB=E6=88=B3=E4=BA=BA=E7=9A=84?= =?UTF-8?q?ID=E4=BB=A5=E6=96=B9=E4=BE=BF=E5=BC=80=E5=8F=91"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 409f9f6b0718ac0d4ffb5bda2e308254df22e108. --- src/recv_handler.py | 1580 +++++++++++++++++++++---------------------- 1 file changed, 785 insertions(+), 795 deletions(-) diff --git a/src/recv_handler.py b/src/recv_handler.py index 46b9bd7..d07cb78 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -1,795 +1,785 @@ -from .logger import logger -from .config import global_config -from .qq_emoji_list import qq_face -import time -import asyncio -import json -import websockets as Server -from typing import List, Tuple, Optional, Dict, Any -import uuid - -from . import MetaEventType, RealMessageType, MessageType, NoticeType -from maim_message import ( - UserInfo, - GroupInfo, - Seg, - BaseMessageInfo, - MessageBase, - TemplateInfo, - FormatInfo, - Router, -) - -from .utils import ( - get_group_info, - get_member_info, - get_image_base64, - get_self_info, - get_stranger_info, - get_message_detail, -) -from .response_pool import get_response - - -class RecvHandler: - maibot_router: Router = None - - def __init__(self): - self.server_connection: Server.ServerConnection = None - self.interval = global_config.napcat_server.heartbeat_interval - self._interval_checking = False - - async def handle_meta_event(self, message: dict) -> None: - event_type = message.get("meta_event_type") - if event_type == MetaEventType.lifecycle: - sub_type = message.get("sub_type") - if sub_type == MetaEventType.Lifecycle.connect: - self_id = message.get("self_id") - self.last_heart_beat = time.time() - logger.info(f"Bot {self_id} 连接成功") - asyncio.create_task(self.check_heartbeat(self_id)) - elif event_type == MetaEventType.heartbeat: - if message["status"].get("online") and message["status"].get("good"): - if not self._interval_checking: - asyncio.create_task(self.check_heartbeat()) - self.last_heart_beat = time.time() - self.interval = message.get("interval") / 1000 - else: - self_id = message.get("self_id") - logger.warning(f"Bot {self_id} Napcat 端异常!") - - async def check_heartbeat(self, id: int) -> None: - self._interval_checking = True - while True: - now_time = time.time() - if now_time - self.last_heart_beat > self.interval * 2: - logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") - break - else: - logger.debug("心跳正常") - await asyncio.sleep(self.interval) - - def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: - # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif - """ - 检查是否允许聊天 - Parameters: - user_id: int: 用户ID - group_id: int: 群ID - Returns: - bool: 是否允许聊天 - """ - logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") - if group_id: - if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: - logger.warning("群聊不在聊天白名单中,消息被丢弃") - return False - elif global_config.chat.group_list_type == "blacklist" and group_id in global_config.chat.group_list: - logger.warning("群聊在聊天黑名单中,消息被丢弃") - return False - else: - if global_config.chat.private_list_type == "whitelist" and user_id not in global_config.chat.private_list: - logger.warning("私聊不在聊天白名单中,消息被丢弃") - return False - elif global_config.chat.private_list_type == "blacklist" and user_id in global_config.chat.private_list: - logger.warning("私聊在聊天黑名单中,消息被丢弃") - return False - if user_id in global_config.chat.ban_user_id: - logger.warning("用户在全局黑名单中,消息被丢弃") - return False - return True - - async def handle_raw_message(self, raw_message: dict) -> None: - # sourcery skip: low-code-quality, remove-unreachable-code - """ - 从Napcat接受的原始消息处理 - - Parameters: - raw_message: dict: 原始消息 - """ - message_type: str = raw_message.get("message_type") - message_id: int = raw_message.get("message_id") - # message_time: int = raw_message.get("time") - message_time: float = time.time() # 应可乐要求,现在是float了 - - template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 - format_info: FormatInfo = FormatInfo( - content_format=["text", "image", "emoji"], - accept_format=["text", "image", "emoji", "reply", "voice", "command"], - ) # 格式化信息 - if message_type == MessageType.private: - sub_type = raw_message.get("sub_type") - if sub_type == MessageType.Private.friend: - sender_info: dict = raw_message.get("sender") - - if not self.check_allow_to_chat(sender_info.get("user_id"), None): - return None - - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=sender_info.get("nickname"), - user_cardname=sender_info.get("card"), - ) - - # 不存在群信息 - group_info: GroupInfo = None - elif sub_type == MessageType.Private.group: - """ - 本部分暂时不做支持,先放着 - """ - logger.warning("群临时消息类型不支持") - return None - - sender_info: dict = raw_message.get("sender") - - # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取 - fetched_member_info: dict = await get_member_info( - self.server_connection, - raw_message.get("group_id"), - sender_info.get("user_id"), - ) - nickname = fetched_member_info.get("nickname") if fetched_member_info else None - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=nickname, - user_cardname=None, - ) - - # -------------------这里需要群信息吗?------------------- - - # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 - fetched_group_info: dict = await get_group_info(self.server_connection, raw_message.get("group_id")) - group_name = "" - if fetched_group_info.get("group_name"): - group_name = fetched_group_info.get("group_name") - - group_info: GroupInfo = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=raw_message.get("group_id"), - group_name=group_name, - ) - - else: - logger.warning(f"私聊消息类型 {sub_type} 不支持") - return None - elif message_type == MessageType.group: - sub_type = raw_message.get("sub_type") - if sub_type == MessageType.Group.normal: - sender_info: dict = raw_message.get("sender") - - if not self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")): - return None - - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=sender_info.get("nickname"), - user_cardname=sender_info.get("card"), - ) - - # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 - fetched_group_info = await get_group_info(self.server_connection, raw_message.get("group_id")) - group_name: str = None - if fetched_group_info: - group_name = fetched_group_info.get("group_name") - - group_info: GroupInfo = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=raw_message.get("group_id"), - group_name=group_name, - ) - - else: - logger.warning(f"群聊消息类型 {sub_type} 不支持") - return None - - additional_config: dict = {} - if global_config.voice.use_tts: - additional_config["allow_tts"] = True - - # 消息信息 - message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.maibot_server.platform_name, - message_id=message_id, - time=message_time, - user_info=user_info, - group_info=group_info, - template_info=template_info, - format_info=format_info, - additional_config=additional_config, - ) - - # 处理实际信息 - if not raw_message.get("message"): - logger.warning("原始消息内容为空") - return None - - # 获取Seg列表 - seg_message: List[Seg] = await self.handle_real_message(raw_message) - if not seg_message: - logger.warning("处理后消息内容为空") - return None - submit_seg: Seg = Seg( - type="seglist", - data=seg_message, - ) - # MessageBase创建 - message_base: MessageBase = MessageBase( - message_info=message_info, - message_segment=submit_seg, - raw_message=raw_message.get("raw_message"), - ) - - logger.info("发送到Maibot处理信息") - await self.message_process(message_base) - - async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None: - # sourcery skip: low-code-quality - """ - 处理实际消息 - Parameters: - real_message: dict: 实际消息 - Returns: - seg_message: list[Seg]: 处理后的消息段列表 - """ - real_message: list = raw_message.get("message") - if not real_message: - return None - seg_message: List[Seg] = [] - for sub_message in real_message: - sub_message: dict - sub_message_type = sub_message.get("type") - match sub_message_type: - case RealMessageType.text: - ret_seg = await self.handle_text_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("text处理失败") - case RealMessageType.face: - ret_seg = await self.handle_face_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("face处理失败或不支持") - case RealMessageType.reply: - if not in_reply: - ret_seg = await self.handle_reply_message(sub_message) - if ret_seg: - seg_message += ret_seg - else: - logger.warning("reply处理失败") - case RealMessageType.image: - ret_seg = await self.handle_image_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("image处理失败") - case RealMessageType.record: - logger.warning("不支持语音解析") - case RealMessageType.video: - logger.warning("不支持视频解析") - case RealMessageType.at: - ret_seg = await self.handle_at_message( - sub_message, - raw_message.get("self_id"), - raw_message.get("group_id"), - ) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("at处理失败") - case RealMessageType.rps: - logger.warning("暂时不支持猜拳魔法表情解析") - case RealMessageType.dice: - logger.warning("暂时不支持骰子表情解析") - case RealMessageType.shake: - # 预计等价于戳一戳 - logger.warning("暂时不支持窗口抖动解析") - case RealMessageType.share: - logger.warning("暂时不支持链接解析") - case RealMessageType.forward: - messages = await self.get_forward_message(sub_message) - if not messages: - logger.warning("转发消息内容为空或获取失败") - return None - ret_seg = await self.handle_forward_message(messages) - if ret_seg: - seg_message.append(ret_seg) - else: - logger.warning("转发消息处理失败") - case RealMessageType.node: - logger.warning("不支持转发消息节点解析") - case _: - logger.warning(f"未知消息类型: {sub_message_type}") - return seg_message - - async def handle_text_message(self, raw_message: dict) -> Seg: - """ - 处理纯文本信息 - Parameters: - raw_message: dict: 原始消息 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - plain_text: str = message_data.get("text") - return Seg(type="text", data=plain_text) - - async def handle_face_message(self, raw_message: dict) -> Seg | None: - """ - 处理表情消息 - Parameters: - raw_message: dict: 原始消息 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - face_raw_id: str = str(message_data.get("id")) - if face_raw_id in qq_face: - face_content: str = qq_face.get(face_raw_id) - return Seg(type="text", data=face_content) - else: - logger.warning(f"不支持的表情:{face_raw_id}") - return None - - async def handle_image_message(self, raw_message: dict) -> Seg | None: - """ - 处理图片消息与表情包消息 - Parameters: - raw_message: dict: 原始消息 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - image_sub_type = message_data.get("sub_type") - try: - image_base64 = await get_image_base64(message_data.get("url")) - except Exception as e: - logger.error(f"图片消息处理失败: {str(e)}") - return None - if image_sub_type == 0: - """这部分认为是图片""" - return Seg(type="image", data=image_base64) - elif image_sub_type == 1: - """这部分认为是表情包""" - return Seg(type="emoji", data=image_base64) - else: - logger.warning(f"不支持的图片子类型:{image_sub_type}") - return None - - async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int) -> Seg | None: - # sourcery skip: use-named-expression - """ - 处理at消息 - Parameters: - raw_message: dict: 原始消息 - self_id: int: 机器人QQ号 - group_id: int: 群号 - Returns: - seg_data: Seg: 处理后的消息段 - """ - message_data: dict = raw_message.get("data") - if message_data: - qq_id = message_data.get("qq") - if str(self_id) == str(qq_id): - logger.debug("机器人被at") - self_info: dict = await get_self_info(self.server_connection) - if self_info: - return Seg(type="text", data=f"@<{self_info.get('nickname')}:{self_info.get('user_id')}>") - else: - return None - else: - member_info: dict = await get_member_info(self.server_connection, group_id=group_id, user_id=qq_id) - if member_info: - return Seg(type="text", data=f"@<{member_info.get('nickname')}:{member_info.get('user_id')}>") - else: - return None - - async def get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: - forward_message_data: Dict = raw_message.get("data") - if not forward_message_data: - logger.warning("转发消息内容为空") - return None - forward_message_id = forward_message_data.get("id") - request_uuid = str(uuid.uuid4()) - payload = json.dumps( - { - "action": "get_forward_msg", - "params": {"message_id": forward_message_id}, - "echo": request_uuid, - } - ) - try: - await self.server_connection.send(payload) - response: dict = await get_response(request_uuid) - except TimeoutError: - logger.error("获取转发消息超时") - return None - except Exception as e: - logger.error(f"获取转发消息失败: {str(e)}") - return None - logger.debug( - f"转发消息原始格式:{json.dumps(response)[:80]}..." - if len(json.dumps(response)) > 80 - else json.dumps(response) - ) - response_data: Dict = response.get("data") - if not response_data: - logger.warning("转发消息内容为空或获取失败") - return None - return response_data.get("messages") - - async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: - # sourcery skip: move-assign-in-block, use-named-expression - """ - 处理回复消息 - - """ - raw_message_data: dict = raw_message.get("data") - message_id: int = None - if raw_message_data: - message_id = raw_message_data.get("id") - else: - return None - message_detail: dict = await get_message_detail(self.server_connection, message_id) - if not message_detail: - logger.warning("获取被引用的消息详情失败") - return None - reply_message = await self.handle_real_message(message_detail, in_reply=True) - if reply_message is None: - reply_message = "(获取发言内容失败)" - sender_info: dict = message_detail.get("sender") - sender_nickname: str = sender_info.get("nickname") - sender_id: str = sender_info.get("user_id") - seg_message: List[Seg] = [] - if not sender_nickname: - logger.warning("无法获取被引用的人的昵称,返回默认值") - seg_message.append(Seg(type="text", data="[回复 未知用户:")) - else: - seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}:{sender_id}>:")) - seg_message += reply_message - seg_message.append(Seg(type="text", data="],说:")) - return seg_message - - async def handle_notice(self, raw_message: dict) -> None: - notice_type = raw_message.get("notice_type") - # message_time: int = raw_message.get("time") - message_time: float = time.time() # 应可乐要求,现在是float了 - - group_id = raw_message.get("group_id") - user_id = raw_message.get("user_id") - target_id = raw_message.get("target_id") - - if not self.check_allow_to_chat(user_id, group_id): - logger.warning("notice消息被丢弃") - return None - - handled_message: Seg = None - - match notice_type: - case NoticeType.friend_recall: - logger.info("好友撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") - case NoticeType.group_recall: - logger.info("群内用户撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") - case NoticeType.notify: - sub_type = raw_message.get("sub_type") - match sub_type: - case NoticeType.Notify.poke: - if global_config.chat.enable_poke: - handled_message: Seg = await self.handle_poke_notify(raw_message) - else: - logger.warning("戳一戳消息被禁用,取消戳一戳处理") - case _: - logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") - case _: - logger.warning(f"不支持的notice类型: {notice_type}") - return None - if not handled_message: - logger.warning("notice处理失败或不支持") - return None - - source_name: str = None - source_cardname: str = None - if group_id: - member_info: dict = await get_member_info(self.server_connection, group_id, user_id) - if member_info: - source_name = member_info.get("nickname") - source_cardname = member_info.get("card") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - else: - stranger_info = await get_stranger_info(self.server_connection, user_id) - if stranger_info: - source_name = stranger_info.get("nickname") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=user_id, - user_nickname=source_name, - user_cardname=source_cardname, - ) - - group_info: GroupInfo = None - if group_id: - fetched_group_info = await get_group_info(self.server_connection, group_id) - group_name: str = None - if fetched_group_info: - group_name = fetched_group_info.get("group_name") - else: - logger.warning("无法获取戳一戳消息所在群的名称") - group_info = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=group_id, - group_name=group_name, - ) - - message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.maibot_server.platform_name, - message_id="notice", - time=message_time, - user_info=user_info, - group_info=group_info, - template_info=None, - format_info=None, - additional_config = {"target_id": target_id} - ) - - message_base: MessageBase = MessageBase( - message_info=message_info, - message_segment=handled_message, - raw_message=json.dumps(raw_message), - ) - - logger.info("发送到Maibot处理通知信息") - await self.message_process(message_base) - - async def handle_poke_notify(self, raw_message: dict) -> Seg | None: - self_info: dict = await get_self_info(self.server_connection) - - if not self_info: - logger.error("自身信息获取失败") - return None - self_id = raw_message.get("self_id") - target_id = raw_message.get("target_id") - group_id = raw_message.get("group_id") - user_id = raw_message.get("user_id") - target_name: str = None - raw_info: list = raw_message.get("raw_info") - # 计算Seg - if self_id == target_id: - target_name = self_info.get("nickname") - user_name = "" - else: - if group_id: - user_info: dict = await get_member_info( - self.server_connection, group_id, user_id - ) - fetched_member_info: dict = await get_member_info( - self.server_connection, group_id, target_id - ) - if user_info: - user_name = user_info.get("nickname") - else: - user_name = "QQ用户" - if fetched_member_info: - target_name = fetched_member_info.get("nickname") - else: - target_name = "QQ用户" - else: - return None - try: - first_txt = raw_info[2].get("txt", "戳了戳") - except Exception as e: - logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") - first_txt = "戳了戳" - - seg_data: Seg = Seg( - type="text", - data=f"{user_name}{first_txt}{target_name}(这是QQ的一个功能,用于提及某人,但没那么明显)", - ) - return seg_data - - async def handle_forward_message(self, message_list: list) -> Seg | None: - """ - 递归处理转发消息,并按照动态方式确定图片处理方式 - Parameters: - message_list: list: 转发消息列表 - """ - handled_message, image_count = await self._handle_forward_message(message_list, 0) - handled_message: Seg - image_count: int - if not handled_message: - return None - if image_count < 5 and image_count > 0: - # 处理图片数量小于5的情况,此时解析图片为base64 - logger.trace("图片数量小于5,开始解析图片为base64") - return await self._recursive_parse_image_seg(handled_message, True) - elif image_count > 0: - logger.trace("图片数量大于等于5,开始解析图片为占位符") - # 处理图片数量大于等于5的情况,此时解析图片为占位符 - return await self._recursive_parse_image_seg(handled_message, False) - else: - # 处理没有图片的情况,此时直接返回 - logger.trace("没有图片,直接返回") - return handled_message - - async def _recursive_parse_image_seg(self, seg_data: Seg, to_image: bool) -> Seg: - # sourcery skip: merge-else-if-into-elif - if to_image: - if seg_data.type == "seglist": - new_seg_list = [] - for i_seg in seg_data.data: - parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) - new_seg_list.append(parsed_seg) - return Seg(type="seglist", data=new_seg_list) - elif seg_data.type == "image": - image_url = seg_data.data - try: - encoded_image = await get_image_base64(image_url) - except Exception as e: - logger.error(f"图片处理失败: {str(e)}") - return Seg(type="text", data="[图片]") - return Seg(type="image", data=encoded_image) - elif seg_data.type == "emoji": - image_url = seg_data.data - try: - encoded_image = await get_image_base64(image_url) - except Exception as e: - logger.error(f"图片处理失败: {str(e)}") - return Seg(type="text", data="[表情包]") - return Seg(type="emoji", data=encoded_image) - else: - logger.trace(f"不处理类型: {seg_data.type}") - return seg_data - else: - if seg_data.type == "seglist": - new_seg_list = [] - for i_seg in seg_data.data: - parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) - new_seg_list.append(parsed_seg) - return Seg(type="seglist", data=new_seg_list) - elif seg_data.type == "image": - return Seg(type="text", data="[图片]") - elif seg_data.type == "emoji": - return Seg(type="text", data="[动画表情]") - else: - logger.trace(f"不处理类型: {seg_data.type}") - return seg_data - - async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[Seg, int] | Tuple[None, int]: - # sourcery skip: low-code-quality - """ - 递归处理实际转发消息 - Parameters: - message_list: list: 转发消息列表,首层对应messages字段,后面对应content字段 - layer: int: 当前层级 - Returns: - seg_data: Seg: 处理后的消息段 - image_count: int: 图片数量 - """ - seg_list: List[Seg] = [] - image_count = 0 - if message_list is None: - return None, 0 - for sub_message in message_list: - sub_message: dict - sender_info: dict = sub_message.get("sender") - user_nickname: str = sender_info.get("nickname", "QQ用户") - user_nickname_str = f"【{user_nickname}】:" - break_seg = Seg(type="text", data="\n") - message_of_sub_message_list: List[Dict[str, Any]] = sub_message.get("message") - if not message_of_sub_message_list: - logger.warning("转发消息内容为空") - continue - message_of_sub_message = message_of_sub_message_list[0] - if message_of_sub_message.get("type") == RealMessageType.forward: - if layer >= 3: - full_seg_data = Seg( - type="text", - data=("--" * layer) + f"【{user_nickname}】:【转发消息】\n", - ) - else: - sub_message_data = message_of_sub_message.get("data") - if not sub_message_data: - continue - contents = sub_message_data.get("content") - seg_data, count = await self._handle_forward_message(contents, layer + 1) - image_count += count - head_tip = Seg( - type="text", - data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n", - ) - full_seg_data = Seg(type="seglist", data=[head_tip, seg_data]) - seg_list.append(full_seg_data) - elif message_of_sub_message.get("type") == RealMessageType.text: - sub_message_data = message_of_sub_message.get("data") - if not sub_message_data: - continue - text_message = sub_message_data.get("text") - seg_data = Seg(type="text", data=text_message) - data_list: List[Any] = [] - if layer > 0: - data_list = [ - Seg(type="text", data=("--" * layer) + user_nickname_str), - seg_data, - break_seg, - ] - else: - data_list = [ - Seg(type="text", data=user_nickname_str), - seg_data, - break_seg, - ] - seg_list.append(Seg(type="seglist", data=data_list)) - elif message_of_sub_message.get("type") == RealMessageType.image: - image_count += 1 - image_data = message_of_sub_message.get("data") - sub_type = image_data.get("sub_type") - image_url = image_data.get("url") - data_list: List[Any] = [] - if sub_type == 0: - seg_data = Seg(type="image", data=image_url) - else: - seg_data = Seg(type="emoji", data=image_url) - if layer > 0: - data_list = [ - Seg(type="text", data=("--" * layer) + user_nickname_str), - seg_data, - break_seg, - ] - else: - data_list = [ - Seg(type="text", data=user_nickname_str), - seg_data, - break_seg, - ] - full_seg_data = Seg(type="seglist", data=data_list) - seg_list.append(full_seg_data) - return Seg(type="seglist", data=seg_list), image_count - - async def message_process(self, message_base: MessageBase) -> None: - try: - send_status = await self.maibot_router.send_message(message_base) - if not send_status: - raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") - except Exception as e: - logger.error(f"发送消息失败: {str(e)}") - logger.error("请检查与MaiBot之间的连接") - return None - - -recv_handler = RecvHandler() +from .logger import logger +from .config import global_config +from .qq_emoji_list import qq_face +import time +import asyncio +import json +import websockets as Server +from typing import List, Tuple, Optional, Dict, Any +import uuid + +from . import MetaEventType, RealMessageType, MessageType, NoticeType +from maim_message import ( + UserInfo, + GroupInfo, + Seg, + BaseMessageInfo, + MessageBase, + TemplateInfo, + FormatInfo, + Router, +) + +from .utils import ( + get_group_info, + get_member_info, + get_image_base64, + get_self_info, + get_stranger_info, + get_message_detail, +) +from .response_pool import get_response + + +class RecvHandler: + maibot_router: Router = None + + def __init__(self): + self.server_connection: Server.ServerConnection = None + self.interval = global_config.napcat_server.heartbeat_interval + self._interval_checking = False + + async def handle_meta_event(self, message: dict) -> None: + event_type = message.get("meta_event_type") + if event_type == MetaEventType.lifecycle: + sub_type = message.get("sub_type") + if sub_type == MetaEventType.Lifecycle.connect: + self_id = message.get("self_id") + self.last_heart_beat = time.time() + logger.info(f"Bot {self_id} 连接成功") + asyncio.create_task(self.check_heartbeat(self_id)) + elif event_type == MetaEventType.heartbeat: + if message["status"].get("online") and message["status"].get("good"): + if not self._interval_checking: + asyncio.create_task(self.check_heartbeat()) + self.last_heart_beat = time.time() + self.interval = message.get("interval") / 1000 + else: + self_id = message.get("self_id") + logger.warning(f"Bot {self_id} Napcat 端异常!") + + async def check_heartbeat(self, id: int) -> None: + self._interval_checking = True + while True: + now_time = time.time() + if now_time - self.last_heart_beat > self.interval * 2: + logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") + break + else: + logger.debug("心跳正常") + await asyncio.sleep(self.interval) + + def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: + # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif + """ + 检查是否允许聊天 + Parameters: + user_id: int: 用户ID + group_id: int: 群ID + Returns: + bool: 是否允许聊天 + """ + logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") + if group_id: + if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: + logger.warning("群聊不在聊天白名单中,消息被丢弃") + return False + elif global_config.chat.group_list_type == "blacklist" and group_id in global_config.chat.group_list: + logger.warning("群聊在聊天黑名单中,消息被丢弃") + return False + else: + if global_config.chat.private_list_type == "whitelist" and user_id not in global_config.chat.private_list: + logger.warning("私聊不在聊天白名单中,消息被丢弃") + return False + elif global_config.chat.private_list_type == "blacklist" and user_id in global_config.chat.private_list: + logger.warning("私聊在聊天黑名单中,消息被丢弃") + return False + if user_id in global_config.chat.ban_user_id: + logger.warning("用户在全局黑名单中,消息被丢弃") + return False + return True + + async def handle_raw_message(self, raw_message: dict) -> None: + # sourcery skip: low-code-quality, remove-unreachable-code + """ + 从Napcat接受的原始消息处理 + + Parameters: + raw_message: dict: 原始消息 + """ + message_type: str = raw_message.get("message_type") + message_id: int = raw_message.get("message_id") + # message_time: int = raw_message.get("time") + message_time: float = time.time() # 应可乐要求,现在是float了 + + template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 + format_info: FormatInfo = FormatInfo( + content_format=["text", "image", "emoji"], + accept_format=["text", "image", "emoji", "reply", "voice", "command"], + ) # 格式化信息 + if message_type == MessageType.private: + sub_type = raw_message.get("sub_type") + if sub_type == MessageType.Private.friend: + sender_info: dict = raw_message.get("sender") + + if not self.check_allow_to_chat(sender_info.get("user_id"), None): + return None + + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=sender_info.get("nickname"), + user_cardname=sender_info.get("card"), + ) + + # 不存在群信息 + group_info: GroupInfo = None + elif sub_type == MessageType.Private.group: + """ + 本部分暂时不做支持,先放着 + """ + logger.warning("群临时消息类型不支持") + return None + + sender_info: dict = raw_message.get("sender") + + # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取 + fetched_member_info: dict = await get_member_info( + self.server_connection, + raw_message.get("group_id"), + sender_info.get("user_id"), + ) + nickname = fetched_member_info.get("nickname") if fetched_member_info else None + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=nickname, + user_cardname=None, + ) + + # -------------------这里需要群信息吗?------------------- + + # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 + fetched_group_info: dict = await get_group_info(self.server_connection, raw_message.get("group_id")) + group_name = "" + if fetched_group_info.get("group_name"): + group_name = fetched_group_info.get("group_name") + + group_info: GroupInfo = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=raw_message.get("group_id"), + group_name=group_name, + ) + + else: + logger.warning(f"私聊消息类型 {sub_type} 不支持") + return None + elif message_type == MessageType.group: + sub_type = raw_message.get("sub_type") + if sub_type == MessageType.Group.normal: + sender_info: dict = raw_message.get("sender") + + if not self.check_allow_to_chat(sender_info.get("user_id"), raw_message.get("group_id")): + return None + + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=sender_info.get("nickname"), + user_cardname=sender_info.get("card"), + ) + + # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 + fetched_group_info = await get_group_info(self.server_connection, raw_message.get("group_id")) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + + group_info: GroupInfo = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=raw_message.get("group_id"), + group_name=group_name, + ) + + else: + logger.warning(f"群聊消息类型 {sub_type} 不支持") + return None + + additional_config: dict = {} + if global_config.voice.use_tts: + additional_config["allow_tts"] = True + + # 消息信息 + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id=message_id, + time=message_time, + user_info=user_info, + group_info=group_info, + template_info=template_info, + format_info=format_info, + additional_config=additional_config, + ) + + # 处理实际信息 + if not raw_message.get("message"): + logger.warning("原始消息内容为空") + return None + + # 获取Seg列表 + seg_message: List[Seg] = await self.handle_real_message(raw_message) + if not seg_message: + logger.warning("处理后消息内容为空") + return None + submit_seg: Seg = Seg( + type="seglist", + data=seg_message, + ) + # MessageBase创建 + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=submit_seg, + raw_message=raw_message.get("raw_message"), + ) + + logger.info("发送到Maibot处理信息") + await self.message_process(message_base) + + async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None: + # sourcery skip: low-code-quality + """ + 处理实际消息 + Parameters: + real_message: dict: 实际消息 + Returns: + seg_message: list[Seg]: 处理后的消息段列表 + """ + real_message: list = raw_message.get("message") + if not real_message: + return None + seg_message: List[Seg] = [] + for sub_message in real_message: + sub_message: dict + sub_message_type = sub_message.get("type") + match sub_message_type: + case RealMessageType.text: + ret_seg = await self.handle_text_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("text处理失败") + case RealMessageType.face: + ret_seg = await self.handle_face_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("face处理失败或不支持") + case RealMessageType.reply: + if not in_reply: + ret_seg = await self.handle_reply_message(sub_message) + if ret_seg: + seg_message += ret_seg + else: + logger.warning("reply处理失败") + case RealMessageType.image: + ret_seg = await self.handle_image_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("image处理失败") + case RealMessageType.record: + logger.warning("不支持语音解析") + case RealMessageType.video: + logger.warning("不支持视频解析") + case RealMessageType.at: + ret_seg = await self.handle_at_message( + sub_message, + raw_message.get("self_id"), + raw_message.get("group_id"), + ) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("at处理失败") + case RealMessageType.rps: + logger.warning("暂时不支持猜拳魔法表情解析") + case RealMessageType.dice: + logger.warning("暂时不支持骰子表情解析") + case RealMessageType.shake: + # 预计等价于戳一戳 + logger.warning("暂时不支持窗口抖动解析") + case RealMessageType.share: + logger.warning("暂时不支持链接解析") + case RealMessageType.forward: + messages = await self.get_forward_message(sub_message) + if not messages: + logger.warning("转发消息内容为空或获取失败") + return None + ret_seg = await self.handle_forward_message(messages) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("转发消息处理失败") + case RealMessageType.node: + logger.warning("不支持转发消息节点解析") + case _: + logger.warning(f"未知消息类型: {sub_message_type}") + return seg_message + + async def handle_text_message(self, raw_message: dict) -> Seg: + """ + 处理纯文本信息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + plain_text: str = message_data.get("text") + return Seg(type="text", data=plain_text) + + async def handle_face_message(self, raw_message: dict) -> Seg | None: + """ + 处理表情消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + face_raw_id: str = str(message_data.get("id")) + if face_raw_id in qq_face: + face_content: str = qq_face.get(face_raw_id) + return Seg(type="text", data=face_content) + else: + logger.warning(f"不支持的表情:{face_raw_id}") + return None + + async def handle_image_message(self, raw_message: dict) -> Seg | None: + """ + 处理图片消息与表情包消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + image_sub_type = message_data.get("sub_type") + try: + image_base64 = await get_image_base64(message_data.get("url")) + except Exception as e: + logger.error(f"图片消息处理失败: {str(e)}") + return None + if image_sub_type == 0: + """这部分认为是图片""" + return Seg(type="image", data=image_base64) + elif image_sub_type == 1: + """这部分认为是表情包""" + return Seg(type="emoji", data=image_base64) + else: + logger.warning(f"不支持的图片子类型:{image_sub_type}") + return None + + async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int) -> Seg | None: + # sourcery skip: use-named-expression + """ + 处理at消息 + Parameters: + raw_message: dict: 原始消息 + self_id: int: 机器人QQ号 + group_id: int: 群号 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + if message_data: + qq_id = message_data.get("qq") + if str(self_id) == str(qq_id): + logger.debug("机器人被at") + self_info: dict = await get_self_info(self.server_connection) + if self_info: + return Seg(type="text", data=f"@<{self_info.get('nickname')}:{self_info.get('user_id')}>") + else: + return None + else: + member_info: dict = await get_member_info(self.server_connection, group_id=group_id, user_id=qq_id) + if member_info: + return Seg(type="text", data=f"@<{member_info.get('nickname')}:{member_info.get('user_id')}>") + else: + return None + + async def get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: + forward_message_data: Dict = raw_message.get("data") + if not forward_message_data: + logger.warning("转发消息内容为空") + return None + forward_message_id = forward_message_data.get("id") + request_uuid = str(uuid.uuid4()) + payload = json.dumps( + { + "action": "get_forward_msg", + "params": {"message_id": forward_message_id}, + "echo": request_uuid, + } + ) + try: + await self.server_connection.send(payload) + response: dict = await get_response(request_uuid) + except TimeoutError: + logger.error("获取转发消息超时") + return None + except Exception as e: + logger.error(f"获取转发消息失败: {str(e)}") + return None + logger.debug( + f"转发消息原始格式:{json.dumps(response)[:80]}..." + if len(json.dumps(response)) > 80 + else json.dumps(response) + ) + response_data: Dict = response.get("data") + if not response_data: + logger.warning("转发消息内容为空或获取失败") + return None + return response_data.get("messages") + + async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: + # sourcery skip: move-assign-in-block, use-named-expression + """ + 处理回复消息 + + """ + raw_message_data: dict = raw_message.get("data") + message_id: int = None + if raw_message_data: + message_id = raw_message_data.get("id") + else: + return None + message_detail: dict = await get_message_detail(self.server_connection, message_id) + if not message_detail: + logger.warning("获取被引用的消息详情失败") + return None + reply_message = await self.handle_real_message(message_detail, in_reply=True) + if reply_message is None: + reply_message = "(获取发言内容失败)" + sender_info: dict = message_detail.get("sender") + sender_nickname: str = sender_info.get("nickname") + sender_id: str = sender_info.get("user_id") + seg_message: List[Seg] = [] + if not sender_nickname: + logger.warning("无法获取被引用的人的昵称,返回默认值") + seg_message.append(Seg(type="text", data="[回复 未知用户:")) + else: + seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}:{sender_id}>:")) + seg_message += reply_message + seg_message.append(Seg(type="text", data="],说:")) + return seg_message + + async def handle_notice(self, raw_message: dict) -> None: + notice_type = raw_message.get("notice_type") + # message_time: int = raw_message.get("time") + message_time: float = time.time() # 应可乐要求,现在是float了 + + group_id = raw_message.get("group_id") + user_id = raw_message.get("user_id") + + if not self.check_allow_to_chat(user_id, group_id): + logger.warning("notice消息被丢弃") + return None + + handled_message: Seg = None + + match notice_type: + case NoticeType.friend_recall: + logger.info("好友撤回一条消息") + logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") + logger.warning("暂时不支持撤回消息处理") + case NoticeType.group_recall: + logger.info("群内用户撤回一条消息") + logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") + logger.warning("暂时不支持撤回消息处理") + case NoticeType.notify: + sub_type = raw_message.get("sub_type") + match sub_type: + case NoticeType.Notify.poke: + if global_config.chat.enable_poke: + handled_message: Seg = await self.handle_poke_notify(raw_message) + else: + logger.warning("戳一戳消息被禁用,取消戳一戳处理") + case _: + logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") + case _: + logger.warning(f"不支持的notice类型: {notice_type}") + return None + if not handled_message: + logger.warning("notice处理失败或不支持") + return None + + source_name: str = None + source_cardname: str = None + if group_id: + member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if member_info: + source_name = member_info.get("nickname") + source_cardname = member_info.get("card") + else: + logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") + source_name = "QQ用户" + else: + stranger_info = await get_stranger_info(self.server_connection, user_id) + if stranger_info: + source_name = stranger_info.get("nickname") + else: + logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") + source_name = "QQ用户" + + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=source_name, + user_cardname=source_cardname, + ) + + group_info: GroupInfo = None + if group_id: + fetched_group_info = await get_group_info(self.server_connection, group_id) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + else: + logger.warning("无法获取戳一戳消息所在群的名称") + group_info = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=group_id, + group_name=group_name, + ) + + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id="notice", + time=message_time, + user_info=user_info, + group_info=group_info, + template_info=None, + format_info=None, + ) + + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=handled_message, + raw_message=json.dumps(raw_message), + ) + + logger.info("发送到Maibot处理通知信息") + await self.message_process(message_base) + + async def handle_poke_notify(self, raw_message: dict) -> Seg | None: + self_info: dict = await get_self_info(self.server_connection) + if not self_info: + logger.error("自身信息获取失败") + return None + self_id = raw_message.get("self_id") + target_id = raw_message.get("target_id") + target_name: str = None + raw_info: list = raw_message.get("raw_info") + # 计算Seg + if self_id == target_id: + target_name = self_info.get("nickname") + else: + return None + try: + first_txt = raw_info[2].get("txt", "戳了戳") + second_txt = raw_info[4].get("txt", "") + except Exception as e: + logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") + first_txt = "戳了戳" + second_txt = "" + """ + # 不启用戳其他人的处理 + else: + # 由于Napcat不支持获取昵称,所以需要单独获取 + group_id = raw_message.get("group_id") + fetched_member_info: dict = await get_member_info( + self.server_connection, group_id, target_id + ) + if fetched_member_info: + target_name = fetched_member_info.get("nickname") + """ + seg_data: Seg = Seg( + type="text", + data=f"{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", + ) + return seg_data + + async def handle_forward_message(self, message_list: list) -> Seg | None: + """ + 递归处理转发消息,并按照动态方式确定图片处理方式 + Parameters: + message_list: list: 转发消息列表 + """ + handled_message, image_count = await self._handle_forward_message(message_list, 0) + handled_message: Seg + image_count: int + if not handled_message: + return None + if image_count < 5 and image_count > 0: + # 处理图片数量小于5的情况,此时解析图片为base64 + logger.trace("图片数量小于5,开始解析图片为base64") + return await self._recursive_parse_image_seg(handled_message, True) + elif image_count > 0: + logger.trace("图片数量大于等于5,开始解析图片为占位符") + # 处理图片数量大于等于5的情况,此时解析图片为占位符 + return await self._recursive_parse_image_seg(handled_message, False) + else: + # 处理没有图片的情况,此时直接返回 + logger.trace("没有图片,直接返回") + return handled_message + + async def _recursive_parse_image_seg(self, seg_data: Seg, to_image: bool) -> Seg: + # sourcery skip: merge-else-if-into-elif + if to_image: + if seg_data.type == "seglist": + new_seg_list = [] + for i_seg in seg_data.data: + parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) + new_seg_list.append(parsed_seg) + return Seg(type="seglist", data=new_seg_list) + elif seg_data.type == "image": + image_url = seg_data.data + try: + encoded_image = await get_image_base64(image_url) + except Exception as e: + logger.error(f"图片处理失败: {str(e)}") + return Seg(type="text", data="[图片]") + return Seg(type="image", data=encoded_image) + elif seg_data.type == "emoji": + image_url = seg_data.data + try: + encoded_image = await get_image_base64(image_url) + except Exception as e: + logger.error(f"图片处理失败: {str(e)}") + return Seg(type="text", data="[表情包]") + return Seg(type="emoji", data=encoded_image) + else: + logger.trace(f"不处理类型: {seg_data.type}") + return seg_data + else: + if seg_data.type == "seglist": + new_seg_list = [] + for i_seg in seg_data.data: + parsed_seg = await self._recursive_parse_image_seg(i_seg, to_image) + new_seg_list.append(parsed_seg) + return Seg(type="seglist", data=new_seg_list) + elif seg_data.type == "image": + return Seg(type="text", data="[图片]") + elif seg_data.type == "emoji": + return Seg(type="text", data="[动画表情]") + else: + logger.trace(f"不处理类型: {seg_data.type}") + return seg_data + + async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple[Seg, int] | Tuple[None, int]: + # sourcery skip: low-code-quality + """ + 递归处理实际转发消息 + Parameters: + message_list: list: 转发消息列表,首层对应messages字段,后面对应content字段 + layer: int: 当前层级 + Returns: + seg_data: Seg: 处理后的消息段 + image_count: int: 图片数量 + """ + seg_list: List[Seg] = [] + image_count = 0 + if message_list is None: + return None, 0 + for sub_message in message_list: + sub_message: dict + sender_info: dict = sub_message.get("sender") + user_nickname: str = sender_info.get("nickname", "QQ用户") + user_nickname_str = f"【{user_nickname}】:" + break_seg = Seg(type="text", data="\n") + message_of_sub_message_list: List[Dict[str, Any]] = sub_message.get("message") + if not message_of_sub_message_list: + logger.warning("转发消息内容为空") + continue + message_of_sub_message = message_of_sub_message_list[0] + if message_of_sub_message.get("type") == RealMessageType.forward: + if layer >= 3: + full_seg_data = Seg( + type="text", + data=("--" * layer) + f"【{user_nickname}】:【转发消息】\n", + ) + else: + sub_message_data = message_of_sub_message.get("data") + if not sub_message_data: + continue + contents = sub_message_data.get("content") + seg_data, count = await self._handle_forward_message(contents, layer + 1) + image_count += count + head_tip = Seg( + type="text", + data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n", + ) + full_seg_data = Seg(type="seglist", data=[head_tip, seg_data]) + seg_list.append(full_seg_data) + elif message_of_sub_message.get("type") == RealMessageType.text: + sub_message_data = message_of_sub_message.get("data") + if not sub_message_data: + continue + text_message = sub_message_data.get("text") + seg_data = Seg(type="text", data=text_message) + data_list: List[Any] = [] + if layer > 0: + data_list = [ + Seg(type="text", data=("--" * layer) + user_nickname_str), + seg_data, + break_seg, + ] + else: + data_list = [ + Seg(type="text", data=user_nickname_str), + seg_data, + break_seg, + ] + seg_list.append(Seg(type="seglist", data=data_list)) + elif message_of_sub_message.get("type") == RealMessageType.image: + image_count += 1 + image_data = message_of_sub_message.get("data") + sub_type = image_data.get("sub_type") + image_url = image_data.get("url") + data_list: List[Any] = [] + if sub_type == 0: + seg_data = Seg(type="image", data=image_url) + else: + seg_data = Seg(type="emoji", data=image_url) + if layer > 0: + data_list = [ + Seg(type="text", data=("--" * layer) + user_nickname_str), + seg_data, + break_seg, + ] + else: + data_list = [ + Seg(type="text", data=user_nickname_str), + seg_data, + break_seg, + ] + full_seg_data = Seg(type="seglist", data=data_list) + seg_list.append(full_seg_data) + return Seg(type="seglist", data=seg_list), image_count + + async def message_process(self, message_base: MessageBase) -> None: + try: + send_status = await self.maibot_router.send_message(message_base) + if not send_status: + raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") + except Exception as e: + logger.error(f"发送消息失败: {str(e)}") + logger.error("请检查与MaiBot之间的连接") + return None + + +recv_handler = RecvHandler() From 6a1a145307fc335ff5fdbfc202c441d3ad9d7ca1 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Tue, 24 Jun 2025 13:58:41 +0800 Subject: [PATCH 027/112] Update recv_handler.py --- src/recv_handler.py | 46 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/recv_handler.py b/src/recv_handler.py index d07cb78..687f693 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -484,6 +484,7 @@ async def handle_notice(self, raw_message: dict) -> None: group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") + target_id = raw_message.get("target_id") if not self.check_allow_to_chat(user_id, group_id): logger.warning("notice消息被丢弃") @@ -564,6 +565,7 @@ async def handle_notice(self, raw_message: dict) -> None: group_info=group_info, template_info=None, format_info=None, + additional_config = {"target_id": target_id}# 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁 ) message_base: MessageBase = MessageBase( @@ -577,39 +579,51 @@ async def handle_notice(self, raw_message: dict) -> None: async def handle_poke_notify(self, raw_message: dict) -> Seg | None: self_info: dict = await get_self_info(self.server_connection) + if not self_info: logger.error("自身信息获取失败") return None self_id = raw_message.get("self_id") target_id = raw_message.get("target_id") + group_id = raw_message.get("group_id") + user_id = raw_message.get("user_id") target_name: str = None raw_info: list = raw_message.get("raw_info") # 计算Seg - if self_id == target_id: + if self_id == target_id: # 现在这里应当是专注于处理私聊戳一戳的,也就是说当私聊里,被戳的是另一方时,不会给这个消息。 target_name = self_info.get("nickname") + user_name = "" # 这样的话应该能保证消息大概是“某某某:戳了戳麦麦”,而不是“某某某:某某某戳了戳麦麦” + + elif self_id == user_id: + return None # 这应当让ada不发送麦麦戳别人的消息,因为这个消息已经被mmc的命令记录了,没必要记第二次。 + else: - return None + if group_id: # 如果是群聊环境,老实说做这一步判定没啥意义,毕竟私聊是没有其他人之间的戳一戳的,但是感觉可以有这个判定来强限制群聊环境 + user_info: dict = await get_member_info( + self.server_connection, group_id, user_id + ) + fetched_member_info: dict = await get_member_info( + self.server_connection, group_id, target_id + ) + if user_info: + user_name = user_info.get("nickname") + else: + user_name = "QQ用户" + if fetched_member_info: + target_name = fetched_member_info.get("nickname") + else: + target_name = "QQ用户" + else: + return None try: first_txt = raw_info[2].get("txt", "戳了戳") - second_txt = raw_info[4].get("txt", "") except Exception as e: logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") first_txt = "戳了戳" - second_txt = "" - """ - # 不启用戳其他人的处理 - else: - # 由于Napcat不支持获取昵称,所以需要单独获取 - group_id = raw_message.get("group_id") - fetched_member_info: dict = await get_member_info( - self.server_connection, group_id, target_id - ) - if fetched_member_info: - target_name = fetched_member_info.get("nickname") - """ + seg_data: Seg = Seg( type="text", - data=f"{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", + data=f"{user_name}{first_txt}{target_name}(这是QQ的一个功能,用于提及某人,但没那么明显)", ) return seg_data From eb062e12586d6d281a6b57d9e10bd97839557024 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 24 Jun 2025 15:10:11 +0800 Subject: [PATCH 028/112] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- template/template_config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/template_config.toml b/template/template_config.toml index 8598260..06ac657 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -1,5 +1,5 @@ [inner] -version = "0.1.0" # 版本号 +version = "0.1.1" # 版本号 # 请勿修改版本号,除非你知道自己在做什么 [nickname] # 现在没用 From 53ded443512ff60074527222d34997045f12f2c6 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Thu, 26 Jun 2025 13:31:30 +0800 Subject: [PATCH 029/112] fix --- src/recv_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/recv_handler.py b/src/recv_handler.py index cde0c2a..4d45052 100644 --- a/src/recv_handler.py +++ b/src/recv_handler.py @@ -107,6 +107,7 @@ async def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bo else: self.bot_id_list[user_id] = False update_bot_id(self.bot_id_list) + user_id = int(user_id) logger.debug("开始检查聊天白名单/黑名单") if group_id: if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: From ca0fc4db1123cc2cbc9ad74cfa0f5308ab718a7c Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 28 Jun 2025 01:54:03 +0800 Subject: [PATCH 030/112] =?UTF-8?q?REFACTOR=20=E4=B8=8E=E7=A6=81=E8=A8=80?= =?UTF-8?q?=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- README.md | 4 +- command_args.md | 2 + data/NapcatAdapter.db | Bin 0 -> 20480 bytes main.py | 18 +- notify_args.md | 40 ++ src/__init__.py | 65 --- src/database.py | 121 +++++ src/recv_handler/__init__.py | 83 +++ .../message_handler.py} | 331 +++--------- src/recv_handler/message_sending.py | 31 ++ src/recv_handler/meta_event_handler.py | 49 ++ src/recv_handler/notice_handler.py | 493 ++++++++++++++++++ src/{ => recv_handler}/qq_emoji_list.py | 0 src/send_handler.py | 8 +- src/utils.py | 116 +++-- 16 files changed, 994 insertions(+), 370 deletions(-) create mode 100644 data/NapcatAdapter.db create mode 100644 notify_args.md create mode 100644 src/database.py create mode 100644 src/recv_handler/__init__.py rename src/{recv_handler.py => recv_handler/message_handler.py} (72%) create mode 100644 src/recv_handler/message_sending.py create mode 100644 src/recv_handler/meta_event_handler.py create mode 100644 src/recv_handler/notice_handler.py rename src/{ => recv_handler}/qq_emoji_list.py (100%) diff --git a/.gitignore b/.gitignore index b2d679d..60f4dc6 100644 --- a/.gitignore +++ b/.gitignore @@ -272,4 +272,5 @@ $RECYCLE.BIN/ config.toml config.toml.back test -data/qq_bot.json \ No newline at end of file +data/qq_bot.json +data/ban_list.json \ No newline at end of file diff --git a/README.md b/README.md index 4615f49..3e76e39 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,6 @@ sequenceDiagram - [x] 群踢人功能 # 特别鸣谢 - 特别感谢[@Maple127667](https://github.com/Maple127667)对本项目代码思路的支持 \ No newline at end of file + 特别感谢[@Maple127667](https://github.com/Maple127667)对本项目代码思路的支持 + + 以及[@墨梓柒](https://github.com/DrSmoothl)对部分代码想法的支持 \ No newline at end of file diff --git a/command_args.md b/command_args.md index 8dff207..01390a7 100644 --- a/command_args.md +++ b/command_args.md @@ -13,6 +13,8 @@ Seg.data: Dict[str, Any] = { } ``` 其中,群聊ID将会通过Group_Info.group_id自动获取。 + +**当`duration`为 0 时相当于解除禁言。** ## 群聊全体禁言 ```python Seg.data: Dict[str, Any] = { diff --git a/data/NapcatAdapter.db b/data/NapcatAdapter.db new file mode 100644 index 0000000000000000000000000000000000000000..53f80c298d672ee9b080a67a4994e31a45414f0b GIT binary patch literal 20480 zcmeI#%TB^T6oBC=H>C-e9gA)z#sws3LU;jD8$-mRh>0X74TNGExhS*-m%8=Icq5m# zWQtc_p!p|hJJXq-&iUHSxxQ+-o+C%I`K0g3x+n@w6Gu`CA(Fw74vwgW<5V;VuG+W$ zwr)}!KELM*A0m~15QY81D!&RkGz1Vp009ILKmY**5I_KdI1AjRGNrnqy|~k%vvimC zpg;8&&fLDA&-_Q*9jbBqq+>R^rfgcL=B@l^ooriDt(E2I;Yu%=Db)j@^-Gd+x-EX2T~gJI#wmrzg+No-C`-RT;&p=#_&+rqnPrvCe<-G!CkI zyYG9m^>|}lQ@ajp`Q7km%Y~<6c%mVI00IagfB*srAb bool: + """ + 检查两个 BanUser 对象是否相同。 + """ + return obj1.user_id == obj2.user_id and obj1.group_id == obj2.group_id + + +class DatabaseManager: + """ + 数据库管理类,负责与数据库交互。 + """ + + def __init__(self): + DATABASE_FILE = os.path.join(os.path.dirname(__file__), "..", "data", "NapcatAdapter.db") + self.sqlite_url = f"sqlite:///{DATABASE_FILE}" # SQLite 数据库 URL + self.engine = create_engine(self.sqlite_url, echo=False) # 创建数据库引擎 + self._ensure_database() # 确保数据库和表已创建 + + def _ensure_database(self) -> None: + """ + 确保数据库和表已创建。 + """ + logger.info("确保数据库文件和表已创建...") + SQLModel.metadata.create_all(self.engine) + logger.success("数据库和表已创建或已存在") + + def update_ban_record(self, ban_list: List[BanUser]) -> None: + """ + 更新禁言列表到数据库。 + 支持在不存在时创建新记录,对于多余的项目自动删除。 + """ + with Session(self.engine) as session: + all_records = session.exec(select(BanUser)).all() + for ban_user in ban_list: + statement = select(BanUser).where( + BanUser.user_id == ban_user.user_id, BanUser.group_id == ban_user.group_id + ) + if existing_record := session.exec(statement).first(): + if existing_record.lift_time == ban_user.lift_time: + logger.debug(f"禁言记录未变更: {existing_record}") + continue + # 更新现有记录的 lift_time + existing_record.lift_time = ban_user.lift_time + session.add(existing_record) + logger.debug(f"更新禁言记录: {existing_record}") + else: + # 创建新记录 + session.add(ban_user) + logger.debug(f"创建新禁言记录: {ban_user}") + # 删除不在 ban_list 中的记录 + for record in all_records: + if not any(is_identical(record, ban_user) for ban_user in ban_list): + session.delete(record) + logger.debug(f"删除禁言记录: {record}") + + session.commit() + logger.info("禁言记录已更新") + + def get_ban_records(self) -> List[BanUser]: + """ + 读取所有禁言记录。 + """ + with Session(self.engine) as session: + statement = select(BanUser) + return session.exec(statement).all() + + def create_ban_record(self, ban_record: BanUser) -> None: + """ + 为特定群组中的用户创建禁言记录。 + 一个简化版本的添加方式,防止 update_ban_record 方法的复杂性。 + 其同时还是简化版的更新方式。 + """ + with Session(self.engine) as session: + session.add(ban_record) + session.commit() + logger.debug(f"创建/更新禁言记录: {ban_record}") + + def delete_ban_record(self, ban_record: BanUser) -> bool: + """ + 删除特定用户在特定群组中的禁言记录。 + 一个简化版本的删除方式,防止 update_ban_record 方法的复杂性。 + """ + user_id = ban_record.user_id + group_id = ban_record.group_id + with Session(self.engine) as session: + statement = select(BanUser).where(BanUser.user_id == user_id, BanUser.group_id == group_id) + if ban_record := session.exec(statement).first(): + session.delete(ban_record) + session.commit() + logger.debug(f"删除禁言记录: {ban_record}") + else: + logger.info(f"未找到禁言记录: user_id: {user_id}, group_id: {group_id}") + + +db_manager = DatabaseManager() diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py new file mode 100644 index 0000000..40f0070 --- /dev/null +++ b/src/recv_handler/__init__.py @@ -0,0 +1,83 @@ +from enum import Enum + + +class MetaEventType: + lifecycle = "lifecycle" # 生命周期 + + class Lifecycle: + connect = "connect" # 生命周期 - WebSocket 连接成功 + + heartbeat = "heartbeat" # 心跳 + + +class MessageType: # 接受消息大类 + private = "private" # 私聊消息 + + class Private: + friend = "friend" # 私聊消息 - 好友 + group = "group" # 私聊消息 - 群临时 + group_self = "group_self" # 私聊消息 - 群中自身发送 + other = "other" # 私聊消息 - 其他 + + group = "group" # 群聊消息 + + class Group: + normal = "normal" # 群聊消息 - 普通 + anonymous = "anonymous" # 群聊消息 - 匿名消息 + notice = "notice" # 群聊消息 - 系统提示 + + +class NoticeType: # 通知事件 + friend_recall = "friend_recall" # 私聊消息撤回 + group_recall = "group_recall" # 群聊消息撤回 + notify = "notify" + group_ban = "group_ban" # 群禁言 + + class Notify: + poke = "poke" # 戳一戳 + + class GroupBan: + ban = "ban" # 禁言 + lift_ban = "lift_ban" # 解除禁言 + + +class RealMessageType: # 实际消息分类 + text = "text" # 纯文本 + face = "face" # qq表情 + image = "image" # 图片 + record = "record" # 语音 + video = "video" # 视频 + at = "at" # @某人 + rps = "rps" # 猜拳魔法表情 + dice = "dice" # 骰子 + shake = "shake" # 私聊窗口抖动(只收) + poke = "poke" # 群聊戳一戳 + share = "share" # 链接分享(json形式) + reply = "reply" # 回复消息 + forward = "forward" # 转发消息 + node = "node" # 转发消息节点 + + +class MessageSentType: + private = "private" + + class Private: + friend = "friend" + group = "group" + + group = "group" + + class Group: + normal = "normal" + + +class CommandType(Enum): + """命令类型""" + + GROUP_BAN = "set_group_ban" # 禁言用户 + GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 + GROUP_KICK = "set_group_kick" # 踢出群聊 + SEND_POKE = "send_poke" # 戳一戳 + + def __str__(self) -> str: + return self.value diff --git a/src/recv_handler.py b/src/recv_handler/message_handler.py similarity index 72% rename from src/recv_handler.py rename to src/recv_handler/message_handler.py index 4d45052..608e486 100644 --- a/src/recv_handler.py +++ b/src/recv_handler/message_handler.py @@ -1,14 +1,22 @@ -from .logger import logger -from .config import global_config +from src.logger import logger +from src.config import global_config +from src.utils import ( + get_group_info, + get_member_info, + get_image_base64, + get_self_info, + get_message_detail, +) from .qq_emoji_list import qq_face +from .message_sending import message_send_instance +from . import RealMessageType, MessageType + import time -import asyncio import json import websockets as Server from typing import List, Tuple, Optional, Dict, Any import uuid -from . import MetaEventType, RealMessageType, MessageType, NoticeType from maim_message import ( UserInfo, GroupInfo, @@ -17,97 +25,54 @@ MessageBase, TemplateInfo, FormatInfo, - Router, ) -from .utils import ( - get_group_info, - get_member_info, - get_image_base64, - get_self_info, - get_stranger_info, - get_message_detail, - read_bot_id, - update_bot_id, -) -from .response_pool import get_response +from src.response_pool import get_response -class RecvHandler: - maibot_router: Router = None +class MessageHandler: def __init__(self): self.server_connection: Server.ServerConnection = None - self.interval = global_config.napcat_server.heartbeat_interval - self._interval_checking = False - self.bot_id_list: Dict[int, bool] = {} - - async def handle_meta_event(self, message: dict) -> None: - event_type = message.get("meta_event_type") - if event_type == MetaEventType.lifecycle: - sub_type = message.get("sub_type") - if sub_type == MetaEventType.Lifecycle.connect: - self_id = message.get("self_id") - self.last_heart_beat = time.time() - logger.info(f"Bot {self_id} 连接成功") - asyncio.create_task(self.check_heartbeat(self_id)) - elif event_type == MetaEventType.heartbeat: - if message["status"].get("online") and message["status"].get("good"): - if not self._interval_checking: - asyncio.create_task(self.check_heartbeat()) - self.last_heart_beat = time.time() - self.interval = message.get("interval") / 1000 - else: - self_id = message.get("self_id") - logger.warning(f"Bot {self_id} Napcat 端异常!") - - async def check_heartbeat(self, id: int) -> None: - self._interval_checking = True - while True: - now_time = time.time() - if now_time - self.last_heart_beat > self.interval * 2: - logger.error(f"Bot {id} 连接已断开,被下线,或者Napcat卡死!") - break - else: - logger.debug("心跳正常") - await asyncio.sleep(self.interval) - - async def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bool: + self.bot_id_list: Dict[str, bool] = {} + + def set_server_connection(self, server_connection: Server.ServerConnection) -> None: + """设置Napcat连接""" + self.server_connection = server_connection + + async def check_allow_to_chat( + self, + user_id: int, + group_id: Optional[int], + ignore_bot: Optional[bool] = False, + ignore_global_list: Optional[bool] = False, + ) -> bool: # sourcery skip: hoist-statement-from-if, merge-else-if-into-elif """ 检查是否允许聊天 Parameters: user_id: int: 用户ID group_id: int: 群ID + ignore_bot: bool: 是否忽略机器人检查 + ignore_global_list: bool: 是否忽略全局黑名单检查 Returns: bool: 是否允许聊天 """ - user_id = str(user_id) logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") - if global_config.chat.ban_qq_bot and group_id: + if global_config.chat.ban_qq_bot and group_id and not ignore_bot: logger.debug("开始判断是否为机器人") - if not self.bot_id_list: - self.bot_id_list = read_bot_id() - if user_id in self.bot_id_list: - if self.bot_id_list[user_id]: - logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃") - return False - else: - member_info = await get_member_info(self.server_connection, group_id, user_id) - if member_info: - is_bot = member_info.get("is_robot") - if is_bot is None: - logger.warning("无法获取用户是否为机器人,默认为不是但是不进行更新") + member_info = await get_member_info(self.server_connection, group_id, user_id) + if member_info: + is_bot = member_info.get("is_robot") + if is_bot is None: + logger.warning("无法获取用户是否为机器人,默认为不是但是不进行更新") + else: + if is_bot: + logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃,新机器人加入拦截名单") + self.bot_id_list[user_id] = True + return False else: - if is_bot: - logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃,新机器人加入拦截名单") - self.bot_id_list[user_id] = True - update_bot_id(self.bot_id_list) - return False - else: - self.bot_id_list[user_id] = False - update_bot_id(self.bot_id_list) - user_id = int(user_id) + self.bot_id_list[user_id] = False logger.debug("开始检查聊天白名单/黑名单") if group_id: if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: @@ -123,7 +88,7 @@ async def check_allow_to_chat(self, user_id: int, group_id: Optional[int]) -> bo elif global_config.chat.private_list_type == "blacklist" and user_id in global_config.chat.private_list: logger.warning("私聊在聊天黑名单中,消息被丢弃") return False - if user_id in global_config.chat.ban_user_id: + if user_id in global_config.chat.ban_user_id and not ignore_global_list: logger.warning("用户在全局黑名单中,消息被丢弃") return False return True @@ -275,7 +240,7 @@ async def handle_raw_message(self, raw_message: dict) -> None: ) logger.info("发送到Maibot处理信息") - await self.message_process(message_base) + await message_send_instance.message_send(message_base) async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None: # sourcery skip: low-code-quality @@ -343,7 +308,7 @@ async def handle_real_message(self, raw_message: dict, in_reply: bool = False) - case RealMessageType.share: logger.warning("暂时不支持链接解析") case RealMessageType.forward: - messages = await self.get_forward_message(sub_message) + messages = await self._get_forward_message(sub_message) if not messages: logger.warning("转发消息内容为空或获取失败") return None @@ -440,40 +405,6 @@ async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int else: return None - async def get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: - forward_message_data: Dict = raw_message.get("data") - if not forward_message_data: - logger.warning("转发消息内容为空") - return None - forward_message_id = forward_message_data.get("id") - request_uuid = str(uuid.uuid4()) - payload = json.dumps( - { - "action": "get_forward_msg", - "params": {"message_id": forward_message_id}, - "echo": request_uuid, - } - ) - try: - await self.server_connection.send(payload) - response: dict = await get_response(request_uuid) - except TimeoutError: - logger.error("获取转发消息超时") - return None - except Exception as e: - logger.error(f"获取转发消息失败: {str(e)}") - return None - logger.debug( - f"转发消息原始格式:{json.dumps(response)[:80]}..." - if len(json.dumps(response)) > 80 - else json.dumps(response) - ) - response_data: Dict = response.get("data") - if not response_data: - logger.warning("转发消息内容为空或获取失败") - return None - return response_data.get("messages") - async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: # sourcery skip: move-assign-in-block, use-named-expression """ @@ -506,142 +437,6 @@ async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: seg_message.append(Seg(type="text", data="],说:")) return seg_message - async def handle_notice(self, raw_message: dict) -> None: - notice_type = raw_message.get("notice_type") - # message_time: int = raw_message.get("time") - message_time: float = time.time() # 应可乐要求,现在是float了 - - group_id = raw_message.get("group_id") - user_id = raw_message.get("user_id") - - if not await self.check_allow_to_chat(user_id, group_id): - logger.warning("notice消息被丢弃") - return None - - handled_message: Seg = None - - match notice_type: - case NoticeType.friend_recall: - logger.info("好友撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") - case NoticeType.group_recall: - logger.info("群内用户撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") - case NoticeType.notify: - sub_type = raw_message.get("sub_type") - match sub_type: - case NoticeType.Notify.poke: - if global_config.chat.enable_poke: - handled_message: Seg = await self.handle_poke_notify(raw_message) - else: - logger.warning("戳一戳消息被禁用,取消戳一戳处理") - case _: - logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") - case _: - logger.warning(f"不支持的notice类型: {notice_type}") - return None - if not handled_message: - logger.warning("notice处理失败或不支持") - return None - - source_name: str = None - source_cardname: str = None - if group_id: - member_info: dict = await get_member_info(self.server_connection, group_id, user_id) - if member_info: - source_name = member_info.get("nickname") - source_cardname = member_info.get("card") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - else: - stranger_info = await get_stranger_info(self.server_connection, user_id) - if stranger_info: - source_name = stranger_info.get("nickname") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=user_id, - user_nickname=source_name, - user_cardname=source_cardname, - ) - - group_info: GroupInfo = None - if group_id: - fetched_group_info = await get_group_info(self.server_connection, group_id) - group_name: str = None - if fetched_group_info: - group_name = fetched_group_info.get("group_name") - else: - logger.warning("无法获取戳一戳消息所在群的名称") - group_info = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=group_id, - group_name=group_name, - ) - - message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.maibot_server.platform_name, - message_id="notice", - time=message_time, - user_info=user_info, - group_info=group_info, - template_info=None, - format_info=None, - ) - - message_base: MessageBase = MessageBase( - message_info=message_info, - message_segment=handled_message, - raw_message=json.dumps(raw_message), - ) - - logger.info("发送到Maibot处理通知信息") - await self.message_process(message_base) - - async def handle_poke_notify(self, raw_message: dict) -> Seg | None: - self_info: dict = await get_self_info(self.server_connection) - if not self_info: - logger.error("自身信息获取失败") - return None - self_id = raw_message.get("self_id") - target_id = raw_message.get("target_id") - target_name: str = None - raw_info: list = raw_message.get("raw_info") - # 计算Seg - if self_id == target_id: - target_name = self_info.get("nickname") - else: - return None - try: - first_txt = raw_info[2].get("txt", "戳了戳") - second_txt = raw_info[4].get("txt", "") - except Exception as e: - logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") - first_txt = "戳了戳" - second_txt = "" - """ - # 不启用戳其他人的处理 - else: - # 由于Napcat不支持获取昵称,所以需要单独获取 - group_id = raw_message.get("group_id") - fetched_member_info: dict = await get_member_info( - self.server_connection, group_id, target_id - ) - if fetched_member_info: - target_name = fetched_member_info.get("nickname") - """ - seg_data: Seg = Seg( - type="text", - data=f"{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", - ) - return seg_data - async def handle_forward_message(self, message_list: list) -> Seg | None: """ 递归处理转发消息,并按照动态方式确定图片处理方式 @@ -800,15 +595,39 @@ async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple seg_list.append(full_seg_data) return Seg(type="seglist", data=seg_list), image_count - async def message_process(self, message_base: MessageBase) -> None: + async def _get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: + forward_message_data: Dict = raw_message.get("data") + if not forward_message_data: + logger.warning("转发消息内容为空") + return None + forward_message_id = forward_message_data.get("id") + request_uuid = str(uuid.uuid4()) + payload = json.dumps( + { + "action": "get_forward_msg", + "params": {"message_id": forward_message_id}, + "echo": request_uuid, + } + ) try: - send_status = await self.maibot_router.send_message(message_base) - if not send_status: - raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") + await self.server_connection.send(payload) + response: dict = await get_response(request_uuid) + except TimeoutError: + logger.error("获取转发消息超时") + return None except Exception as e: - logger.error(f"发送消息失败: {str(e)}") - logger.error("请检查与MaiBot之间的连接") + logger.error(f"获取转发消息失败: {str(e)}") + return None + logger.debug( + f"转发消息原始格式:{json.dumps(response)[:80]}..." + if len(json.dumps(response)) > 80 + else json.dumps(response) + ) + response_data: Dict = response.get("data") + if not response_data: + logger.warning("转发消息内容为空或获取失败") return None + return response_data.get("messages") -recv_handler = RecvHandler() +message_handler = MessageHandler() diff --git a/src/recv_handler/message_sending.py b/src/recv_handler/message_sending.py new file mode 100644 index 0000000..de35399 --- /dev/null +++ b/src/recv_handler/message_sending.py @@ -0,0 +1,31 @@ +from src.logger import logger +from maim_message import MessageBase, Router + + +class MessageSending: + """ + 负责把消息发送到麦麦 + """ + + maibot_router: Router = None + + def __init__(self): + pass + + async def message_send(self, message_base: MessageBase) -> bool: + """ + 发送消息 + Parameters: + message_base: MessageBase: 消息基类,包含发送目标和消息内容等信息 + """ + try: + send_status = await self.maibot_router.send_message(message_base) + if not send_status: + raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") + except Exception as e: + logger.error(f"发送消息失败: {str(e)}") + logger.error("请检查与MaiBot之间的连接") + return send_status + + +message_send_instance = MessageSending() diff --git a/src/recv_handler/meta_event_handler.py b/src/recv_handler/meta_event_handler.py new file mode 100644 index 0000000..bb9efe6 --- /dev/null +++ b/src/recv_handler/meta_event_handler.py @@ -0,0 +1,49 @@ +from src.logger import logger +from src.config import global_config +import time +import asyncio + +from . import MetaEventType + + +class MetaEventHandler: + """ + 处理Meta事件 + """ + + def __init__(self): + self.interval = global_config.napcat_server.heartbeat_interval + self._interval_checking = False + + async def handle_meta_event(self, message: dict) -> None: + event_type = message.get("meta_event_type") + if event_type == MetaEventType.lifecycle: + sub_type = message.get("sub_type") + if sub_type == MetaEventType.Lifecycle.connect: + self_id = message.get("self_id") + self.last_heart_beat = time.time() + logger.info(f"Bot {self_id} 连接成功") + asyncio.create_task(self.check_heartbeat(self_id)) + elif event_type == MetaEventType.heartbeat: + if message["status"].get("online") and message["status"].get("good"): + if not self._interval_checking: + asyncio.create_task(self.check_heartbeat()) + self.last_heart_beat = time.time() + self.interval = message.get("interval") / 1000 + else: + self_id = message.get("self_id") + logger.warning(f"Bot {self_id} Napcat 端异常!") + + async def check_heartbeat(self, id: int) -> None: + self._interval_checking = True + while True: + now_time = time.time() + if now_time - self.last_heart_beat > self.interval * 2: + logger.error(f"Bot {id} 可能发生了连接断开,被下线,或者Napcat卡死!") + break + else: + logger.debug("心跳正常") + await asyncio.sleep(self.interval) + + +meta_event_handler = MetaEventHandler() diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py new file mode 100644 index 0000000..e4bd468 --- /dev/null +++ b/src/recv_handler/notice_handler.py @@ -0,0 +1,493 @@ +import time +import json +import asyncio +import websockets as Server +from typing import Tuple, Optional + +from src.logger import logger +from src.config import global_config +from src.database import BanUser, db_manager, is_identical +from . import NoticeType +from .message_sending import message_send_instance +from .message_handler import message_handler +from maim_message import UserInfo, GroupInfo, Seg, BaseMessageInfo, MessageBase + +from src.utils import ( + get_group_info, + get_member_info, + get_self_info, + get_stranger_info, + read_ban_list, +) + +notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=100) +unsuccessful_notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=3) + + +class NoticeHandler: + banned_list: list[BanUser] = [] # 当前仍在禁言中的用户列表 + lifted_list: list[BanUser] = [] # 已经自然解除禁言 + + def __init__(self): + self.server_connection: Server.ServerConnection = None + + async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: + """设置Napcat连接""" + self.server_connection = server_connection + + self.banned_list, self.lifted_list = await read_ban_list(self.server_connection) + + asyncio.create_task(self.auto_lift_detect()) + asyncio.create_task(self.send_notice()) + asyncio.create_task(self.handle_natural_lift()) + + def _ban_operation(self, group_id: int, user_id: Optional[int] = None, lift_time: Optional[int] = None) -> None: + """ + 将用户禁言记录添加到self.banned_list中 + 如果是全体禁言,则user_id为0 + """ + if user_id is None: + user_id = 0 # 使用0表示全体禁言 + lift_time = -1 + ban_record = BanUser(user_id=user_id, group_id=group_id, lift_time=lift_time) + for record in self.banned_list: + if is_identical(record, ban_record): + self.banned_list.remove(record) + self.banned_list.append(ban_record) + db_manager.create_ban_record(ban_record) # 作为更新 + return + self.banned_list.append(ban_record) + db_manager.create_ban_record(ban_record) # 添加到数据库 + + def _lift_operation(self, group_id: int, user_id: Optional[int]) -> None: + """ + 从self.lifted_group_list中移除已经解除全体禁言的群 + """ + if user_id is None: + user_id = 0 # 使用0表示全体禁言 + ban_record = BanUser(user_id=user_id, group_id=group_id, lift_time=-1) + self.lifted_list.append(ban_record) + db_manager.delete_ban_record(ban_record) # 删除数据库中的记录 + + async def handle_notice(self, raw_message: dict) -> None: + notice_type = raw_message.get("notice_type") + # message_time: int = raw_message.get("time") + message_time: float = time.time() # 应可乐要求,现在是float了 + + group_id = raw_message.get("group_id") + user_id = raw_message.get("user_id") + + # if not await self.check_allow_to_chat(user_id, group_id): + # logger.warning("notice消息被丢弃") + # return None + + handled_message: Seg = None + user_info: UserInfo = None + + match notice_type: + case NoticeType.friend_recall: + logger.info("好友撤回一条消息") + logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") + logger.warning("暂时不支持撤回消息处理") + case NoticeType.group_recall: + logger.info("群内用户撤回一条消息") + logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") + logger.warning("暂时不支持撤回消息处理") + case NoticeType.notify: + sub_type = raw_message.get("sub_type") + match sub_type: + case NoticeType.Notify.poke: + if global_config.chat.enable_poke and await message_handler.check_allow_to_chat( + user_id, group_id, False, False + ): + logger.info("处理戳一戳消息") + handled_message, user_info = await self.handle_poke_notify(raw_message, group_id, user_id) + else: + logger.warning("戳一戳消息被禁用,取消戳一戳处理") + case _: + logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") + case NoticeType.group_ban: + sub_type = raw_message.get("sub_type") + match sub_type: + case NoticeType.GroupBan.ban: + if await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None + logger.info("处理群禁言") + handled_message, user_info = await self.handle_ban_notify(raw_message, group_id) + case NoticeType.GroupBan.lift_ban: + if await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None + logger.info("处理解除群禁言") + handled_message, user_info = await self.handle_lift_ban_notify(raw_message, group_id) + case _: + logger.warning(f"不支持的group_ban类型: {notice_type}.{sub_type}") + case _: + logger.warning(f"不支持的notice类型: {notice_type}") + return None + if not handled_message or not user_info: + logger.warning("notice处理失败或不支持") + return None + + group_info: GroupInfo = None + if group_id: + fetched_group_info = await get_group_info(self.server_connection, group_id) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + else: + logger.warning("无法获取notice消息所在群的名称") + group_info = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=group_id, + group_name=group_name, + ) + + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id="notice", + time=message_time, + user_info=user_info, + group_info=group_info, + template_info=None, + format_info=None, + ) + + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=handled_message, + raw_message=json.dumps(raw_message), + ) + + logger.info("发送到Maibot处理通知信息") + await message_send_instance.message_send(message_base) + + async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: int) -> Tuple[Seg | None, UserInfo]: + self_info: dict = await get_self_info(self.server_connection) + if not self_info: + logger.error("自身信息获取失败") + return None + self_id = raw_message.get("self_id") + target_id = raw_message.get("target_id") + target_name: str = None + raw_info: list = raw_message.get("raw_info") + + # 计算user_info + source_name: str = None + source_cardname: str = None + if group_id: + member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if member_info: + source_name = member_info.get("nickname") + source_cardname = member_info.get("card") + else: + logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") + source_name = "QQ用户" + else: + stranger_info = await get_stranger_info(self.server_connection, user_id) + if stranger_info: + source_name = stranger_info.get("nickname") + else: + logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") + source_name = "QQ用户" + + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=source_name, + user_cardname=source_cardname, + ) + + # 计算Seg + if self_id == target_id: + target_name = self_info.get("nickname") + else: + return None + try: + first_txt = raw_info[2].get("txt", "戳了戳") + second_txt = raw_info[4].get("txt", "") + except Exception as e: + logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") + first_txt = "戳了戳" + second_txt = "" + """ + # 不启用戳其他人的处理 + else: + # 由于Napcat不支持获取昵称,所以需要单独获取 + group_id = raw_message.get("group_id") + fetched_member_info: dict = await get_member_info( + self.server_connection, group_id, target_id + ) + if fetched_member_info: + target_name = fetched_member_info.get("nickname") + """ + seg_data: Seg = Seg( + type="text", + data=f"{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", + ) + return seg_data, user_info + + async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg, UserInfo] | Tuple[None, None]: + if not group_id: + logger.error("群ID不能为空,无法处理禁言通知") + return None, None + + # 计算user_info + operator_id = raw_message.get("operator_id") + operator_nickname: str = None + operator_cardname: str = None + + member_info: dict = await get_member_info(self.server_connection, group_id, operator_id) + if member_info: + operator_nickname = member_info.get("nickname") + operator_cardname = member_info.get("card") + else: + logger.warning("无法获取禁言执行者的昵称,消息可能会无效") + operator_nickname = "QQ用户" + + operator_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=operator_id, + user_nickname=operator_nickname, + user_cardname=operator_cardname, + ) + + # 计算Seg + user_id = raw_message.get("user_id") + banned_user_info: UserInfo = None + user_nickname: str = "QQ用户" + user_cardname: str = None + sub_type: str = None + + duration = raw_message.get("duration") + if duration is None: + logger.error("禁言时长不能为空,无法处理禁言通知") + return None, None + + if user_id == 0: # 为全体禁言 + sub_type: str = "whole_ban" + self._ban_operation(group_id) + else: # 为单人禁言 + # 获取被禁言人的信息 + sub_type: str = "ban" + fetched_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if fetched_member_info: + user_nickname = fetched_member_info.get("nickname") + user_cardname = fetched_member_info.get("card") + banned_user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_nickname, + user_cardname=user_cardname, + ) + self._ban_operation(group_id, user_id, int(time.time() + duration)) + + seg_data: Seg = Seg( + type="notify", + data={ + "sub_type": sub_type, + "duration": duration, + "banned_user_info": banned_user_info, + }, + ) + + return seg_data, operator_info + + async def handle_lift_ban_notify( + self, raw_message: dict, group_id: int + ) -> Tuple[Seg, UserInfo] | Tuple[None, None]: + if not group_id: + logger.error("群ID不能为空,无法处理解除禁言通知") + return None, None + + # 计算user_info + operator_id = raw_message.get("operator_id") + operator_nickname: str = None + operator_cardname: str = None + + member_info: dict = await get_member_info(self.server_connection, group_id, operator_id) + if member_info: + operator_nickname = member_info.get("nickname") + operator_cardname = member_info.get("card") + else: + logger.warning("无法获取解除禁言执行者的昵称,消息可能会无效") + operator_nickname = "QQ用户" + + operator_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=operator_id, + user_nickname=operator_nickname, + user_cardname=operator_cardname, + ) + + # 计算Seg + sub_type: str = None + user_nickname: str = "QQ用户" + user_cardname: str = None + lifted_user_info: UserInfo = None + + user_id = raw_message.get("user_id") + if user_id == 0: # 全体禁言解除 + sub_type = "whole_lift_ban" + self._lift_operation(group_id) + else: # 单人禁言解除 + sub_type = "lift_ban" + # 获取被解除禁言人的信息 + fetched_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if fetched_member_info: + user_nickname = fetched_member_info.get("nickname") + user_cardname = fetched_member_info.get("card") + else: + logger.warning("无法获取解除禁言消息发送者的昵称,消息可能会无效") + lifted_user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_nickname, + user_cardname=user_cardname, + ) + self._lift_operation(group_id, user_id) + + seg_data: Seg = Seg( + type="notify", + data={ + "sub_type": sub_type, + "lifted_user_info": lifted_user_info, + }, + ) + return seg_data, operator_info + + async def handle_natural_lift(self) -> None: + while True: + if len(self.lifted_list) != 0: + lift_record = self.lifted_list.pop() + group_id = lift_record.group_id + user_id = lift_record.user_id + + db_manager.delete_ban_record(lift_record) # 从数据库中删除禁言记录 + + seg_message: Seg = await self.natural_lift(group_id, user_id) + + fetched_group_info = await get_group_info(self.server_connection, group_id) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + else: + logger.warning("无法获取notice消息所在群的名称") + group_info = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=group_id, + group_name=group_name, + ) + + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id="notice", + time=time.time(), + user_info=None, # 自然解除禁言没有操作者 + group_info=group_info, + template_info=None, + format_info=None, + ) + + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=seg_message, + raw_message=json.dumps( + { + "post_type": "notice", + "notice_type": "group_ban", + "sub_type": "lift_ban", + "group_id": group_id, + "user_id": user_id, + "operator_id": None, # 自然解除禁言没有操作者 + } + ), + ) + if notice_queue.full() or unsuccessful_notice_queue.full(): + logger.warning("通知队列已满,可能是多次发送失败,消息丢弃") + else: + await notice_queue.put(message_base) + + await asyncio.sleep(0.5) # 确保队列处理间隔 + else: + await asyncio.sleep(5) # 每5秒检查一次 + + async def natural_lift(self, group_id: int, user_id: int) -> Seg | None: + if not group_id: + logger.error("群ID不能为空,无法处理解除禁言通知") + return None + + if user_id == 0: # 理论上永远不会触发 + return Seg( + type="notify", + data={ + "sub_type": "whole_lift_ban", + "lifted_user_info": None, + }, + ) + + user_nickname: str = "QQ用户" + user_cardname: str = None + fetched_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if fetched_member_info: + user_nickname = fetched_member_info.get("nickname") + user_cardname = fetched_member_info.get("card") + + lifted_user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_nickname, + user_cardname=user_cardname, + ) + + return Seg( + type="notify", + data={ + "sub_type": "lift_ban", + "lifted_user_info": lifted_user_info, + }, + ) + + async def auto_lift_detect(self) -> None: + while True: + for ban_record in self.banned_list: + if ban_record.user_id == 0 or ban_record.lift_time == -1: + continue + if ban_record.lift_time <= int(time.time()): + # 触发自然解除禁言 + logger.info(f"检测到用户 {ban_record.user_id} 在群 {ban_record.group_id} 的禁言已解除") + self.lifted_list.append(ban_record) + self.banned_list.remove(ban_record) + asyncio.sleep(5) + + async def send_notice(self) -> None: + """ + 发送通知消息到Napcat + """ + while True: + if not unsuccessful_notice_queue.empty(): + to_be_send: MessageBase = await unsuccessful_notice_queue.get() + try: + send_status = await message_send_instance.message_send(to_be_send) + if send_status: + unsuccessful_notice_queue.task_done() + else: + await unsuccessful_notice_queue.put(to_be_send) + except Exception as e: + logger.error(f"发送通知消息失败: {str(e)}") + await unsuccessful_notice_queue.put(to_be_send) + asyncio.sleep(0.2) + continue + to_be_send: MessageBase = await notice_queue.get() + try: + send_status = await message_send_instance.message_send(to_be_send) + if send_status: + notice_queue.task_done() + else: + await unsuccessful_notice_queue.put(to_be_send) + except Exception as e: + logger.error(f"发送通知消息失败: {str(e)}") + await unsuccessful_notice_queue.put(to_be_send) + await asyncio.sleep(1) + + +notice_handler = NoticeHandler() diff --git a/src/qq_emoji_list.py b/src/recv_handler/qq_emoji_list.py similarity index 100% rename from src/qq_emoji_list.py rename to src/recv_handler/qq_emoji_list.py diff --git a/src/send_handler.py b/src/send_handler.py index 2a6709e..c375679 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -21,6 +21,10 @@ class SendHandler: def __init__(self): self.server_connection: Server.ServerConnection = None + def set_server_connection(self, server_connection: Server.ServerConnection) -> None: + """设置Napcat连接""" + self.server_connection = server_connection + async def handle_message(self, raw_message_base_dict: dict) -> None: raw_message_base: MessageBase = MessageBase.from_dict(raw_message_base_dict) message_segment: Seg = raw_message_base.message_segment @@ -254,8 +258,8 @@ def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tup duration: int = int(args["duration"]) user_id: int = int(args["qq_id"]) group_id: int = int(group_info.group_id) - if duration <= 0: - raise ValueError("封禁时间必须大于0") + if duration < 0: + raise ValueError("封禁时间必须大于等于0") if not user_id or not group_id: raise ValueError("封禁命令缺少必要参数") if duration > 2592000: diff --git a/src/utils.py b/src/utils.py index 9e9941c..c23ee9f 100644 --- a/src/utils.py +++ b/src/utils.py @@ -2,15 +2,16 @@ import json import base64 import uuid +import urllib3 +import ssl +import io + +from src.database import BanUser, db_manager from .logger import logger from .response_pool import get_response -import urllib3 -import ssl -from pathlib import Path from PIL import Image -import io -import os +from typing import Union, List, Tuple class SSLAdapter(urllib3.PoolManager): @@ -44,6 +45,28 @@ async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> d return socket_response.get("data") +async def get_group_detail_info(websocket: Server.ServerConnection, group_id: int) -> dict: + """ + 获取群详细信息 + + 返回值需要处理可能为空的情况 + """ + logger.debug("获取群详细信息中") + request_uuid = str(uuid.uuid4()) + payload = json.dumps({"action": "get_group_detail_info", "params": {"group_id": group_id}, "echo": request_uuid}) + try: + await websocket.send(payload) + socket_response: dict = await get_response(request_uuid) + except TimeoutError: + logger.error(f"获取群详细信息超时,群号: {group_id}") + return None + except Exception as e: + logger.error(f"获取群详细信息失败: {e}") + return None + logger.debug(socket_response) + return socket_response.get("data") + + async def get_member_info(websocket: Server.ServerConnection, group_id: int, user_id: int) -> dict: """ 获取群成员信息 @@ -171,7 +194,7 @@ async def get_stranger_info(websocket: Server.ServerConnection, user_id: int) -> return response.get("data") -async def get_message_detail(websocket: Server.ServerConnection, message_id: str) -> dict: +async def get_message_detail(websocket: Server.ServerConnection, message_id: Union[str, int]) -> dict: """ 获取消息详情,可能为空 Parameters: @@ -196,41 +219,58 @@ async def get_message_detail(websocket: Server.ServerConnection, message_id: str return response.get("data") -def update_bot_id(data: dict) -> None: +async def read_ban_list( + websocket: Server.ServerConnection, +) -> Tuple[List[BanUser], List[BanUser]]: """ - 更新用户是否为机器人的字典到根目录下的data文件夹中的qq_bot.json。 - Parameters: - data: dict: 包含需要更新的信息。 + 从根目录下的data文件夹中的文件读取禁言列表。 + 同时自动更新已经失效禁言 + Returns: + Tuple[ + 一个仍在禁言中的用户的BanUser列表, + 一个已经自然解除禁言的用户的BanUser列表, + 一个仍在全体禁言中的群的BanUser列表, + 一个已经自然解除全体禁言的群的BanUser列表, + ] """ - json_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "qq_bot.json") try: - with open(json_path, "w", encoding="utf-8") as json_file: - json.dump(data, json_file, ensure_ascii=False, indent=4) - logger.info(f"ID字典已更新到文件: {json_path}") + ban_list = db_manager.get_ban_records() + lifted_list: List[BanUser] = [] + logger.info("已经读取禁言列表") + for ban_record in ban_list: + if ban_record.user_id == 0: + fetched_group_info = await get_group_info(websocket, ban_record.group_id) + if fetched_group_info is None: + logger.warning(f"无法获取群信息,群号: {ban_record.group_id},默认禁言解除") + lifted_list.append(ban_record) + ban_list.remove(ban_record) + continue + group_all_shut: int = fetched_group_info.get("group_all_shut") + if group_all_shut == 0: + lifted_list.append(ban_record) + ban_list.remove(ban_record) + continue + else: + fetched_member_info = await get_member_info(websocket, ban_record.group_id, ban_record.user_id) + if fetched_member_info is None: + logger.warning( + f"无法获取群成员信息,用户ID: {ban_record.user_id}, 群号: {ban_record.group_id},默认禁言解除" + ) + lifted_list.append(ban_record) + ban_list.remove(ban_record) + continue + lift_ban_time: int = fetched_member_info.get("shut_up_timestamp") + if lift_ban_time == 0: + lifted_list.append(ban_record) + ban_list.remove(ban_record) + else: + ban_record.lift_time = lift_ban_time + db_manager.update_ban_record(ban_list) + return ban_list, lifted_list except Exception as e: - logger.error(f"更新ID字典失败: {e}") + logger.error(f"读取禁言列表失败: {e}") + return [], [] -def read_bot_id() -> dict: - """ - 从根目录下的data文件夹中的文件读取机器人ID。 - Returns: - list: 读取的机器人ID信息。 - """ - json_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "qq_bot.json") - try: - with open(json_path, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - logger.info(f"已读取机器人ID信息: {data}") - return data - except FileNotFoundError: - logger.warning(f"文件未找到: {json_path},正在自动创建文件") - json_path = Path(os.path.dirname(os.path.dirname(__file__))) / "data" / "qq_bot.json" - # 确保父目录存在 - json_path.parent.mkdir(parents=True, exist_ok=True) - # 创建空文件 - json_path.touch(exist_ok=True) - return {} - except Exception as e: - logger.error(f"读取机器人ID失败: {e}") - return {} +def save_ban_record(list: List[BanUser]): + return db_manager.update_ban_record(list) From ee873d8cbb50694b91e1c76a83ed2074bbff3c9d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 28 Jun 2025 01:57:46 +0800 Subject: [PATCH 031/112] =?UTF-8?q?fix=20=E7=B1=BB=E5=9E=8B=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 608e486..1f65cf6 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -34,7 +34,7 @@ class MessageHandler: def __init__(self): self.server_connection: Server.ServerConnection = None - self.bot_id_list: Dict[str, bool] = {} + self.bot_id_list: Dict[int, bool] = {} def set_server_connection(self, server_connection: Server.ServerConnection) -> None: """设置Napcat连接""" From 11969095219a87c72174ee64a97dc1207f90b479 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 28 Jun 2025 10:18:24 +0800 Subject: [PATCH 032/112] remove database and update gitignore --- .gitignore | 3 +-- data/NapcatAdapter.db | Bin 20480 -> 0 bytes 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 data/NapcatAdapter.db diff --git a/.gitignore b/.gitignore index 60f4dc6..6c9e2d2 100644 --- a/.gitignore +++ b/.gitignore @@ -272,5 +272,4 @@ $RECYCLE.BIN/ config.toml config.toml.back test -data/qq_bot.json -data/ban_list.json \ No newline at end of file +data/NapcatAdapter.db \ No newline at end of file diff --git a/data/NapcatAdapter.db b/data/NapcatAdapter.db deleted file mode 100644 index 53f80c298d672ee9b080a67a4994e31a45414f0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 zcmeI#%TB^T6oBC=H>C-e9gA)z#sws3LU;jD8$-mRh>0X74TNGExhS*-m%8=Icq5m# zWQtc_p!p|hJJXq-&iUHSxxQ+-o+C%I`K0g3x+n@w6Gu`CA(Fw74vwgW<5V;VuG+W$ zwr)}!KELM*A0m~15QY81D!&RkGz1Vp009ILKmY**5I_KdI1AjRGNrnqy|~k%vvimC zpg;8&&fLDA&-_Q*9jbBqq+>R^rfgcL=B@l^ooriDt(E2I;Yu%=Db)j@^-Gd+x-EX2T~gJI#wmrzg+No-C`-RT;&p=#_&+rqnPrvCe<-G!CkI zyYG9m^>|}lQ@ajp`Q7km%Y~<6c%mVI00IagfB*srAb Date: Sat, 28 Jun 2025 11:44:35 +0800 Subject: [PATCH 033/112] =?UTF-8?q?=E4=BF=AEbug=EF=BC=8C=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++- main.py | 6 ++-- pyproject.toml | 2 +- src/database.py | 54 ++++++++++++++++++++++------- src/recv_handler/message_handler.py | 4 +-- src/recv_handler/message_sending.py | 2 +- src/recv_handler/notice_handler.py | 43 +++++++++++++++-------- src/send_handler.py | 2 +- 8 files changed, 80 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 6c9e2d2..f0977e5 100644 --- a/.gitignore +++ b/.gitignore @@ -272,4 +272,6 @@ $RECYCLE.BIN/ config.toml config.toml.back test -data/NapcatAdapter.db \ No newline at end of file +data/NapcatAdapter.db +data/NapcatAdapter.db-shm +data/NapcatAdapter.db-wal \ No newline at end of file diff --git a/main.py b/main.py index 12657af..a928191 100644 --- a/main.py +++ b/main.py @@ -16,9 +16,9 @@ async def message_recv(server_connection: Server.ServerConnection): - message_handler.set_server_connection(server_connection) - notice_handler.set_server_connection(server_connection) - send_handler.set_server_connection(server_connection) + await message_handler.set_server_connection(server_connection) + asyncio.create_task(notice_handler.set_server_connection(server_connection)) + await send_handler.set_server_connection(server_connection) async for raw_message in server_connection: logger.debug( f"{raw_message[:100]}..." diff --git a/pyproject.toml b/pyproject.toml index 0fedfb2..2f6423d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.2.6" +version = "0.3.0" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/database.py b/src/database.py index 2f58b50..45100cb 100644 --- a/src/database.py +++ b/src/database.py @@ -1,5 +1,6 @@ import os from typing import Optional, List +from dataclasses import dataclass from sqlmodel import Field, Session, SQLModel, create_engine, select from src.logger import logger @@ -13,7 +14,18 @@ """ -class BanUser(SQLModel, table=True): +@dataclass +class BanUser: + """ + 程序处理使用的实例 + """ + + user_id: int + group_id: int + lift_time: Optional[int] = Field(default=-1) + + +class DB_BanUser(SQLModel, table=True): """ 表示数据库中的用户禁言记录。 使用双重主键 @@ -24,7 +36,7 @@ class BanUser(SQLModel, table=True): lift_time: Optional[int] # 禁言解除的时间(时间戳) -def is_identical(self, obj1: BanUser, obj2: BanUser) -> bool: +def is_identical(obj1: BanUser, obj2: BanUser) -> bool: """ 检查两个 BanUser 对象是否相同。 """ @@ -51,15 +63,16 @@ def _ensure_database(self) -> None: logger.success("数据库和表已创建或已存在") def update_ban_record(self, ban_list: List[BanUser]) -> None: + # sourcery skip: class-extract-method """ 更新禁言列表到数据库。 支持在不存在时创建新记录,对于多余的项目自动删除。 """ with Session(self.engine) as session: - all_records = session.exec(select(BanUser)).all() + all_records = session.exec(select(DB_BanUser)).all() for ban_user in ban_list: - statement = select(BanUser).where( - BanUser.user_id == ban_user.user_id, BanUser.group_id == ban_user.group_id + statement = select(DB_BanUser).where( + DB_BanUser.user_id == ban_user.user_id, DB_BanUser.group_id == ban_user.group_id ) if existing_record := session.exec(statement).first(): if existing_record.lift_time == ban_user.lift_time: @@ -71,13 +84,24 @@ def update_ban_record(self, ban_list: List[BanUser]) -> None: logger.debug(f"更新禁言记录: {existing_record}") else: # 创建新记录 - session.add(ban_user) + db_record = DB_BanUser( + user_id=ban_user.user_id, group_id=ban_user.group_id, lift_time=ban_user.lift_time + ) + session.add(db_record) logger.debug(f"创建新禁言记录: {ban_user}") # 删除不在 ban_list 中的记录 - for record in all_records: + for db_record in all_records: + record = BanUser(user_id=db_record.user_id, group_id=db_record.group_id, lift_time=db_record.lift_time) if not any(is_identical(record, ban_user) for ban_user in ban_list): - session.delete(record) - logger.debug(f"删除禁言记录: {record}") + statement = select(DB_BanUser).where( + DB_BanUser.user_id == record.user_id, DB_BanUser.group_id == record.group_id + ) + if ban_record := session.exec(statement).first(): + session.delete(ban_record) + session.commit() + logger.debug(f"删除禁言记录: {ban_record}") + else: + logger.info(f"未找到禁言记录: {ban_record}") session.commit() logger.info("禁言记录已更新") @@ -87,8 +111,9 @@ def get_ban_records(self) -> List[BanUser]: 读取所有禁言记录。 """ with Session(self.engine) as session: - statement = select(BanUser) - return session.exec(statement).all() + statement = select(DB_BanUser) + records = session.exec(statement).all() + return [BanUser(user_id=item.user_id, group_id=item.group_id, lift_time=item.lift_time) for item in records] def create_ban_record(self, ban_record: BanUser) -> None: """ @@ -97,7 +122,10 @@ def create_ban_record(self, ban_record: BanUser) -> None: 其同时还是简化版的更新方式。 """ with Session(self.engine) as session: - session.add(ban_record) + db_record = DB_BanUser( + user_id=ban_record.user_id, group_id=ban_record.group_id, lift_time=ban_record.lift_time + ) + session.add(db_record) session.commit() logger.debug(f"创建/更新禁言记录: {ban_record}") @@ -109,7 +137,7 @@ def delete_ban_record(self, ban_record: BanUser) -> bool: user_id = ban_record.user_id group_id = ban_record.group_id with Session(self.engine) as session: - statement = select(BanUser).where(BanUser.user_id == user_id, BanUser.group_id == group_id) + statement = select(DB_BanUser).where(DB_BanUser.user_id == user_id, DB_BanUser.group_id == group_id) if ban_record := session.exec(statement).first(): session.delete(ban_record) session.commit() diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 1f65cf6..0cec56f 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -36,14 +36,14 @@ def __init__(self): self.server_connection: Server.ServerConnection = None self.bot_id_list: Dict[int, bool] = {} - def set_server_connection(self, server_connection: Server.ServerConnection) -> None: + async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: """设置Napcat连接""" self.server_connection = server_connection async def check_allow_to_chat( self, user_id: int, - group_id: Optional[int], + group_id: Optional[int] = None, ignore_bot: Optional[bool] = False, ignore_global_list: Optional[bool] = False, ) -> bool: diff --git a/src/recv_handler/message_sending.py b/src/recv_handler/message_sending.py index de35399..2e43bbd 100644 --- a/src/recv_handler/message_sending.py +++ b/src/recv_handler/message_sending.py @@ -21,7 +21,7 @@ async def message_send(self, message_base: MessageBase) -> bool: try: send_status = await self.maibot_router.send_message(message_base) if not send_status: - raise RuntimeError("发送消息失败,可能是路由未正确配置或连接异常") + raise RuntimeError("可能是路由未正确配置或连接异常") except Exception as e: logger.error(f"发送消息失败: {str(e)}") logger.error("请检查与MaiBot之间的连接") diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index e4bd468..2d03a49 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -35,6 +35,8 @@ async def set_server_connection(self, server_connection: Server.ServerConnection """设置Napcat连接""" self.server_connection = server_connection + while self.server_connection.state != Server.State.OPEN: + await asyncio.sleep(0.5) self.banned_list, self.lifted_list = await read_ban_list(self.server_connection) asyncio.create_task(self.auto_lift_detect()) @@ -59,7 +61,7 @@ def _ban_operation(self, group_id: int, user_id: Optional[int] = None, lift_time self.banned_list.append(ban_record) db_manager.create_ban_record(ban_record) # 添加到数据库 - def _lift_operation(self, group_id: int, user_id: Optional[int]) -> None: + def _lift_operation(self, group_id: int, user_id: Optional[int] = None) -> None: """ 从self.lifted_group_list中移除已经解除全体禁言的群 """ @@ -77,12 +79,9 @@ async def handle_notice(self, raw_message: dict) -> None: group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") - # if not await self.check_allow_to_chat(user_id, group_id): - # logger.warning("notice消息被丢弃") - # return None - handled_message: Seg = None user_info: UserInfo = None + system_notice: bool = False match notice_type: case NoticeType.friend_recall: @@ -110,15 +109,17 @@ async def handle_notice(self, raw_message: dict) -> None: sub_type = raw_message.get("sub_type") match sub_type: case NoticeType.GroupBan.ban: - if await message_handler.check_allow_to_chat(user_id, group_id, True, False): + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): return None logger.info("处理群禁言") handled_message, user_info = await self.handle_ban_notify(raw_message, group_id) + system_notice = True case NoticeType.GroupBan.lift_ban: - if await message_handler.check_allow_to_chat(user_id, group_id, True, False): + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): return None logger.info("处理解除群禁言") handled_message, user_info = await self.handle_lift_ban_notify(raw_message, group_id) + system_notice = True case _: logger.warning(f"不支持的group_ban类型: {notice_type}.{sub_type}") case _: @@ -158,8 +159,11 @@ async def handle_notice(self, raw_message: dict) -> None: raw_message=json.dumps(raw_message), ) - logger.info("发送到Maibot处理通知信息") - await message_send_instance.message_send(message_base) + if system_notice: + await self.put_notice(message_base) + else: + logger.info("发送到Maibot处理通知信息") + await message_send_instance.message_send(message_base) async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: int) -> Tuple[Seg | None, UserInfo]: self_info: dict = await get_self_info(self.server_connection) @@ -355,6 +359,15 @@ async def handle_lift_ban_notify( ) return seg_data, operator_info + async def put_notice(self, message_base: MessageBase) -> None: + """ + 将处理后的通知消息放入通知队列 + """ + if notice_queue.full() or unsuccessful_notice_queue.full(): + logger.warning("通知队列已满,可能是多次发送失败,消息丢弃") + else: + await notice_queue.put(message_base) + async def handle_natural_lift(self) -> None: while True: if len(self.lifted_list) != 0: @@ -402,11 +415,8 @@ async def handle_natural_lift(self) -> None: } ), ) - if notice_queue.full() or unsuccessful_notice_queue.full(): - logger.warning("通知队列已满,可能是多次发送失败,消息丢弃") - else: - await notice_queue.put(message_base) + await self.put_notice(message_base) await asyncio.sleep(0.5) # 确保队列处理间隔 else: await asyncio.sleep(5) # 每5秒检查一次 @@ -449,6 +459,9 @@ async def natural_lift(self, group_id: int, user_id: int) -> Seg | None: async def auto_lift_detect(self) -> None: while True: + if len(self.banned_list) == 0: + await asyncio.sleep(5) + continue for ban_record in self.banned_list: if ban_record.user_id == 0 or ban_record.lift_time == -1: continue @@ -457,7 +470,7 @@ async def auto_lift_detect(self) -> None: logger.info(f"检测到用户 {ban_record.user_id} 在群 {ban_record.group_id} 的禁言已解除") self.lifted_list.append(ban_record) self.banned_list.remove(ban_record) - asyncio.sleep(5) + await asyncio.sleep(5) async def send_notice(self) -> None: """ @@ -475,7 +488,7 @@ async def send_notice(self) -> None: except Exception as e: logger.error(f"发送通知消息失败: {str(e)}") await unsuccessful_notice_queue.put(to_be_send) - asyncio.sleep(0.2) + await asyncio.sleep(1) continue to_be_send: MessageBase = await notice_queue.get() try: diff --git a/src/send_handler.py b/src/send_handler.py index c375679..6cb3094 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -21,7 +21,7 @@ class SendHandler: def __init__(self): self.server_connection: Server.ServerConnection = None - def set_server_connection(self, server_connection: Server.ServerConnection) -> None: + async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: """设置Napcat连接""" self.server_connection = server_connection From ed9ecae9dcfbcd6e22efd04e8aec23b353a0c12a Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 28 Jun 2025 12:23:50 +0800 Subject: [PATCH 034/112] =?UTF-8?q?maim=5Fmessage=20logger=E4=BC=A0?= =?UTF-8?q?=E5=85=A5=EF=BC=8C=E7=89=88=E6=9C=AC=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 1 - src/__init__.py | 9 +++++++++ src/mmc_com_layer.py | 2 +- src/utils.py | 12 ++++++------ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index c0ad7e0..54d8729 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,5 @@ requests maim_message loguru pillow -tomli tomlkit rich \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py index 4298de2..b1ac77e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,4 +1,7 @@ from enum import Enum +import tomlkit +import os +from .logger import logger class CommandType(Enum): @@ -11,3 +14,9 @@ class CommandType(Enum): def __str__(self) -> str: return self.value + + +pyproject_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pyproject.toml") +toml_data = tomlkit.parse(open(pyproject_path, "r", encoding="utf-8").read()) +version = toml_data["project"]["version"] +logger.info(f"版本\n\nMaiBot-Napcat-Adapter 版本: {version}\n") diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index ab50cca..f7fd1ad 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -11,7 +11,7 @@ ) } ) -router = Router(route_config) +router = Router(route_config, logger) async def mmc_start_com(): diff --git a/src/utils.py b/src/utils.py index c23ee9f..caa0b56 100644 --- a/src/utils.py +++ b/src/utils.py @@ -23,7 +23,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> dict: +async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> dict | None: """ 获取群相关信息 @@ -45,7 +45,7 @@ async def get_group_info(websocket: Server.ServerConnection, group_id: int) -> d return socket_response.get("data") -async def get_group_detail_info(websocket: Server.ServerConnection, group_id: int) -> dict: +async def get_group_detail_info(websocket: Server.ServerConnection, group_id: int) -> dict | None: """ 获取群详细信息 @@ -67,7 +67,7 @@ async def get_group_detail_info(websocket: Server.ServerConnection, group_id: in return socket_response.get("data") -async def get_member_info(websocket: Server.ServerConnection, group_id: int, user_id: int) -> dict: +async def get_member_info(websocket: Server.ServerConnection, group_id: int, user_id: int) -> dict | None: """ 获取群成员信息 @@ -133,7 +133,7 @@ def convert_image_to_gif(image_base64: str) -> str: return image_base64 -async def get_self_info(websocket: Server.ServerConnection) -> dict: +async def get_self_info(websocket: Server.ServerConnection) -> dict | None: """ 获取自身信息 Parameters: @@ -169,7 +169,7 @@ def get_image_format(raw_data: str) -> str: return Image.open(io.BytesIO(image_bytes)).format.lower() -async def get_stranger_info(websocket: Server.ServerConnection, user_id: int) -> dict: +async def get_stranger_info(websocket: Server.ServerConnection, user_id: int) -> dict | None: """ 获取陌生人信息 Parameters: @@ -194,7 +194,7 @@ async def get_stranger_info(websocket: Server.ServerConnection, user_id: int) -> return response.get("data") -async def get_message_detail(websocket: Server.ServerConnection, message_id: Union[str, int]) -> dict: +async def get_message_detail(websocket: Server.ServerConnection, message_id: Union[str, int]) -> dict | None: """ 获取消息详情,可能为空 Parameters: From 5c6cdddbd3ba039b9edaaa4cf65489f222a6f4b4 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 14:21:42 +0800 Subject: [PATCH 035/112] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=92=A4?= =?UTF-8?q?=E5=9B=9E=E6=B6=88=E6=81=AF=E6=8C=87=E4=BB=A4=EF=BC=9B=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86=E5=AF=B9=E8=87=AA=E8=BA=AB=E4=B8=8A=E6=8A=A5?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E7=9A=84=E5=A4=84=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 5 +- src/__init__.py | 1 + src/recv_handler/__init__.py | 1 + src/recv_handler/message_sent_handler.py | 173 +++++++++++++++++++++++ src/send_handler.py | 25 ++++ 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/recv_handler/message_sent_handler.py diff --git a/main.py b/main.py index a928191..652d6b8 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ from src.recv_handler.message_handler import message_handler from src.recv_handler.meta_event_handler import meta_event_handler from src.recv_handler.notice_handler import notice_handler +from src.recv_handler.message_sent_handler import message_sent_handler from src.recv_handler.message_sending import message_send_instance from src.send_handler import send_handler from src.config import global_config @@ -27,7 +28,7 @@ async def message_recv(server_connection: Server.ServerConnection): ) decoded_raw_message: dict = json.loads(raw_message) post_type = decoded_raw_message.get("post_type") - if post_type in ["meta_event", "message", "notice"]: + if post_type in ["meta_event", "message", "notice","message_sent"]: await message_queue.put(decoded_raw_message) elif post_type is None: await put_response(decoded_raw_message) @@ -43,6 +44,8 @@ async def message_process(): await meta_event_handler.handle_meta_event(message) elif post_type == "notice": await notice_handler.handle_notice(message) + elif post_type == "message_sent": + await message_sent_handler.handle_sent_message(message) else: logger.warning(f"未知的post_type: {post_type}") message_queue.task_done() diff --git a/src/__init__.py b/src/__init__.py index b1ac77e..0ac0aa5 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -11,6 +11,7 @@ class CommandType(Enum): GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 GROUP_KICK = "set_group_kick" # 踢出群聊 SEND_POKE = "send_poke" # 戳一戳 + DELETE_MSG = "delete_msg" # 撤回消息 def __str__(self) -> str: return self.value diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 40f0070..5bd1b74 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -78,6 +78,7 @@ class CommandType(Enum): GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 GROUP_KICK = "set_group_kick" # 踢出群聊 SEND_POKE = "send_poke" # 戳一戳 + DELETE_MSG = "delete_msg" # 撤回消息 def __str__(self) -> str: return self.value diff --git a/src/recv_handler/message_sent_handler.py b/src/recv_handler/message_sent_handler.py new file mode 100644 index 0000000..be600d9 --- /dev/null +++ b/src/recv_handler/message_sent_handler.py @@ -0,0 +1,173 @@ +from src.logger import logger +from src.config import global_config +from src.utils import ( + get_group_info, + get_member_info, +) +from .message_sending import message_send_instance +from . import MessageSentType +from .message_handler import message_handler + +import time +import websockets as Server +from typing import List, Dict + +from maim_message import ( + UserInfo, + GroupInfo, + Seg, + BaseMessageInfo, + MessageBase, + TemplateInfo, + FormatInfo, +) + +class MessageSentHandler: + def __init__(self): + self.server_connection: Server.ServerConnection = None + self.bot_id_list: Dict[int, bool] = {} + + async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: + """设置Napcat连接""" + self.server_connection = server_connection + + async def handle_sent_message(self, raw_message: dict) -> None: + """ + 从Napcat接受的原始发送消息处理 + + Parameters: + raw_message: dict: 原始消息 + """ + message_type: str = raw_message.get("message_type") + message_id: int = raw_message.get("message_id") + message_time: float = time.time() + + template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 + format_info: FormatInfo = FormatInfo( + content_format=["text", "image", "emoji"], + accept_format=["text", "image", "emoji", "reply"], + ) # 上报信息暂时只支持这四种解析 + if message_type == MessageSentType.private: + sub_type = raw_message.get("sub_type") + if sub_type == MessageSentType.Private.friend: + sender_info: dict = raw_message.get("sender") + + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=sender_info.get("nickname"), + user_cardname=sender_info.get("card"), + ) + + # 不存在群信息 + group_info: GroupInfo = None + elif sub_type == MessageSentType.Private.group: + """ + 本部分暂时不做支持,先放着 + """ + logger.warning("群临时消息类型不支持") + return None + + sender_info: dict = raw_message.get("sender") + + # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取 + fetched_member_info: dict = await get_member_info( + self.server_connection, + raw_message.get("group_id"), + sender_info.get("user_id"), + ) + nickname = fetched_member_info.get("nickname") if fetched_member_info else None + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=nickname, + user_cardname=None, + ) + + # -------------------这里需要群信息吗?------------------- + + # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 + fetched_group_info: dict = await get_group_info(self.server_connection, raw_message.get("group_id")) + group_name = "" + if fetched_group_info.get("group_name"): + group_name = fetched_group_info.get("group_name") + + group_info: GroupInfo = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=raw_message.get("group_id"), + group_name=group_name, + ) + + else: + logger.warning(f"私聊消息类型 {sub_type} 不支持") + return None + elif message_type == MessageSentType.group: + sub_type = raw_message.get("sub_type") + if sub_type == MessageSentType.Group.normal: + sender_info: dict = raw_message.get("sender") + + # 发送者用户信息 + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=sender_info.get("user_id"), + user_nickname=sender_info.get("nickname"), + user_cardname=sender_info.get("card"), + ) + + # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 + fetched_group_info = await get_group_info(self.server_connection, raw_message.get("group_id")) + group_name: str = None + if fetched_group_info: + group_name = fetched_group_info.get("group_name") + + group_info: GroupInfo = GroupInfo( + platform=global_config.maibot_server.platform_name, + group_id=raw_message.get("group_id"), + group_name=group_name, + ) + + else: + logger.warning(f"群聊消息类型 {sub_type} 不支持") + return None + + additional_config: dict = {"sent_message":True} + + # 消息信息 + message_info: BaseMessageInfo = BaseMessageInfo( + platform=global_config.maibot_server.platform_name, + message_id=message_id, + time=message_time, + user_info=user_info, + group_info=group_info, + template_info=template_info, + format_info=format_info, + additional_config=additional_config, + ) + + # 处理实际信息 + if not raw_message.get("message"): + logger.warning("原始消息内容为空") + return None + + # 获取Seg列表 + seg_message: List[Seg] = await message_handler.handle_real_message(raw_message) + if not seg_message: + logger.warning("处理后消息内容为空") + return None + submit_seg: Seg = Seg( + type="seglist", + data=seg_message, + ) + # MessageBase创建 + message_base: MessageBase = MessageBase( + message_info=message_info, + message_segment=submit_seg, + raw_message=raw_message.get("raw_message"), + ) + + logger.info("发送到Maibot处理以更新数据库") + await message_send_instance.message_send(message_base) + +message_sent_handler = MessageSentHandler() \ No newline at end of file diff --git a/src/send_handler.py b/src/send_handler.py index 6cb3094..0be634d 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -103,6 +103,8 @@ async def send_command(self, raw_message_base: MessageBase) -> None: command, args_dict = self.handle_kick_command(seg_data.get("args"), group_info) case CommandType.SEND_POKE.name: command, args_dict = self.handle_poke_command(seg_data.get("args"), group_info) + case CommandType.DELETE_MSG.name: + command, args_dict = self.delete_msg_command(seg_data.get("args")) case _: logger.error(f"未知命令: {command_name}") return @@ -347,6 +349,29 @@ def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu "user_id": user_id, }, ) + + def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: + """处理撤回消息命令 + + Args: + args (Dict[str, Any]): 参数字典 + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + try: + message_id = int(args["message_id"]) + if message_id <= 0: + raise ValueError("消息ID无效") + except (ValueError, TypeError) as e: + raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") + + return ( + CommandType.DELETE_MSG.value, + { + "message_id": message_id + }, + ) async def send_message_to_napcat(self, action: str, params: dict) -> dict: request_uuid = str(uuid.uuid4()) From bdbdc79f1448a3ded5a092159c5aadddb01253d5 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 14:37:22 +0800 Subject: [PATCH 036/112] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=8C=E9=A1=BA=E4=BE=BF=E8=A1=A5=E4=B8=80=E4=B8=AA=E7=BC=BA?= =?UTF-8?q?=E5=A4=B1=E4=BE=9D=E8=B5=96=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- command_args.md | 13 ++++++++++++- requirements.txt | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3e76e39..266ebac 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ sequenceDiagram - [x] 读取戳一戳的自定义内容 - [ ] 语音解析(?) - [ ] 所有的notice类 - - [ ] 撤回 + - [x] 撤回(已添加相关指令) - [x] 发送消息 - [x] 发送文本 - [x] 发送图片 diff --git a/command_args.md b/command_args.md index 01390a7..afd5f37 100644 --- a/command_args.md +++ b/command_args.md @@ -46,4 +46,15 @@ Seg.data: Dict[str, Any] = { "qq_id": "目标QQ号" } } -``` \ No newline at end of file +``` + +## 撤回消息 +```python +Seg.data: Dict[str, Any] = { + "name": "DELETE_MSG", + "args": { + "message_id": "消息所对应的message_id" + } +} +``` +message_id怎么搞到手全凭你本事,也请在自己的插件里写好确定是否能撤回对应的消息的功能,毕竟这玩意真的单纯根据message_id撤消息 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 54d8729..5757fb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ maim_message loguru pillow tomlkit -rich \ No newline at end of file +rich +sqlmodel \ No newline at end of file From cae9645fb748f67ed364a0d16f74daf842d32fbb Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 14:49:40 +0800 Subject: [PATCH 037/112] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=B8=8D=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8F=82=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E6=92=A4=E5=9B=9E=E6=8C=87=E4=BB=A4?= =?UTF-8?q?=E6=8D=95=E6=8D=89=E9=94=99=E8=AF=AF=E7=9A=84=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_sent_handler.py | 1 - src/send_handler.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/recv_handler/message_sent_handler.py b/src/recv_handler/message_sent_handler.py index be600d9..3f863cd 100644 --- a/src/recv_handler/message_sent_handler.py +++ b/src/recv_handler/message_sent_handler.py @@ -25,7 +25,6 @@ class MessageSentHandler: def __init__(self): self.server_connection: Server.ServerConnection = None - self.bot_id_list: Dict[int, bool] = {} async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: """设置Napcat连接""" diff --git a/src/send_handler.py b/src/send_handler.py index 0be634d..8e14852 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -363,6 +363,8 @@ def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]] message_id = int(args["message_id"]) if message_id <= 0: raise ValueError("消息ID无效") + except KeyError: + raise ValueError("缺少必需参数: message_id") except (ValueError, TypeError) as e: raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") From f26667db7354c1d0115844989e82ec89d03a92e7 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 28 Jun 2025 16:17:19 +0800 Subject: [PATCH 038/112] requirements.txt update and minor fix --- requirements.txt | 3 ++- src/recv_handler/notice_handler.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 54d8729..5757fb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ maim_message loguru pillow tomlkit -rich \ No newline at end of file +rich +sqlmodel \ No newline at end of file diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 2d03a49..0f46b04 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -205,7 +205,7 @@ async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: in if self_id == target_id: target_name = self_info.get("nickname") else: - return None + return None, None try: first_txt = raw_info[2].get("txt", "戳了戳") second_txt = raw_info[4].get("txt", "") From 588ba6417e26af102fe7398d0cb4edeb898a3130 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 17:04:00 +0800 Subject: [PATCH 039/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=88=B3=E4=B8=80?= =?UTF-8?q?=E6=88=B3=EF=BC=8C=E5=90=8C=E6=97=B6=E9=98=BB=E6=AD=A2=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E7=9A=84=E6=88=B3=E4=B8=80=E6=88=B3=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/notice_handler.py | 83 +++++++++++++++--------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 2d03a49..f6fb16e 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -165,68 +165,67 @@ async def handle_notice(self, raw_message: dict) -> None: logger.info("发送到Maibot处理通知信息") await message_send_instance.message_send(message_base) - async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: int) -> Tuple[Seg | None, UserInfo]: + async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: int) -> Tuple[Seg | None, UserInfo | None]: self_info: dict = await get_self_info(self.server_connection) + if not self_info: logger.error("自身信息获取失败") - return None + return None, None + self_id = raw_message.get("self_id") target_id = raw_message.get("target_id") target_name: str = None raw_info: list = raw_message.get("raw_info") - # 计算user_info - source_name: str = None - source_cardname: str = None - if group_id: - member_info: dict = await get_member_info(self.server_connection, group_id, user_id) - if member_info: - source_name = member_info.get("nickname") - source_cardname = member_info.get("card") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" + user_qq_info: dict = await get_member_info( + self.server_connection, group_id, user_id + ) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") else: - stranger_info = await get_stranger_info(self.server_connection, user_id) - if stranger_info: - source_name = stranger_info.get("nickname") - else: - logger.warning("无法获取戳一戳消息发送者的昵称,消息可能会无效") - source_name = "QQ用户" - - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=user_id, - user_nickname=source_name, - user_cardname=source_cardname, - ) + user_name = "QQ用户" + user_cardname = "QQ用户" + logger.info("无法获取戳一戳对方的用户昵称") # 计算Seg - if self_id == target_id: + if self_id == target_id: # 现在这里应当是专注于处理私聊戳一戳的,也就是说当私聊里,被戳的是另一方时,不会给这个消息。 + display_name = "" target_name = self_info.get("nickname") + + elif self_id == user_id: + return None, None # 这应当让ada不发送麦麦戳别人的消息,因为这个消息已经被mmc的命令记录了,没必要记第二次。 + else: - return None + if group_id: # 如果是群聊环境,老实说做这一步判定没啥意义,毕竟私聊是没有其他人之间的戳一戳的,但是感觉可以有这个判定来强限制群聊环境 + fetched_member_info: dict = await get_member_info( + self.server_connection, group_id, target_id + ) + if fetched_member_info: + target_name = fetched_member_info.get("nickname") + else: + target_name = "QQ用户" + logger.info("无法获取被戳一戳方的用户昵称") + display_name = user_name + else: + return None, None try: first_txt = raw_info[2].get("txt", "戳了戳") second_txt = raw_info[4].get("txt", "") except Exception as e: logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") first_txt = "戳了戳" - second_txt = "" - """ - # 不启用戳其他人的处理 - else: - # 由于Napcat不支持获取昵称,所以需要单独获取 - group_id = raw_message.get("group_id") - fetched_member_info: dict = await get_member_info( - self.server_connection, group_id, target_id - ) - if fetched_member_info: - target_name = fetched_member_info.get("nickname") - """ + + user_info: UserInfo = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + seg_data: Seg = Seg( type="text", - data=f"{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", + data=f"{display_name}{first_txt}{target_name}{second_txt}(这是QQ的一个功能,用于提及某人,但没那么明显)", ) return seg_data, user_info @@ -503,4 +502,4 @@ async def send_notice(self) -> None: await asyncio.sleep(1) -notice_handler = NoticeHandler() +notice_handler = NoticeHandler() \ No newline at end of file From b6cabc593b6bc99040c3ea46e3f7d75ace2e2f88 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 17:10:59 +0800 Subject: [PATCH 040/112] =?UTF-8?q?=E7=A7=81=E8=81=8A=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/notice_handler.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index f6fb16e..4288a02 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -177,16 +177,28 @@ async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: in target_name: str = None raw_info: list = raw_message.get("raw_info") - user_qq_info: dict = await get_member_info( + if group_id: + user_qq_info: dict = await get_member_info( self.server_connection, group_id, user_id ) - if user_qq_info: - user_name = user_qq_info.get("nickname") - user_cardname = user_qq_info.get("card") + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + user_name = "QQ用户" + user_cardname = "QQ用户" + logger.info("无法获取戳一戳对方的用户昵称") else: - user_name = "QQ用户" - user_cardname = "QQ用户" - logger.info("无法获取戳一戳对方的用户昵称") + user_qq_info: dict = await get_stranger_info( + self.server_connection, user_id + ) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + user_name = "QQ用户" + user_cardname = "QQ用户" + logger.info("无法获取戳一戳对方的用户昵称") # 计算Seg if self_id == target_id: # 现在这里应当是专注于处理私聊戳一戳的,也就是说当私聊里,被戳的是另一方时,不会给这个消息。 From 6d3745ff0af98f97f056760572f860edbd714206 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 17:33:00 +0800 Subject: [PATCH 041/112] =?UTF-8?q?=E5=9B=9E=E9=80=80=E4=B8=8A=E6=8A=A5?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 5 +- src/recv_handler/message_sent_handler.py | 172 ----------------------- 2 files changed, 1 insertion(+), 176 deletions(-) delete mode 100644 src/recv_handler/message_sent_handler.py diff --git a/main.py b/main.py index 652d6b8..a928191 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,6 @@ from src.recv_handler.message_handler import message_handler from src.recv_handler.meta_event_handler import meta_event_handler from src.recv_handler.notice_handler import notice_handler -from src.recv_handler.message_sent_handler import message_sent_handler from src.recv_handler.message_sending import message_send_instance from src.send_handler import send_handler from src.config import global_config @@ -28,7 +27,7 @@ async def message_recv(server_connection: Server.ServerConnection): ) decoded_raw_message: dict = json.loads(raw_message) post_type = decoded_raw_message.get("post_type") - if post_type in ["meta_event", "message", "notice","message_sent"]: + if post_type in ["meta_event", "message", "notice"]: await message_queue.put(decoded_raw_message) elif post_type is None: await put_response(decoded_raw_message) @@ -44,8 +43,6 @@ async def message_process(): await meta_event_handler.handle_meta_event(message) elif post_type == "notice": await notice_handler.handle_notice(message) - elif post_type == "message_sent": - await message_sent_handler.handle_sent_message(message) else: logger.warning(f"未知的post_type: {post_type}") message_queue.task_done() diff --git a/src/recv_handler/message_sent_handler.py b/src/recv_handler/message_sent_handler.py deleted file mode 100644 index 3f863cd..0000000 --- a/src/recv_handler/message_sent_handler.py +++ /dev/null @@ -1,172 +0,0 @@ -from src.logger import logger -from src.config import global_config -from src.utils import ( - get_group_info, - get_member_info, -) -from .message_sending import message_send_instance -from . import MessageSentType -from .message_handler import message_handler - -import time -import websockets as Server -from typing import List, Dict - -from maim_message import ( - UserInfo, - GroupInfo, - Seg, - BaseMessageInfo, - MessageBase, - TemplateInfo, - FormatInfo, -) - -class MessageSentHandler: - def __init__(self): - self.server_connection: Server.ServerConnection = None - - async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: - """设置Napcat连接""" - self.server_connection = server_connection - - async def handle_sent_message(self, raw_message: dict) -> None: - """ - 从Napcat接受的原始发送消息处理 - - Parameters: - raw_message: dict: 原始消息 - """ - message_type: str = raw_message.get("message_type") - message_id: int = raw_message.get("message_id") - message_time: float = time.time() - - template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 - format_info: FormatInfo = FormatInfo( - content_format=["text", "image", "emoji"], - accept_format=["text", "image", "emoji", "reply"], - ) # 上报信息暂时只支持这四种解析 - if message_type == MessageSentType.private: - sub_type = raw_message.get("sub_type") - if sub_type == MessageSentType.Private.friend: - sender_info: dict = raw_message.get("sender") - - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=sender_info.get("nickname"), - user_cardname=sender_info.get("card"), - ) - - # 不存在群信息 - group_info: GroupInfo = None - elif sub_type == MessageSentType.Private.group: - """ - 本部分暂时不做支持,先放着 - """ - logger.warning("群临时消息类型不支持") - return None - - sender_info: dict = raw_message.get("sender") - - # 由于临时会话中,Napcat默认不发送成员昵称,所以需要单独获取 - fetched_member_info: dict = await get_member_info( - self.server_connection, - raw_message.get("group_id"), - sender_info.get("user_id"), - ) - nickname = fetched_member_info.get("nickname") if fetched_member_info else None - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=nickname, - user_cardname=None, - ) - - # -------------------这里需要群信息吗?------------------- - - # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 - fetched_group_info: dict = await get_group_info(self.server_connection, raw_message.get("group_id")) - group_name = "" - if fetched_group_info.get("group_name"): - group_name = fetched_group_info.get("group_name") - - group_info: GroupInfo = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=raw_message.get("group_id"), - group_name=group_name, - ) - - else: - logger.warning(f"私聊消息类型 {sub_type} 不支持") - return None - elif message_type == MessageSentType.group: - sub_type = raw_message.get("sub_type") - if sub_type == MessageSentType.Group.normal: - sender_info: dict = raw_message.get("sender") - - # 发送者用户信息 - user_info: UserInfo = UserInfo( - platform=global_config.maibot_server.platform_name, - user_id=sender_info.get("user_id"), - user_nickname=sender_info.get("nickname"), - user_cardname=sender_info.get("card"), - ) - - # 获取群聊相关信息,在此单独处理group_name,因为默认发送的消息中没有 - fetched_group_info = await get_group_info(self.server_connection, raw_message.get("group_id")) - group_name: str = None - if fetched_group_info: - group_name = fetched_group_info.get("group_name") - - group_info: GroupInfo = GroupInfo( - platform=global_config.maibot_server.platform_name, - group_id=raw_message.get("group_id"), - group_name=group_name, - ) - - else: - logger.warning(f"群聊消息类型 {sub_type} 不支持") - return None - - additional_config: dict = {"sent_message":True} - - # 消息信息 - message_info: BaseMessageInfo = BaseMessageInfo( - platform=global_config.maibot_server.platform_name, - message_id=message_id, - time=message_time, - user_info=user_info, - group_info=group_info, - template_info=template_info, - format_info=format_info, - additional_config=additional_config, - ) - - # 处理实际信息 - if not raw_message.get("message"): - logger.warning("原始消息内容为空") - return None - - # 获取Seg列表 - seg_message: List[Seg] = await message_handler.handle_real_message(raw_message) - if not seg_message: - logger.warning("处理后消息内容为空") - return None - submit_seg: Seg = Seg( - type="seglist", - data=seg_message, - ) - # MessageBase创建 - message_base: MessageBase = MessageBase( - message_info=message_info, - message_segment=submit_seg, - raw_message=raw_message.get("raw_message"), - ) - - logger.info("发送到Maibot处理以更新数据库") - await message_send_instance.message_send(message_base) - -message_sent_handler = MessageSentHandler() \ No newline at end of file From 9cda205c7b80309e9dce1564e4629902bb3333d8 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 17:46:20 +0800 Subject: [PATCH 042/112] =?UTF-8?q?=E5=8A=A0=E4=B8=8Atarget=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/notice_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 4288a02..cf84848 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -78,6 +78,7 @@ async def handle_notice(self, raw_message: dict) -> None: group_id = raw_message.get("group_id") user_id = raw_message.get("user_id") + target_id = raw_message.get("target_id") handled_message: Seg = None user_info: UserInfo = None @@ -151,6 +152,7 @@ async def handle_notice(self, raw_message: dict) -> None: group_info=group_info, template_info=None, format_info=None, + additional_config = {"target_id": target_id}# 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁 ) message_base: MessageBase = MessageBase( From 775f66129bbebf7923fbd5e27360111b2d3d768b Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 28 Jun 2025 17:59:31 +0800 Subject: [PATCH 043/112] ruff and minor fix --- src/__init__.py | 2 +- src/recv_handler/__init__.py | 2 +- src/recv_handler/notice_handler.py | 55 +++++++++++++----------------- src/send_handler.py | 10 +++--- 4 files changed, 31 insertions(+), 38 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 0ac0aa5..e5c2d6a 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -11,7 +11,7 @@ class CommandType(Enum): GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 GROUP_KICK = "set_group_kick" # 踢出群聊 SEND_POKE = "send_poke" # 戳一戳 - DELETE_MSG = "delete_msg" # 撤回消息 + DELETE_MSG = "delete_msg" # 撤回消息 def __str__(self) -> str: return self.value diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 5bd1b74..068af29 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -78,7 +78,7 @@ class CommandType(Enum): GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 GROUP_KICK = "set_group_kick" # 踢出群聊 SEND_POKE = "send_poke" # 戳一戳 - DELETE_MSG = "delete_msg" # 撤回消息 + DELETE_MSG = "delete_msg" # 撤回消息 def __str__(self) -> str: return self.value diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index cf84848..73656ad 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -152,7 +152,7 @@ async def handle_notice(self, raw_message: dict) -> None: group_info=group_info, template_info=None, format_info=None, - additional_config = {"target_id": target_id}# 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁 + additional_config={"target_id": target_id}, # 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁 ) message_base: MessageBase = MessageBase( @@ -167,54 +167,45 @@ async def handle_notice(self, raw_message: dict) -> None: logger.info("发送到Maibot处理通知信息") await message_send_instance.message_send(message_base) - async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: int) -> Tuple[Seg | None, UserInfo | None]: + async def handle_poke_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: self_info: dict = await get_self_info(self.server_connection) if not self_info: logger.error("自身信息获取失败") return None, None - + self_id = raw_message.get("self_id") target_id = raw_message.get("target_id") target_name: str = None raw_info: list = raw_message.get("raw_info") if group_id: - user_qq_info: dict = await get_member_info( - self.server_connection, group_id, user_id - ) - if user_qq_info: - user_name = user_qq_info.get("nickname") - user_cardname = user_qq_info.get("card") - else: - user_name = "QQ用户" - user_cardname = "QQ用户" - logger.info("无法获取戳一戳对方的用户昵称") + user_qq_info: dict = await get_member_info(self.server_connection, group_id, user_id) else: - user_qq_info: dict = await get_stranger_info( - self.server_connection, user_id - ) - if user_qq_info: - user_name = user_qq_info.get("nickname") - user_cardname = user_qq_info.get("card") - else: - user_name = "QQ用户" - user_cardname = "QQ用户" - logger.info("无法获取戳一戳对方的用户昵称") + user_qq_info: dict = await get_stranger_info(self.server_connection, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + user_name = "QQ用户" + user_cardname = "QQ用户" + logger.info("无法获取戳一戳对方的用户昵称") # 计算Seg - if self_id == target_id: # 现在这里应当是专注于处理私聊戳一戳的,也就是说当私聊里,被戳的是另一方时,不会给这个消息。 + if self_id == target_id: display_name = "" target_name = self_info.get("nickname") elif self_id == user_id: - return None, None # 这应当让ada不发送麦麦戳别人的消息,因为这个消息已经被mmc的命令记录了,没必要记第二次。 + # 让ada不发送麦麦戳别人的消息 + return None, None else: - if group_id: # 如果是群聊环境,老实说做这一步判定没啥意义,毕竟私聊是没有其他人之间的戳一戳的,但是感觉可以有这个判定来强限制群聊环境 - fetched_member_info: dict = await get_member_info( - self.server_connection, group_id, target_id - ) + # 老实说这一步判定没啥意义,毕竟私聊是没有其他人之间的戳一戳,但是感觉可以有这个判定来强限制群聊环境 + if group_id: + fetched_member_info: dict = await get_member_info(self.server_connection, group_id, target_id) if fetched_member_info: target_name = fetched_member_info.get("nickname") else: @@ -223,12 +214,14 @@ async def handle_poke_notify(self, raw_message: dict, group_id: int, user_id: in display_name = user_name else: return None, None + + first_txt: str = "戳了戳" + second_txt: str = "" try: first_txt = raw_info[2].get("txt", "戳了戳") second_txt = raw_info[4].get("txt", "") except Exception as e: logger.warning(f"解析戳一戳消息失败: {str(e)},将使用默认文本") - first_txt = "戳了戳" user_info: UserInfo = UserInfo( platform=global_config.maibot_server.platform_name, @@ -516,4 +509,4 @@ async def send_notice(self) -> None: await asyncio.sleep(1) -notice_handler = NoticeHandler() \ No newline at end of file +notice_handler = NoticeHandler() diff --git a/src/send_handler.py b/src/send_handler.py index 8e14852..bf3260f 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -349,7 +349,7 @@ def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tu "user_id": user_id, }, ) - + def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: """处理撤回消息命令 @@ -364,14 +364,14 @@ def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]] if message_id <= 0: raise ValueError("消息ID无效") except KeyError: - raise ValueError("缺少必需参数: message_id") + raise ValueError("缺少必需参数: message_id") from None except (ValueError, TypeError) as e: - raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") - + raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") from None + return ( CommandType.DELETE_MSG.value, { - "message_id": message_id + "message_id": message_id, }, ) From 1bdd165d1267825d59a02fd0ed6737adc3b3e896 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sat, 28 Jun 2025 20:28:40 +0800 Subject: [PATCH 044/112] =?UTF-8?q?=E6=94=AF=E6=8C=81ada=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=8C=E9=9C=80=E8=A6=81mmc=E9=82=A3?= =?UTF-8?q?=E8=BE=B9=E6=9C=89=E7=9B=B8=E5=BA=94=E4=BB=A3=E7=A0=81=E9=85=8D?= =?UTF-8?q?=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 49 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index 8e14852..99b83c3 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -15,6 +15,7 @@ from .response_pool import get_response from .logger import logger from .utils import get_image_format, convert_image_to_gif +from .recv_handler.message_sending import message_send_instance class SendHandler: @@ -48,7 +49,7 @@ async def send_normal_message(self, raw_message_base: MessageBase) -> None: id_name: str = None processed_message: list = [] try: - processed_message = await self.handle_seg_recursive(message_segment) + processed_message, seg_type = await self.handle_seg_recursive(message_segment) except Exception as e: logger.error(f"处理消息时发生错误: {e}") return @@ -83,6 +84,13 @@ async def send_normal_message(self, raw_message_base: MessageBase) -> None: else: logger.warning(f"消息发送失败,napcat返回:{str(response)}") + qq_message_id = response.get("data", {}).get("message_id") + + if seg_type in {"text","image","emoji","reply","voice","voiceurl","music"}: + await self.message_sent_back(raw_message_base, qq_message_id) + else: + logger.debug("消息类型不支持回调更新数据库") + async def send_command(self, raw_message_base: MessageBase) -> None: """ 处理命令类 @@ -128,19 +136,19 @@ def get_level(self, seg_data: Seg) -> int: else: return 1 - async def handle_seg_recursive(self, seg_data: Seg) -> list: + async def handle_seg_recursive(self, seg_data: Seg) -> tuple[list, str]: payload: list = [] if seg_data.type == "seglist": # level = self.get_level(seg_data) # 给以后可能的多层嵌套做准备,此处不使用 if not seg_data.data: return [] for seg in seg_data.data: - payload = self.process_message_by_type(seg, payload) + payload, seg_type = self.process_message_by_type(seg, payload) else: - payload = self.process_message_by_type(seg_data, payload) - return payload + payload, seg_type = self.process_message_by_type(seg_data, payload) + return payload, seg_type - def process_message_by_type(self, seg: Seg, payload: list) -> list: + def process_message_by_type(self, seg: Seg, payload: list) -> tuple[list, str]: # sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression new_payload = payload if seg.type == "reply": @@ -170,7 +178,7 @@ def process_message_by_type(self, seg: Seg, payload: list) -> list: elif seg.type == "music": song_id = seg.data new_payload = self.build_payload(payload, self.handle_music_message(song_id), False) - return new_payload + return new_payload, seg.type def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator @@ -363,8 +371,6 @@ def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]] message_id = int(args["message_id"]) if message_id <= 0: raise ValueError("消息ID无效") - except KeyError: - raise ValueError("缺少必需参数: message_id") except (ValueError, TypeError) as e: raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") @@ -388,6 +394,29 @@ async def send_message_to_napcat(self, action: str, params: dict) -> dict: logger.error(f"发送消息失败: {e}") return {"status": "error", "message": str(e)} return response + + async def message_sent_back(self, message_base: MessageBase, qq_message_id: str): + # 修改 additional_config,添加 echo 字段 + if message_base.message_info.additional_config is None: + message_base.message_info.additional_config = {} + + message_base.message_info.additional_config["echo"] = True + + # 获取原始的 mmc_message_id + mmc_message_id = message_base.message_info.message_id + + # 修改 message_segment 为 notify 类型 + message_base.message_segment = Seg( + type="notify", + data={ + "sub_type": "echo", + "echo": mmc_message_id, + "actual_id": qq_message_id + } + ) + await message_send_instance.message_send(message_base) + logger.debug("已回送消息ID") + return -send_handler = SendHandler() +send_handler = SendHandler() \ No newline at end of file From e526b9fda763c4c216a906aafa2ec8623e4ee14f Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Sun, 29 Jun 2025 00:42:53 +0800 Subject: [PATCH 045/112] =?UTF-8?q?=E4=B8=8D=E5=86=8D=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index 0bdfe3f..6adedaf 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -49,7 +49,7 @@ async def send_normal_message(self, raw_message_base: MessageBase) -> None: id_name: str = None processed_message: list = [] try: - processed_message, seg_type = await self.handle_seg_recursive(message_segment) + processed_message = await self.handle_seg_recursive(message_segment) except Exception as e: logger.error(f"处理消息时发生错误: {e}") return @@ -85,11 +85,7 @@ async def send_normal_message(self, raw_message_base: MessageBase) -> None: logger.warning(f"消息发送失败,napcat返回:{str(response)}") qq_message_id = response.get("data", {}).get("message_id") - - if seg_type in {"text","image","emoji","reply","voice","voiceurl","music"}: - await self.message_sent_back(raw_message_base, qq_message_id) - else: - logger.debug("消息类型不支持回调更新数据库") + await self.message_sent_back(raw_message_base, qq_message_id) async def send_command(self, raw_message_base: MessageBase) -> None: """ @@ -136,19 +132,19 @@ def get_level(self, seg_data: Seg) -> int: else: return 1 - async def handle_seg_recursive(self, seg_data: Seg) -> tuple[list, str]: + async def handle_seg_recursive(self, seg_data: Seg) -> list: payload: list = [] if seg_data.type == "seglist": # level = self.get_level(seg_data) # 给以后可能的多层嵌套做准备,此处不使用 if not seg_data.data: return [] for seg in seg_data.data: - payload, seg_type = self.process_message_by_type(seg, payload) + payload = self.process_message_by_type(seg, payload) else: - payload, seg_type = self.process_message_by_type(seg_data, payload) - return payload, seg_type + payload = self.process_message_by_type(seg_data, payload) + return payload - def process_message_by_type(self, seg: Seg, payload: list) -> tuple[list, str]: + def process_message_by_type(self, seg: Seg, payload: list) -> list: # sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression new_payload = payload if seg.type == "reply": @@ -178,7 +174,7 @@ def process_message_by_type(self, seg: Seg, payload: list) -> tuple[list, str]: elif seg.type == "music": song_id = seg.data new_payload = self.build_payload(payload, self.handle_music_message(song_id), False) - return new_payload, seg.type + return new_payload def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator From 0d3a77d140a63038f6ae1b4e550507cca4183c56 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 29 Jun 2025 01:15:56 +0800 Subject: [PATCH 046/112] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E5=92=8Cruf?= =?UTF-8?q?f?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/send_handler.py | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2f6423d..5815d08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.3.0" +version = "0.4.0" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/send_handler.py b/src/send_handler.py index 6adedaf..d36598b 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -392,29 +392,24 @@ async def send_message_to_napcat(self, action: str, params: dict) -> dict: logger.error(f"发送消息失败: {e}") return {"status": "error", "message": str(e)} return response - + async def message_sent_back(self, message_base: MessageBase, qq_message_id: str): # 修改 additional_config,添加 echo 字段 if message_base.message_info.additional_config is None: message_base.message_info.additional_config = {} - + message_base.message_info.additional_config["echo"] = True - + # 获取原始的 mmc_message_id mmc_message_id = message_base.message_info.message_id - + # 修改 message_segment 为 notify 类型 message_base.message_segment = Seg( - type="notify", - data={ - "sub_type": "echo", - "echo": mmc_message_id, - "actual_id": qq_message_id - } + type="notify", data={"sub_type": "echo", "echo": mmc_message_id, "actual_id": qq_message_id} ) await message_send_instance.message_send(message_base) logger.debug("已回送消息ID") return -send_handler = SendHandler() \ No newline at end of file +send_handler = SendHandler() From 96fa750100be2bd02963f63ee6773899ad56a781 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 29 Jun 2025 01:18:34 +0800 Subject: [PATCH 047/112] fix bug --- src/send_handler.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/send_handler.py b/src/send_handler.py index d36598b..e02f1b1 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -81,12 +81,11 @@ async def send_normal_message(self, raw_message_base: MessageBase) -> None: ) if response.get("status") == "ok": logger.info("消息发送成功") + qq_message_id = response.get("data", {}).get("message_id") + await self.message_sent_back(raw_message_base, qq_message_id) else: logger.warning(f"消息发送失败,napcat返回:{str(response)}") - qq_message_id = response.get("data", {}).get("message_id") - await self.message_sent_back(raw_message_base, qq_message_id) - async def send_command(self, raw_message_base: MessageBase) -> None: """ 处理命令类 @@ -393,7 +392,7 @@ async def send_message_to_napcat(self, action: str, params: dict) -> dict: return {"status": "error", "message": str(e)} return response - async def message_sent_back(self, message_base: MessageBase, qq_message_id: str): + async def message_sent_back(self, message_base: MessageBase, qq_message_id: str) -> None: # 修改 additional_config,添加 echo 字段 if message_base.message_info.additional_config is None: message_base.message_info.additional_config = {} From 7564361a63cc83fedf8b9b29478534556ea69a4f Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 30 Jun 2025 11:28:07 +0800 Subject: [PATCH 048/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E5=8C=85=E7=A7=8D=E7=B1=BB=E5=B0=9D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 0cec56f..62288ae 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -370,7 +370,7 @@ async def handle_image_message(self, raw_message: dict) -> Seg | None: if image_sub_type == 0: """这部分认为是图片""" return Seg(type="image", data=image_base64) - elif image_sub_type == 1: + elif image_sub_type in [1, 2, 3, 7, None]: """这部分认为是表情包""" return Seg(type="emoji", data=image_base64) else: From 939f3b7d08bacd38e06c4fa2273a7291a703ff8d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 30 Jun 2025 11:34:04 +0800 Subject: [PATCH 049/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E5=8C=85=E7=A7=8D=E7=B1=BB=E5=B0=9D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/recv_handler/message_handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5815d08..f3256a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.0" +version = "0.4.1" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 62288ae..c84483f 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -370,7 +370,7 @@ async def handle_image_message(self, raw_message: dict) -> Seg | None: if image_sub_type == 0: """这部分认为是图片""" return Seg(type="image", data=image_base64) - elif image_sub_type in [1, 2, 3, 7, None]: + elif image_sub_type not in [4, 9]: """这部分认为是表情包""" return Seg(type="emoji", data=image_base64) else: From 92af300035f28a637f4947d4a519314b6c214fb0 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 30 Jun 2025 17:20:20 +0800 Subject: [PATCH 050/112] =?UTF-8?q?docs=E8=BF=87=E6=97=B6=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/36bd6c6d15c0fa7ece5b856d0e51ebe5.jpg | Bin 161132 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/36bd6c6d15c0fa7ece5b856d0e51ebe5.jpg diff --git a/docs/36bd6c6d15c0fa7ece5b856d0e51ebe5.jpg b/docs/36bd6c6d15c0fa7ece5b856d0e51ebe5.jpg deleted file mode 100644 index a5973385fa5f047457ba1bb111d9a55ea14eb453..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161132 zcmeFZcT`hf*De}FK&6Q^0U-j4C{2-~w5SM(hzLj*q97fNO0N-+-UI{`gh&&qk*>5z z@6th9Lhm67ErGPNfA4qhcgA=AI`@uq?;ZCYH)Jygi?!EYbFMY#n(LX*+VsEldC=L1 z+Pc~x1_ls_0r&yYXF!@D#uF$0`2wGlz?bP16Vu6)OlMeFm`|~tVPj)G!^+CeaqcWT z2Nwq`>sh|DT--doyu56j{O9?2&Y$Dq<@u)(21ekXlT4?Xm`?Msv$FI2pZ?H)gSbwe z{BY8ZkwFY}f{THXi-Fz_0t5YIV))kw^xr=WCxAXOvz$78h83t#a~5=hfsygVNydMA z4b%<-{tr6I#dJ>e#$9G^!xt=KZ+PTGl5$Ro->YchH5$fWx@r41^z<1%{__HYmnE)9 zUcIKEsHA-Bwuh9?s866v+nEX36y|B2nyt2Bs zzOhNz-P=DnB$AGf|B;IU#P}a#0l)u4u>VOeEh-Wcf!fh7&%(#mIG%N%RKu zxx0oeFWzvA$%mZcxtEkv(Q;b+rV*ak_U-T)zDo)VmkIxf_Akl)*8~gwza-gz3idzb zng+2kG5~|e$OVFcsK&W5lA!-`C8xbRBbl2z#KdFx=tPX<*Z=?e?>hLfuTULX0P-`# zvJxJW7kt7ziNkK9V~u)9m)dELSf+^u*kMnfoAblK79OeFT||E*`oc>%JME??EMZr* z(ta6!=C9w~CN7O#ss$QJ8`pqNH?tl!+@OP4x%E2_X9iQeVh#j8^4-#kq-EV5%*2_G zdFv?p^?reIcj>%qzV%(4#vhOP0$E9?h^Apw=%4^_GOKYR@a_oh z3Q1g>pdEiT#b9inTN@l1xBv3R)W>D6O{wqfHZ0^b{gcqj!B7V{N^VBNZikoxp$*s^ zGc$SJMXO}DTaVb^K8O{v`Yz+p5F=F*PkSUguhyV#<&yoFMa`sivLDXxm@u=*`3_q{ z2SwMv`zaVczUS<(UsnGhDsA^7SLcVO&ZX5{Dh<1jbSK#!D|UrTBdb;Cte%X@eR$TU zQK3FIDqr{bm4+91n-Eix760;?Z=FunxX|ODW83phLN{TTTT4F&ZXbCdGF|O9Z1fi6 z&D#CVI^E^4&7!deBRka%VUC4=y95V3H`JI!_hi8iN6PNxXd3m6j{~u#S^g)1gpWv{ z-xB&>wvWU@`8P{EJe=QLZqI(1>?bGhhVDfXb`f27z{VzeS*9Z+$0NPYBdR4$tEUnz zl-^l^?`Zk1wU-$@BQO`vXTr~Hg&y}=CgA#OYR5F%+yhHE<}t1b0tK(praZ50E*aRK+-ub<+xE#z@QHh7e z(Zudg?pZo}fB4(8wwaE+9Z8g{cgXOuBmJVC>28`#jHU=LmOb3Fi0lv)Npd0{n(+yG z{mHzVD(2+`qU!3<>|pgt7wgp%omx7bH4_A z=^%MZA>xzy8$nk=ju8SCvs6GJ@AUa}mM&QEOHbNMk4bP{DdbvTt9!DFCGgkTFP zds9$7*Q5@Q+)XY6!2wpQZV_bxHl;5yprC|bFN)QB%`sLsHPsOU9gTgW(Oo8P+%-*! zii!jKd`ctFp7npYdwzEJL5qn_(UpS09`Y^g!2*MM;cLBq?)xQ3j(#E^m#&{;?eeZ# zZG^6OJ7Vk7k*EPlXmZF#dU6fFlT1vTR^Ms3{=CHT`LJ|+Ho3s_jz*~@qut{%tfK3K zozimKhT0GI65(xmoKJL^OPr3W*$pm}O8nPttfUh;v)m<}^ zu^saYh<~_)dFWKVA%JwaR=am2Xl0H_DWBnv0Uf(oeGFAjHaitn@Hz1gi?NBjB31tQ zY0S)%+MlWORs|2fnL30wZ@*(Cayo3_Y;*_@Tp~L-6-$TUxlTSlTv48WuU=6~bM?=^ ztk);UC85%nQO79Ub~$W8J2a4!4mveaP%alO_qd?IJf&FowbD#~(zcauS=q4KcQcb>c&f)eppXq2@xBE0#d53K(Ljkd z$O$wBSw>@GJ?sXDwt=*%WC!qG-+vt89!y$lk5{a<)I7)>q@# z8EHq>G`F;#fjS2>O<0wh5mjmis(NV0ZH|$2(7Sw-_y@D|Wsi;i9Blblag9p7v#3EJy`s^ux&j6V>s3w$r4k z6Eo3-{yF#_!MQ-PpO4jNP&|DQy_6P^S@w9t#G&bDhGT~F7dfHN%XXwmN9(hu(c4-U zmjXMGq+B|v#lJ3EO}N0?J7YyD+ff&2G4r$c^~a3m#Q zuFoZ28nSa+0Q!48ju3!iaLqt;-SX0ZYklvq-vZs?805NZW!}2VAlOFjL!{RA5Hm@) zj=0lEVfe1!5O&*2!U@jB^mK;G99EkIriK(2DA%} z6}(Sumcs|F9op0tO59KnBhNsg1$WlVBitSXKw9QU$T+{P?)uiZsR8~Jk3AifPX+;dM5&{L{G_JUpo@NLs^3WtbJ*kn89O6dU)HA& zwiveZ_#IYlej2k>>EGtiBFx4RG~e~jiAQ2d4tJJRtqqvpX*i85Qn?pAl>fSUQn@<> zPx|ng4*NE=Pa2>Mj+FFYuu{@RY_HL8eo??|DcG>H-($WS#;1RnIxO( z9WFoR55D=)FEV8i7?`6BA?h?l*XszxpeY$>}(~1*@Yo zLa{5~JjO-Nd)5pqt}iR44Z-^q>zeA`i@|>>WDe6oe}giq_ym24p;@jZ+#Np2e4B`W z1;76U!2kZ2=FrH9w*o_E@n)L8O!&ZIj)!noR<*vuB)8I7JdgdSl;0q;cnCINzM+rY z2jScZmL_;iE2m3O^GC|=yx`oWWVSLOJ_XkV9JVz6s-|WKnonpkZ8d#7`#E@r*c8GH z=PQqFMe|nRZPM*?oi(XvM&zoj^eEm%6eDHg93~yMLFsj9q z4w@@uq@k|RGM>RlE9syrt5OxG#p9)ICtvQ)W6pLs`?POt>v2JINxpSlhld+EwdpRn zH7HSW3@O&gc=+N+&9K^CLf)gQc2k1yaJkq!Hc*Q5p)pLPe+05(Ln@5vY3(Z1-|PC6 z95XON(J63s_pX0c;P_oJ{~LPV@=x-om%@gJDK(Kft5x}g7@ei=!0YZbKhLhQw55L9 zc9DSoPddo$!W#a2g|~`|6k~^oY#lzeEPi0yNXWHhUHZ{?4RcG!DIrL1y5z5e7Z$vt1ffQ51pP1HBH3MqiVwr~)Svj5h znYTI{{u!Y>!11Q;f}La0p8!*62RzD~4q^-Bf-eevmN}EUN<2^7CP3S{V&qB`{H zsKWSthe)!~k&JliyFYWd(K@?{U${u{g+K^FcJN_}p5AI2tw)Zd?`nSC`|ITRx7@w6 zDt102u}q0~rM`?XbvTk@+r4f0MlO#X2;Ap;qvo_L(lKWUC#>?ek+znMd>wjQx3q?K z8lg;cV|?h~R55B+;8bH0dG&~u<(iVC8qGemt3b~GSqQpf2C6UBHc(6o!7AL!P3-^W zx0BveCsHQGbe?#t+{&6%le^K(I2mZ+{8ml@t0K1LA=aog`o;d?qh79|PQd-1#|^q$s;O z2MPg0vNLO%sE$IyswYMq(xq#Ydz$ZS5`E&>OSMniOm*E9}>_hVwG}~`yZU-H`7daw@!)P)Z)lC()j`(Rr|rRhm@OyJ2mm^5iZqp zJ;)yq+`5IWUh;_jZ0gp~J%pXjHWGJ)bnN1t7^fwRRcyXy#OIrFNf%v+{ZQ3TxOJTO zu8qL!|8ME@)szo=-4RvDXbGI~&Zjo~SQ)CdiFtR6D!rgh|~LDjxaodVkw+xU72zu8+nr4V5)zhk8%aSH(oEaG`Qd!y!+tK*obZY zIqTc|33APA)N#eR{!%}ekMXdnWA8Byi9j!u(+Gb`2j2wlg5$L^zH~={u0{5M+%=K$ zC2%Dwtfq|RcYSBwqLjrx71lAmf{A)1E!%}F9Wg3mlcSAv@H{84HX*?>e(4R4O**ro zj|f}Z1pUH}Q?|`vZ9mXkDkif@eU62yJuE4gg!o+3y!?!bC=XjRh|fH`qZMhRV8$2O z;p4L%c-u={BD5@DvkrSbFwCy;FCA1nzNsNc3UXc?fZZQkYZ{Z082NnIWe@q7eriDI z)5_9ipEmBmg2%=J)8_m(B>zoVw53fWh%Bw5HU2TOnV7)>9Z~-FDytvE8z_kHifdGs z2nldi5Okcf`J<|*T9i|F|J5q*8=;?SJN8!&kYXfFpEm^8`K~C+?VNRgrRWzggw~-h zKXy%p${tEgDfC@{T+`ewS;A4qcWBc1p9J^NjY89bRxuI4#bfqP9Tf_cn+T_nR(3nrs*h@STr z+E`ad6lvLk+?)N=8Z%dlOrV1}3rLX*azAE#{kts2G<@fO{i-J`OfYq_Rs1x1u+hX| z-Ip||xhcHUXjnh$x43meaH^14N3L%%54=Q+{0MZM=VaZWuL1XQ*4;I?Y=OvvvaX7V))b9 z2Fz+^(cG@~xX%2SZ1K_6F^Y1rus9jqHZkGvRk`s*|C0k(@Gjry*`o&TUe(5G@<9o~ zabW-x|Mf?v)@M)H**j!W!bzWVns4m5$Q>u(<$qXq@>PYJUXmbfHp(Q&Z{O0HYO|JP zE(;VWCw03iUKNgZ@G~T8sg*CM!R5IIXLH+b(m^grlGbyQdyrT8^fboB@E#pB<-Mn~ z+H#Vi8FabaZx0%AXK>sHg_wfQSi$Q=beeaV4BOgCgy zaCOx$r9VDn4Njn8;VUI{5Xz+a>|8vzHNG}A=b7tX3w&WKHrjSFzuC&KTnq>pSAz1W zM(#Lt0srORvnXJIRzmS zMc1BfqH58EbMKeaib3k=O7|;H;;;)YULmh|>%**Xp&tId|K*x?!x`(%4?*=i-mr!X zPV2FZI5JA1Mma$Ne%=LrWvC20U)_Y?w+wGusJBqd%+%yZ@9|bjn9@OZb~2uZ6va_! zM=~kxFrduLXz*Bh#va3)Ob2`P zAB8L+^PBi0;|d%5&cS$YJH~_c54@FhbI4O=$^cuCdQA&n#J=R35RK zfriGE_NiH+Pp5vYeAJNGe|Ou=YJReM#C=XtbD(#I6ItQ z$HB4Jbi`0G{Z7r{jZzWW&>1@IO~jm~sJhy}feBNG!bdu>5@G$xQl45m56}-%;D6Qu zfG9CyMyQ&-*?{vLIss5hoJVcLCAYPDm8CJ76&PvOT$%#D? z@=Ee}%6|BFM&(Wfhg%Ki1eks5?}qIuyBJj`FTGnk?q5&SL67DHf_HQt?w05qw;c1w zs|79mReRx{3$<<4Q-|D%fAO<=O6bV-!i@%M3AK8+HfZec!4CQg{?F&d0gg5O358f; z?@p~g^G(wYwR1^B@;@)q#2VM`QxVlAVAdJUWhY z5SfUsr$Wi^)LC1&U6e(HD>I*FNX2%=@ns0xVAtOp^J~93b3y;+gZDbl zcCMfch-w}VcCz?PN-*woA-f7HPYQqcEir?T#MQf3R0 zFx^umEyazo?9Id$CMz)TY2VR3y}RELpdz?`)nHoZlj#rYyWwg(p1dSIOSV@n8+tMF zk^_beA$nR8I%Oak2__jH&?la1qbCe8pbi-xjzxDKJD%z%g3;CfPncBk8I-%lkQv`VhGW`>h$$Q%89rbOVAxq{`A$cCnuOrr2R)K_unpq%W-AN zO;@=EBb*m5In93jBlr_p?ek!b2kJz*#vK0ULSyCbOZ;l4XbKfo*?GeX3N9C%oQJhr zX+WmSW2UjC&V;0{1s6nnscmu4_w7IIbc!TZ^}6q^9K*N}SV& z8jQjOC$VNXIjb~#`%+Yjr ziqGi#l$XiMBvY5=5&5AYx?u^zyIB=)kO$QQ=g&o|lw!X(%S(I=uq1R_t{8~_}vgGW|4hA~}KD`~uUl>T2^<$Wue%6NF-g^fjN5 z`J)xHjP%GobkN8pEhiJ=wvIvb%qW})#!c35;fm-W`vGc-hpTI##&TWSz?<{Oxjh09 z;?P%V-{uCw6{{KoBC52;{S{7gWLwdo%EH>2@V79#`M%K+1ci|vBo8*QLGUUEC`RaJ zedK$EfHId^%gxFe@!8)+)#5~uHxg7|KdGn(VI+0wjmshn2~QClwat9PrEafYEs)g) zX_Y>XI%ejhG!568X0We=Fstrfb61fC`WZt0>3EH<7i-RXnRK%5JIK${r`}(sJ)peJ z7kwvNP0i%jYX#Nze!{nBwB=`KZQQH}f89LO71GmI16d%(dC9I0l>W|@lDeiapUVX=O*92FC+1H!r2ad zJO1-b@S1yAWr$guk|ke}*8=9`RyOCjw4yrqW2*tVCu24kKQ6ZfU9q%jM<>K;B7Amk z70%0GH=<^@bV^O2Kr2A8vChVQt|mhh)K{f1fB(9A-Oe$ijX;5KS0_L8oZL3N_8=20 zKk-NC6ZX|&&1hL+>RPHADp{Z$zLGc##6<8Qe^iGaj2#lGPmC#VZY#8?<->pX3Rw|( z;Gk_5AF_9bMi2;orRB?g8)CYdZ%=HbT&!j^Gia_W{nC%NRqFw^$1(Aj zGASPo&=Lbl02_Ip?S81_gUCTqw6!$ zf>Kj)8)u{gb9ot$Bp$$>5J@&ve|n&`j)t-L5~y;w-@dk_PG5J(?P9u zPN?O*vB_D4a6=-ocFDq5j3>UO>{vP1wtS&lOc}!GA4&bu;F2izJP-L1sJbwC$~Rbb zCTcCBziDlfS_fZ(I$OO_|f>0Owdfk}KR_lgvR&AZ11JUwNArJrQGmqMw66PdyJjtKXU$Nxz z4Vo0e9r<2CHS6KVmCts@l~TT!KQ9NK7cDk@L#AkgX|{NMb4PaRzbm8i7cfOFAsUk=^5>NdHriH zEZ#@r&kh{t?cVj9oM*iNiKboM%E0|5WBAu!cP+eCSZyTR)YK7uBZJrTj_N2tmJz39 zrjLhrc{fWAeYg@WO0b@fclg=-)g`R@^rB#$`+NjGeZOdClX6R;Q0|XJ#b8*_cSKj=D^)6 z>D*t-seC1KS5=4JlFt%Gr`7yA{K(Zz@vswt{-cd>4>;@v%-%f+cA-PTs>udJHq5>% zsLc5)t~W;D6#6CPVXJ{KvJiSWAfTb>mtmUmn z>arPvuMz6E7)utu`7rGd2CMS)EfmuOb7Vq(CN4m$-7|DP)9zCz34r-7Bh5)4GW1AN zhOw5hM(O%VCid21tpPL1@|=ANQU7-TaktK67iA4@?{gBot7* z1LgwSYNrd^r9{?eAaU6_VE#dIPU-aHt)(YI2?xz zbpGa>Fv69y!IuP;9F^ThjX0z&H5kZG9HRA{)5+bNCXvhfzQ~AXjz9xEb3Ak6SBl<XuvR8RiMBwK}i3pN*mR12Aa=mE@KR}@Q9DFXP$ddYy>&S0t%3b_e ztq%vvC1Ld5&+0UDR08KMYo#&xqGS|0u9|ERjyfy((=uO|qtMj$>yy{ojANVPr3W=hI{4|X1%t4Kp;Bu{ z2@Ba*ug-~{>$~|Sxgzx5MFzvO+%;U&h*ICTAnwV~a*EN z#k%6j5VRZ}ln}Fi`lMiAtkvl1c;TpBW5|J!r>;cxIb^0_t#_5|TZWdh4}xB)CBCi( z6Ik2YhA>aAXF6}c$369nB99ShLj$e}%u*WqK96Hf^l#wGMh$(ly=vq`L;#LD z2Wzt?486W>3+{E%W#!`4W3Tp|Wfi5YA1;)6D^FW;N_WPuO1VSluFOvRZdn1O%g}us zw~L^AXW<$2UAxuFss50Kq&cNmUbk7!JmVzBd-9qYkCl1Gvc}HLM3uQ;ytg}e)BV2T zSjwFXvWfT@2vcVojbpL~%;EfoI&jwQ*XYt=!fl>Lsf`Pdqf}+wZJaE>95d$>X1#*e zSeHmPkQMB@*}rVx0ntItv4WckN~7LqKzpjNDLSZC#k!+lC)^zpUZZ+niX9z&BP=UK zx$%mpQS)&8h`312=sNnsxI!N1bf8&@>xmnCtbg4mfk6)F4S_r|?JO*Fo|a2GByR=C zRo_mK)3}rQSLz0%`s-qYA!DqZ%z=>kPT7@&&1n<%3qwCW-)x?mj{&t3y27}-R1}o1 z7&i^ChHeUV!>S2f8CS*Ke%cp3QpIu?}8s9Y# z!r6^>=lB=LrbrC6~KaI~TMB!b<2b&NL0v_I4*QJK-dc;A8{$vt?W;%0otEJM{1RxM4NI^k=gH-=XggtY1DT%W3R0=(T3^)Zi07UrsIQQ_gxLGductCfnkr z?VtW2UBwgv=fVN9T}WR8y?IQ0OcwtAD`x9adUt*NZ*igrXNFp6ulKG=RCz|5QT4dH z>FS?VsS+P@W!ptB$UXM^idnZdT?>PnXUKP#_R>cGKo#?7q(^J8<{Nhe(5LYzifFT7 z^>F%=`my;MsO;79rSJCoT@{BEmJvLZhXa?7MA$lO3ZjrD%-f_ zJVW}7T|1}q3oxMjPm>x@{@bP1LNnBJIDLY8M??efZ^uX8uP;?jeLB%Wqo9$3&HcyuPC(BPu_wJV+} zhP67nOEvb+ccgAEy>uM@v(kF73=mGANHWHcYMdi!YByG)s`1!R%vAs0%v{eYzg)-J z^O0^p7BW%(1q7*u(rifG3^NLz(>*NYJOZkmnY(9jRr)cfcyf8TIrmtC)KG1lu~G~u z)HmD8@0`-#?Qj!R{hO9@@6m(hy( zaM0TfgRpW>`01XUyNfbUvGKXJV+A+#eB3^@F676j_8!VhaZ{{HMD`YLOaR1_iY*&C zfsvpILqI?eYDrz&`SM zEg9tY!a{?##O<^oJ?s;dg%J7PQlRU4ul6+i#;X9=Andyj9w8gIY1n&nFVDAj(dtFo zsNqGKJ3O84giscAtq215REENMu>*YkS%*Lk+F}(6Pfe7rXdoM4({t8n>cBRUp_vRB zC2P+B3$9Dx3Si5F?ggbm>(b4{#rZqzypxL`bt01|3izi!E6gvz3 zg>z!hRNPnAy_tPZ0lMgL3*?_ckU}s(w~i^t-%4_}lr)smw1Cw6dz8V?x(HzoxrMs; zEIKGK1`(vBqKd=REhFv2S_HL{ouY*^P=~Vok8i3aQ@^;~ApAlQ=X@|c|V6`Ud zuk`HhWL+X-whO!l388EPU&Gt#p69m%7-y#L?oHl+yU`Ca@ zsOUHH`Mh;Wv6159<6m>>v zU`c2Tau|m9MlF(ARjpol6tBb7D^ea-0QOLKVnx7mjeI!6&8a-x67;L?)#8)}fx{vjQ;>vuAw&$fF9OH`Qgm{F zGf3F~OIfWZuEKMCwEFK-A{&RWu8}C~WZsD)!!FX*C_RtyCS*^lA^Q5#TpmAGDF5 zLh}zqR#AKNiovIx;b$ql3+`vN3ebODky7hn#LHa!WF%S>OxXo8wUCrxI%o-VM_4v* z5jeUb0Jn7xzWQVQ;$HCJ_YFs_6vJJ3laL;3G2jAb+z*zr<*S=%qtuRl8$SGMNbx40 z>7J{N=u@&~qtMH`nj^J20Ba2MS8=1xY#&L@@sQfA3A#7$tEnyooc0M@m7dIqMZ(fo zBEV!UhW2@BjX*V;q@4vb1LO~t)uI>kaBRtd9!=uxx)l()PUsa+*8WS_Oq^+ENztn|gtGNR-U5phZ_+c#s5mO5_XyV?`Mo5znB^~M0#;Muyn zF=E37*}>LPze##)?p;CzJHm-lQ-SIw)=uCmT2`^e-NTXwIra_@9`N39^55&wuJaVv z!N4Go1y`%0gC?UtFYJ&rGn-1s5haU4vFu_2`BN)x9#zY+%L&2>-DDanka0aQF57iY zSiCDEJrVA-6)%C&8=>6x%UvI~@w43LBdVm742r#q?1j(4^bvkDw)Md{V(JE%g`!tX z+I!qMi92YJ>VC9q+2Nem8E=~@guAp{Wq>$ZKXa8fk5tD;zNaWFV;KW3r0bTFQXY+} zk!Lo{dh4y~>bv2%TsKxhIAbpMh;ZkEBq?tZ!A*IP9Uw$@1@p`~L2YBThTW#ISXn;8 zQVP~sWBIV!owy5- zzPRCn4L(a~Cpo&bG`EUu`hG-jMW?DCUQJykmZx`}+nhMQFuEbwnVF-{?2tw@{Sna3 zr%3+YnQ|X4yw5N{kMCo!)6s1B%3vZqMGzJ_NC&+k1}5%9-YZ&5JsWbDt9C`!I*)w4@zA12oLWW) zDFyj>Y~2y^G}OBL$xw^u6c9T66F&S~UiV&~MuHFvi3v0zQenGbVhC89z#LMB=_%=3 zpnC5Huv9bz3S>-g-dgQ_gi-ArPRxA6zV{`FLH}>_M?tN>M@SPwUDv0Zhw%4;3YfhS zD+o^~+L1~;sR)jwydmJD0XQx6_}Oj1|DvVCA-Jq4I_Ow9NKVV(YF$mGn!$oW8;)0x zaK(fay`(B7Ec6ti7pXPsX(iOvWTp4!roLKoe|N}AQZHxf9g}EgXj#DPWy83U(Ta5S z*1XVZp_fx^y650tJd)IF53jz<)H=x~XeoKqEmvAJVaN4jw5eaJkuGQ@bj=C!2=S*h zcj1lE^C!$#XCIp0R77xYG>cXSsGAwojA6mJyM8BnBR5M=mGJTL9lPh7)_%}(bw7tq zIR8ZK;y&p0HMHdK+!YUYMj&5YEO;izBj3Ureb-0*3-_}YTCua3sV_i|wv~sRK|TKc zH79l?Ek^RD(8_oH25t1GE?%Yt1ap?Va7`)Kj8&&n{`K!q4c9^QCK-|^Z^_8;JP~6K zf7EaoPnv1Cur#VrcHZ+^ufT(rlP?9+d8&Eed^FTLnFs(r|CZD5|I=obpZkvQ|1}Wq z(SPh|{-2o@--Ac_DF+7Rb)%qfaz~p8AOUvT$FRN_?lJ`*TtQK(P(mKrw2KA@(v-MO zfEoz0V!v~{bSm&}K?~xhQI!F)(2(jQ5f6Q06b;R}h!0-lK!&R9Aq<|{`e zz+g@VGPi~EgU3SEaufkzBh>Lk<0|PUEdt9*2QfbZGQm$7#$V-myc%6e?1FMMHc>ig@jIZO#0AK)paPU~Uh5zIu;^wmlyZ^`)BsdklLOFS$S|N> z1U}o>l@=i^MF%n3{&OE=PHqKiL24MjE(c_2L$1;k;H_rBwq(erjSf0tOmSkgjp>7O zIm;2f^lt8FK{^Yp?Hk!t<`tJS{GkvJN-k=`M+&uUsVMb3C4EX#Xz?S%pxo0 z7U@mFlnYHRW(J#)P*Bpl^=*Clg4^DEcw?I5Y=fxLFZQ4HAp@s1PLsN9qVyM$@fz0~ zztrB>5Q>?+Uu`&bPuc(&nUIGb7QlIMn~%rH$VRIra_!zuAaqfc+`Srj6zj}8HZp-e`g%noc;D!S@m_4$LK_cz@66( z)@Hb0gZ)9*gyg*%6D5HT7?2#8A7_?-@Qnke6tpyu4m!H2Ly`AsyZ%7MyT1J`5J;U4 zt^l?N%LD$s-FIe7ehr@a3~3OlW?^_;@==)C0LQM?eTddck=ri9&vGbGy?V$%Mjg0k zbdsjwVoyyC76hf?4Tq6$oM1{Vj!$E9&@B8+o$ z2LR3uhugC1R74*CBvICX2Dt94bq5cidAFzyOJ5<|z7z1HYsJrK1GEcH`CPtrimpdce=@WCsiO_J1fzzlQC2)Y;c4M>k9j&zWcu2U5P5d%5ZN^%xzqE?4-Qk-ZSyc)<1erx9l?y12?U)(;dyB|p5-eaRxdMzaDxlogrmCg4a8KR{O&2RrU@ zawU>Owg??{(*IOiXN)R&HHY`8GFu0Q2wg~eW10L|*r28pJP%7>*ROz9C6(IuzV z>((Tz_WGxXXec^a0Qo%BDU+IxMi2*{>E}ryw9i;dmOXy=zY6;yKkY3drz>}lmmU#J zFrm%dmNq?h?n-gq7*a^C-Gxy*D=(QSo6D%^E?cDKd2!Z%u8T=)U#lfaU2~aQal20 zQ4eO)BT_`g>7b_XkKu01%ISs`G%e0Qm$Y(&uV)-z`KPhpI@^Z$S#obae0`Tg=+OLnT-M~bZnLfZz!jgzhn=i-`Br{r=AdX4{Zdg*p&Nu+v#Kj57oktT;Zbj_#O``B(V49-MRBe1U!y*RXE5ZfDCK0~?NAC>qgx3M@_yiwG zJ*n762f=`ncC%_Mv0QbbD{hs5h+Lhx5-(bv{^VC(mHq8K(FC7pAsSN^Bp&n{h40B` z0~pH#FvtN3LW+4!O$h{ARzDb|2&f0J1IOL|$GaN3|KnW^9yE4R(gFs2R{;$kOMmjf zn(vOd@04cw{@e1e+g>YRA*%`n24FJadzw!J+pOa?8e4WQr1DA#YHbZzMp~+Cu)p6t zOKgYm&H9?ka7am-E|N{h-S^k+Ja(~;bHHlFXI_*~A>)A^AuLT#9|>N!xO*dl_tFFZWN7)&@H|n1tYSQGj;G zF@g68L0#eELyMK+ujCG_O=lj*T;*V%2pU0APVfK$$Gqai{#Q9QfzQqAm1C*$Y8eYW`QxFT1FvIZ8ULP9dBJvGFqp4QFZ8`WU0CJubd|#Kk8bP zw&srpC#}ceM`B;#FA_tqAVdEVGyq3~*z5sq2~gkDSVWpxSZIrr7)mfu2O1KId{;I| zZ3n1=q=SHQ9)@|x!?UDw{uD^vKQWFE;NO6$fNw>eFrbV!$WhJ!YFtXiDLi2bgSVXt z1V$&<#}1f5W)RBT5Wsl;vp^XrXj%oVdzj2g0j5LFnNeUR+<}u;z<6MSoeUuTXF)(2 z55ocm-vMSyr1ac?&$j}GoiM`rH*fyO#{8$Q|L7bxAXNewD%3(OjXfk}ejH3Z*9dfF z>_>2q$myY6mR2*`RlH0$fRWO`1m0<8I>=wZ<^hz(B!G0w1G*7^b1^c8c9t}$g^z5X zX|VQqIO)LW&mZUhobQ`!-n+E)BuCpvlx^QXlNTvPF~k_c;PhH+-(unvrt3`k^TyHY zBSqKSlQ{`OUHBFF-LKp~KpPP~6 zE!!V8gfGNBLZioivfNfRhnHO;p{976tlkLOi*q3<1m5TmJ3hSZwXnrc`&#>7nOdGV zT!bC~ufw4oFbdsE`ea8&S1K}!#&Xv?Y^vU`Nn(Ox^$f6AMkyp3`Uf?qtmSAzea44t zBcpXZSlrj=?nJuo*nyeFi=S_cVBeZ(W<)wsNnf48!En`;p5mXQ(mz-eWCd6qbSYUYvk6+Nd{4kM#WOw(W-9C!3qSR5DGH ze$uLb;ydFQ!vARWuo!QePGG%9(2gwhtK(1FFowGL5Pi5GrAZcDjDHb7mm~p~BN^|& z;5$!LMXfU7(Gc+glRtx<^^Z>K%>LC909@Ijp^e`B zK=6NK?Y*O#>bia5C@MapM3EvOL`4Bb;6a*F5-b!&L_tNmh;#uFkj_RCL0SX^6oeo} z1cXSJmM9&O-lPTyy(OU~kYsz8&wcOro%4-z#(Tao?jMW+J7KT2*Pe5(-~7$8=o3%l zv*rRpdXP@x>SXkAgGoSTrX;bpyfB;PM`S_^rfh7535+hqSyVo6L~)sud@u_e_evAG zCxV$I!ZWxFDDopv*O|{BCE_$$7Mj^`TvocbY#?FNtDh=!s%Q=OK=({bNf`a0vUQD< zw^PGY3n|^I7JRp;Z5p=3g`%k;g?VPn^!Mcgc2mvJh`OmDn3E5&7!-z00~pee~G(Erd$+)3Bs z$O1VH9tN3=3=Kprs@#bA-0qC0^fqVxsB`9Z{QmIi*EgA$9jsPe#@uTMoNc?zO`6nr zPL{3uH!rvoM+U-KwtdICVKK&Czva9AjmV|^OPKW7k@)&-Bqw&ZlxOTW93pO8ph!SCt^Vf7o*^B-OvTiPlNvqsfX zu0DIN))99h;TFr8-L-uEFV1YfNnDx2pYVQawZ7zUg!%6FcW$_?nZkv;6l`}*o_|#Y zuc-BYE!#+zBEogG$!V{u3P zLxlt@aNHAXghgCZj{~4YRl>k$-m5uHP_~aK`~fLoQ2-HA6|<qZIf-$B?n4z(lt{)Q$5@B<@<1CEuKLj|td^xIpAc$dYlbM1T9hJ!sqnib6cP zWsIW6+vC;+zX0@*2|mY6oaw^M$^W4Vt^#z6*L(=o-bsf#ZNbnx<5<83yvPI3m(~^v z@0MV6?kQL>W8QUS1Yd`$at3jk^RNQ#^S$y>LK)^NU;&kI~mFVe#B#)Nt zTk;Z^^VerlQtpwD1I|zbOUafz=$-!3jH+C8HhxfB>C^N{frO#R!m=shTR$?BgcKcg8GZAz zyw4~f&m--R{e?B|{$5=KJ>6>xp2Q#^DV$k)JVbnkg+oaFz3dQLOe!2qY zL`(~_6A;XsdpMUx#59>`mYxT5aU%a};(~N$c3?;OPWrfAUBt6){R8v)9W^B$zY#+` zwdRK7%JqIa7rrLfRX)EAZ ziNH50EvdLJYLV<}Hvv*2FT=sS#6^Aw^vu?w46k9L-Mdv+yg_$0hK$)~>K)f`Zmxg3 zDctkCbUwLr-LLdYY5A*%zhc^y>f_6sm)9OGns%~|bRVPIkV21D)ZC-E#BQ&`4*tW* z6$!Uvl8o+qxvth8lnXg;UM;l#tUCy;&Jf9+e@Z;SatoLQ{)xG_>77KaHP2<4o?Bm; z0l^oE%SLNg%YF64T3CP44=GXbhBVTP>I*da$?Uo|fx9n79@N%pRrjU);Zv`^I#`RX z8sKhw?}>NKP}s5RwY*QUFwfho&S?ASkifI!(UWazx}LekgF_=e4x3J)tB@Z-;qari zGI2Ie{SnJ_fOUH)B|49~;CzY6l)9n6oMPWJJFNZ!r@R7WCo!V=H=;kAj=@6jiU@B- zdu@Z%BcQx`;8RpwQH(%VpQdlKJd&Oam%NFBgEHem*Ive{ojbIHhDmW&q77; zU;gOg`ArDvKqdD$msoG#&DE(%RXehkHRH1KC zBef|!u=V;wc5C zw6V$aI_H#m0fzx{%WdHC^(@4Gu)p3VE=RXS;6bvbz>_za2Y0BFy&N z#$DswRra(hfDX@=9vlC=vB`A;)gCPNToloV440*R=sVGMR%V6gse3zqv$z^vd^|D3tx;56G zlfAjDQ5y_A#>_=fhPe@Mx;iJbl3zmbN+f|L; zGa@F?A0|m*Ib>mFQQqE?w1{kW`mRc>p~o1%Q}g8mbGcL6YbRT3+Sl8MHjia0U)RE{ z${PmFqUa;35$wJ+Zll_v*;g)}N=+aO*UN{WOtM3H6-%MI@2T?J3J}f$WMbJ!i*qKZ+d3#;^3HW0eO8!W}+%2G;Cg zKqabpczAy3wn=_Nq_TxU3$vn{7-ze6lG(gY3fdOw4<2VS@M@~EAtq!aLM8U<>xQzi z(-K~qmZ!$ltcWiLvXBG7CgT~MoD#5isu-4yr`+UB9K;Go$xQkk6dzEidAitK+Gl;6 z`7~*Xa@0wg>W>v|f?1|0#-i#sQNBJ;OJ33fL9DxUYdqp%0z{2BGpX(xae?HlUUm(M zL@lMRxxFLA0 zVo-2KTf}rIx#C@U(Q=}&0lEuM&qW|?PhDP$6ZC;*-@?mh@1NhFQzT#iH*OL$mU)|d zU-|ZzC%+Nvojp@=KMYnhVqVG*@uhJbStG7Kuw*Ax>u)xbHv{^ABW8)i>)7H3b_e6# zaKOUm*XR3}%nN@bT2I1jUW!x1Ql0R%i8n&p$w!Tnh+!qdM|Lo~<@%(Pfkp*oM*T)0 z6hGex6B;|lPAS3iFRHxE3BWsP&8*nQ6qP+W!~Kl+s>`oC90#;**OTaH4f;PVXounkWou_)Yn3MUHW+)##EKo`FAmK}=Ru?hB&>;dPk-CN z1(<{@An38%)0IgBR=2qXHHfU$F38KpF|GdmUApq-Cg%&NPSjfhL!;;57`p*KAlCio z!pUftQu*JA9sjUU6yDL29pfZwqa~7A2EGz-8PGkSz}@8-fxam^Sp9Ry!EU!W1}L^P z9|J#$3V-;L z(x^-jrJj{b15K8?kI3RPne9F1Y=$A`n9k#CYt;cm3$O@Ff|^~+mRka)Fl@}_=QnJf z&bukrLB7WN9b&%4JOOhYQ=IH~3fQByhToy$pQ3_a-dK4;VS!?VXEf{QoYzpv)LB=_~22CUhUx@5#53X*s#;_`4rPuQeXfuuMA*(eY1m_fpT(Qsb%gT9xuw zidD~?vK$hY-QDG2UW<c`rMZl! z+C849Ed&q2KM5W{?G^=DqpR6)Py)ckGELX0O`)vIe_$kOAu4SlcF6eNmeYawsNs>T z&J}!z{an{D)5V0g^&Jan6&f6z{dt?O$H}OC;56tM^Rye}nRHy9G0jwXqd3loC&yoKnBE0$epfx zT5&}UGl49q!`^&4AXa8-xF|^7-Jm+5@rPfSvqY~=)RVHZpU$d zt2<-trBHhgXEV?~9T950txePP>SB>W$`!>ht#9xRLz-+-Tl~{fFeo*+Pp+1 zqh>TaMFAJ^8?h?DD$y(1v7Yk6z0Qf(OFrUVpN7w+M`HnQap5I3ixE`h3s?W+n-OxK zM@jZS5G7xF-PHOozfU(is@l+FALkc+(cj9b)Zz`I>ik9|>YZ9WCbOmXkS zb)6@Lb`d10?8+x)+m;a<_QyHd^%bc2D;e$r!l|*$OK*GOWi-?4>H0^cs%X(~#M~Ra zQWZPCTHU?eH6rl%_wtoxT0tgb+x1yw9K9uU)NL*%~v?|jyM~}Tm>5~vC4uQR~UI~oi_6o8jF@^&FTMY z-@ENyHV$G{50PuJxMO}|V;Y-mdNzw@qW4XP{L|?hYD{qg1N0X({SHq>CR3pLf*XOv zc|`%1+)v`V+&qyX4#EX-lGDhCezTNA&&3anWZ>-w`qM>xSE0TJ_9ndH4L0rG4A@p( z88GdD?^v}_g^z3kCPZEaq@q0c0a5RBI7vUT(Jn83^IzY7#`+?!X+a9cq#;6&qn^*28cuc4iVm$1@KC)f5xVE-d8JbV&9zZxPP=>qE?F4dRcHJ0P}bEh3J_9S)R$ zzWHBo0i;b@#vApRWvl9v{n|ghHQ`botXi?sm1{m5?(-IgoJ%no-`iB1i_ z!YnDZAN+IXLfXFz3TLVxkmlW?f3qmUps=DOcvFi-(GDNY>PQ(n1(Gs<+>0 zvtznL=x}UJ-+&g9JqK$VzKJV4WGLomnSenyi+g71CPYb^ zBTn4*E{7v5qcK#@i@y=mHVEH%zG)&gSIq=7Q_3I%yzPO4@7}ha(2pd}m3g;<=F&r` z-w3Hs9DSegfS3+3B+G6~r=9~a>nV62KNdJ^ae_wyJ-afuS0v0~I-d@NMIp!caew`8tWJ% zI2x_O0Csm*rR;CS@td4@gNH^~s5XreHB2sl1&JWFFibn0z=QreO(>CSO~#xG288uH zIltVpPzpq{^pnLH|1- z5=YO&F%wFNu)K!ofbn)?giXOvgcH-gVBFX`#rmC>WUMlG6!zv107mOCu6>Bj zh3P4V>qL$sY58T>^6RuB|wjV{53#o6%X zB0oN3Xe=Ow)#_lPUNnrMPrkOwaNT?zOtApgY+oo@pDj=a)Z01npWL}Ks3^?3B@#YV zX34sP@P%KcUrd{SeQ#L3_sgXZd=!`I$~V}}toV1on8k;U-Moc$f*TFIlceND3#F?} zS{@fS^ENhc1uiMIHMQ#FTqtk$PgW1Wd;*R5ZCT)T?HvXTcO1-zY6KpiA{Ckt1SYeg zle6Lh&O!jpCXsyqHzJ9Fp+kf~y;>gTlW6SNmW=?j zwgIyi053y73b$?v?y$F27M2N8Zx5$-FmB_7F>?f2_)vl+%N#|`K`TLB{+2AUvqVNE zg03P}2c+L0L}t<%M240zMDu`ZGZ}+G{Qkqdx-9s>wBpE?Y=I6|@gqRmbJprdsl({< ziSflh*@f!;;neTc2(mJSEe;e#0^S@b)lC*~_tV+ufqY-qPeZz=S8K4u7**ibt1M_F z?<07ByrB~U4NFi<01-T!AuxB7k{rVo8RFHJV09YQej{3Z`n7xNe2qXBM4JC`#z$Y4 zPd|~G(hY5i2B~L=M-IG}t;K|a)uZ*)V_pVlBH5*2R|?UlaS@wjc~Nu^XI@D43}S=o zu4{1{eb~DLSeQrN^E-$vCR*( zNE5;{H!AL8Mf75$g(8+IIjr$LwXxB(r`WkVxICBg^X`}I z^tf{?2y@&_pq3H@?`MEsGSvPEk(%WRlv8hktzSed7+X&~?kqIgPI6qo)$o5cMErli z7_U(@%Z!ngJ7@ASZXC}!*pTagX~3(+&dEzGAX&fMawD-^6EhtiFj`{{YZg2TY#$oS zc?9}NvK$%Dau%Xi<-L7j^)o+}+2_h$sX*0~i#&;^*A@B3<>0oV7x$MKEi|+m# z#<9*Y%BRaZutVRT{zlvuu2TU)7qO}R29@0IbNi%bDU3Ui@WF@v0nNV=A5)PHel_JpGSI;nsw+?_pd;Q4Q0*G2-*Az>`@qiO{{6y8 z0EWC85qg25VDt>h;qL}Cpzk9zaR_MT$sTQBVxmavX3pB|mZv0^FPMlZn6dX>hBNKW zb7&&q>azj(G}oC}%u5u1D2YJ}B&{$k^-;{@>2rad^jxVrF@`nmJQ7E4udeF@`v`XS zk<(?EFEMgt(lRRbZ_J9r&^c%Y9g(&fTG(YFrTjhW$?Q)OEo1V`5Zho7+xLTd>TTU$-7_hF0A+^}%ym29GII>rYtz#d=7S9pYLswdB`!%^SKw z2$hV(_u1x=xZw*}7nuS!+g5fYV665Z#TObM;IyuWes0+?)S*QFlXL-&$oIr}9 zoBv(Z+C@)HUHvxTl8$C#1knI!bnWURA`d%2EZ-pYPm&GdKWJEn8N}-j-Y^Qtc5TE} zu$hM^!vX^JF~1QCV)dLbd0QKN+R0fMG8yM;vWjAuSpsxA7vh~r9Z-N^-7M4*!ZF;| zhd^wsDdA=0?uFr!z%jCo~1s{km? z&Ffm+2$W&W!eLMZk?NEVtp;#R&PGFXp?~b{o=S<|2#HS4&$ldYqGh}n|3BYSWg%&g zfmc%i?$`o&fL^0`WJI6>g{t2u22ZM-2Kf)$7;vmf2OO3iNu*l+fk*)e-%`G59JP!E zp7@GUBgh#N{$o-bu?*8xX(04cWQcX36L=Ta8lu^nKKB_FlcenvW6B_+I*J6TISSut zp$(Snnk=wv&s4zWvyy*s92hL5ZW6zJ0=@vgnIa6n$qUs^(2UrJAfU$MH^ADo?n7)i zO8p>CJZ5CWVZ>;NDxn1cV+g=D7s_fc`7uTsUJ5hUZZ% zoCc$7(sla;0qicsN&wIPEKIjMSQ!Ox92{1Tg(xfk41$>eDLrr)9t#PUxWe~ta^}sB z#VLZErAjp*F<11Pv=`mM^4DS+=Ax3|i6zK*)?Z!3Eh`OCe3HDyAF0Y{K&@Y4dHz37 z2p+C$61#mO5VU~!*Td~t*vjG@tH!|s+TobFSBymvj`9G*8DJyf)5}!-CJf{7pHYI; zZNpbx8D?W=m#MnoBH#ZW+H4FEKIvj)EQ_lP`e*1EEvFxti6@LII1C#_q$-0|nGTU) z$yf?NU59ApFsD6{^(75?uT2mSq#~qslCpgQtpw(?3`|r4Zw6T+qC@{VT9M_t4j!Qb ztaaqRzooz}T^XJ0jN@cvlVKMpOzHxU=hsMDzoKRZ7+pPp^y20?`lyoM@L(-MYRF)P zMgIoE{P7>5LPijjFl+z$yI&;9{nZgLKONh_>csOwnp00jdIE zwsN$9^tj>V@&`3)bNO(nTI?7p`B_2VaCBr)6>6jJ! z9*_?w%aTDxVAd~sqF84XNPi$)2CoMmyqA#gG&?YG2KY@Y`43h^%d(6yYZps*Y|ylT z9}mD|X_v(@w57lc(O=<&3rf^GNU@u&5;TRkR2^r7AV>ZE419wU z%-g>{YWdAV=Br?NSM@DN=ZBRWtEe|`ph}-Kr0u`+J^zc(IcA~fz*^Lr(+tVlQ8F@W zEr(6@xf;h@51Ra{&CLCcc&yf=15S2xe5O`=QmT;mfdV|Yv7JOC4c<;Bh77&@I#O0T z>HLIgN6+-z%HaHs5W!oEra-fqIOduDC)y%*w!xUw4|HB}qx!9vwN=A`6)}6*I&?@! zC6tpM{&!4j4Az&E!&T>dyK1wOylRkK{Alst&@S^<-|k7hpH<)A|3(xf7UAi;ci_(c z;!+64>fDWUbmodZetFgR2Wd<(W$+x09)2m;X7$3kD)7=9`b!_Ij~Gh#IBSD8dmE}JjYdI<^?-4%BXRJqS9#)r z`dg*-u$i-*&H(02uK&;80{JWVebNAyGnar{^>*#CRco%>iyO5xG;<9ioSMV%l=OH_ zY)!_VZ(M(2cFE43v~q2(pgnvn_L1u?bMH-HBU?ZwRM%69FO#=p4~Ge`?U=Sn@?Wv! zqbqP0 z{_#^H5d753{c+Kc|0b@sK?KrR+HS!kTV99ZEKE+O2sbyIxM<0QYoB$phTZ>UVlnq| z_PCHXPP6p{xPrb(z^CX^xHQXs1EEAHv=fXI&ng%m*~mvST~ls(l*(l+r4o_(Ui-y z)ox9C#p-GXM3LGbyd_j_;mK4ZwSHn9WCSSIxmLm7ilTwb*YhxhU!Uc#=qv3Q{TAS- z>%ISdRpIOR`w%8l$*y6kNA-EjU*b%A$OE;&j@q_7$y1f`IP+qfxPRah>O>IgsxC+Z zc3l=Dt3%6y`x#ynx`PT)evRd;CT00t&Plb6ylXNuFV=a&nz>ePTxjqGa4*zCgkh7W zozAenTHft3r5b*Ts8)okIT;=3aFmwt;A)e2^~^Tt81;xPcWO6K)twz~N0y2@{9oY; zn(qZFt{m4y>^pb*G3YV7>stosNW1evq(PU)U*U3KD&mr$RV@NkBmWP6+ygPR&lVii zrKHS+4qw=VzjIh?*u=pm{zTNpSwTDA9o@ICCVj~F4!uSljk9pvj!#XrC;Coqdc2g| zuswEjPYB=9!&&uKWal5gLa{;nq>@T$B}N3*UVp^{c@;fNR#jiC4y&d|+pj+1FX-b4e)Je;_(l4uDB7DC_Kihg8i1|S72`8ojpN{pk(%`>5sRDlsjwSk`~4;!Ma!Bu?8eORp_#{NV-)y5atbC z=eI1gjI@cE4xjy=7J4H0uw%p$WP^l+P#Y0BPCEeR ze2m<{oB8bgG^C!c&we`)ANZ4jod|*7WBMFm@qzq@s4?q2GXijWY7EQSN*xr;d=k*p ztoNmNerct-+MT}xa#Cg)*tBdJjk|7TS24ZWX&VarmADG#4B59K68huwdz7ff*|kCJ(n(+cXFS=Rx#O<%rR?$ zE$!_VhzK{Q{rJgyAX`E*JAraK<9+!f?3glLJ4dRS~GdBZi zfwuaoRnWmXr%BM;#j)~K-BnlDPsTMRFVUh$MgbZi-I+xTGLFI`w3NhGQ#7TF{0@gK zy^Lo8yWBoRr93_B#NVKiy!ZU|G7uPOP%yg=J>Ldr#;p)1&d`p+$h9TU{7aLo9ZNzl zWL8g_N>5;&mf*dCS|rfvBKdYHu9n2D7LY3}=&y>|G$Y)}?=M6H)Nrdke?gjII?CD{J~U z;ttmID*lBD@W?y1hMwKd9GrP{Z}W^#yjD)C&xH9N2;>dcRpv*ku;iWSF{UHtr1}1B z&*b`qCnxGe%3Gc!`+wRz`wFxYu32ovtV%dR8?y?yWtU$Y(?gUXS7)>&1G9wYOW16z zyTx>F3U?{BEYsV`PW6lxM0diD;+xn9O*NR20%k*NZo?#Y@5nt(#kOSgHiv+nALr_y zXiT4s!&Mf9Va7eeE6A2RH<~?>LX0QvA_nT$CXxHD?pXEQUFCQCs&v`suKHKJvTzf7 z!%{PvF#&}@1kj!TI9XbLQg-uBQ|SFr6T)+;S9$L%RpIAw8 zVxcfhz9qC^=UIQntR_1vD{kDut;oM4@26H_Y07z?m=O63LP3JU_UFm+7nV-DryhDz za{@ceYzz*nLi{+~>FKWHW2^V8X?sxHirsb7R`B}KlzuV=!&-N}rXef_kuYBD7>yRO zDPZ+;L$|x5qpQkyz(x9&o01*PLzYpAh| z^pK8~f!Cn@UT-IdHX!N_f}l;(SIiKmKUu<5|?`>0#6XdxUst5sB;I(xZ-zuY+X;8JpJN;UC3N4Dc`2 z+QyT8;8FCa)HO|SGpI=GInl2P#{=A1sL_bn1?uk}lDH3bL93eRF_&8jjdJBh_rVX-wrM2Cz&( zm9{s@6vOno4ql`IBVc|K&yp_1!@6lmtMqCcfXkqlu>nkx01(olxqOB;7t*0$lnFB& zq|=XLQ+ORiw?_T})S?(`4E(O;YD#ryQ?hF(N-|m|dEgU{McH0#I zO`xX?oFl`CDZo0*FwPxvx|_sXNqpsu`iV8|TM=>yCR0xAJb{G~5gI~I5Bd;G2f`!( zBt&!e#KBA=0mHme^VS1@h?>nxA=ZOEq%%7Mn%CR#dpxhf_21`yz7s7$lo;6l>x{?K z^1A=ZT{d`lb7$8-2<8+*+MCkI-l677hC|R|W-<1W?$Ams zhYE)=!_FU8NldG1T(WflC63|}U6sdH>M<@%`ms4CmFHme@b%9R@u45~6n|FxYhv`g zgOgL*eA!}FacNL^yCT0047i79%$m_nvcit3F$DE9W_V1U@rwHco-`uzWI=Mt*;O#X zJ0FxAOl`@o65ESFv?Is}LyeeJDkqa7d{=TdmyNDRGvoJWqd!$uk>Z``$@cN(=IE%Oxv?78Y}mv`g8$VF{kn|Z%>|I zy2C)ydyYrS#Y0+aTIzhw&8!0mZ&aP-E3`CCyFPegIN*URIqm;7QpLD0hn!I7y@HtTQ4~I>p^qvBJXkk0yL9yrTEk=IJaMk31VGJlVM?iJom} z_#qplQ;Tc<0jfHC)#v_qFM`bGtbZNwaP8bbCx`s2JwYsJ0rFq9;-D>`g&NJhli~UR zDNtUuv2Rm|hT$?}<2S2mbg03n87ns$0gPocD`WqT#SJs9C2!W4&QI_vm2so|(^NEoa$6n$HYV0%jV~%sEWIL4H zG7mVt!GPndL`&FO*oh7F$H=Hm<-dkL$rK2T*dr-soO@ir^9yyW6bRcAB!J zp0i`WHNqORVAR9TSk}7p+8I^Fxi14Y!I_x7*Lg7u`{`hU=a)4;3w}RY)&=_ey@v!r zIPHedE@!dVxV4|YYzJ*-RTEW$P8`adL#j5+ECOA-{f^}`fC+%A?Mkq{m;HxrGS{K zDF*&=I?(?;gkOehx4RE~71k0%EdVT=n!l(~${WWlL;3$kbNdp^fkE8NyXQ4%2|3P?%p76diHallkVm69#Tb;T_#fbI7|8fttS zGL}1Pc3FV6i7vSxHBLCG)+PZk{|O9!aeM91x6#%2ADC@y&)+@3PrHa9@#Bqc%v8bt?<*Oni#rWTA2!3NGeaXu5&!=$0hY zd-H`l7Mg2u(`%^@qYfFdYx;5(pRHTg)krXr-z<*wlr1y)6P=y0dWB~|H_JJD4&J+; zmcmh*yoL(@?RmkM&MSZ(pfM~HhM;Ui)#ANUSwW{wQIw!*jj-NVrka;j@G1rC@Ezk9 z2nXBNp7RCqHI@pMRHx3PE>=5qD)m+NM|r;Y$CYuE3rCCHdZz}AOAMTO5q(DuHzN(! z)4?grmr>U2FaFj@7Mme;4Q%%WSy_XcFCKXAsLjA3I$#jg(V`j)>MY;z-qtey0@JseG}lDM#re?LHur-!4(yfqx)GjA1v%Vc>&l)o@q zK<8{+8_!ipywT%bH9kf;n}wtdxQ&C8n8v)TCIx_ymX_k=Jbk45hq`Gm!bW_Wv*D<_jhyAzALZW5!Dl}P6-v~nf*1c+8G*=K~ zvZKjc)a9Fwjakg`|Hb7{dE4Te+TSHVlE*0YXSi9gO*O|4G1hc@H%P%kpZO0uK>Uh^ zerFS&I6*eLx<+fbhuBrHHEEI4DO-{p`VE$X;}~w)4I&qkaV<;9$UxgYP4~cb2~Ryy zY2GW>KRtI7aRAbt*B; zg(drda1d8JcBZ?8Eql9%B9mV8wfM@{ufNPc#2kO;f|`o+bJYi!DtN`nm>N-AJZ#3W zY(yWYiDVDs*S!^%G5S4c+uonkoZiYiIy^cyX}?M6s3@l6P9eN-2Ln$q{H{DJsqlAm zm>6{`ze8hlRmPZncs<}Yz>U-1S8s(8N%TIAVo zQ>nr=G{<6ssC?H3MbIJfmiayJh}(Xx?!pf!MgX}tS*Luq+l%c=*fI*QGuXLv~pT@b`oJ|TW{#55krL3t9 zD%H)Z`bQyukQmdxiSBOd4;sLG=^RK*u;FZVfN zUOl5KTO^gLYV<+YmxddXpR|0jL=a)L*3w1AyN}cOX5!T391P4e%u?oV%DoU_2Hw{f zk>rqt59_16lbZ-4-Q&30p(E_An&|#jXX};E`qOQqt*)nFC~?MjGsW~?F%^#Y%W)L= z%gNe~%S>M_bo$Co-NWmMfsQ~v+OGb}?BE$HA@nmNLboJ(oUBV-ohv((QHM$tDIcm% znIrix2VZj)D3`v>mYodIjVR3jyjK+URJG8RvniGuYa;2f!8f4&JW z%iZ>W&p&;ltLC_THc>$M`T$QAf2{GoXBF>WotzB10PSRX^i=!gTRN0cO&t=SE~}mX zxFD4EwKpq403rQZ<6L>LVvj zSoefc@472el$m!2w5cr8V3O}VL`@yJx>@WaHgbK{QQ$O;w`&2gl&~@CuUAMD$5F|X z8pJ;7QfG1JGn=O3Nx$mcoL7~soWB|iF!3R7Bxo#+JFosXf=7X}cmA%j^5c;aOGggp z=y(UzJ5Mi}bB7LN89EkK zcHu6Yjl5iM=d;z)pUnMVvoqwkT2V}hw!{pC!Kk*? zb+x3w6p1f4<46o5dwIcwcjt%Po8id>P^>E=y{-xqkwRA6LWpXQeq$q z*N73`u$S>nvHj7c)xFM)@YBQGiX9U?2*%KQ$zMcT()I^%G$fid z)guM$KgX~mw4z%KZnu-q=7?36cliTSZ)ZN+QXlT^Uil37xH-%=S~E2>lTTqW^-=A} z8gGk4he`Ykl0B_qqh_r9qmuz>tL2@j6>+XD``YYO(l1EpGAi+C344fE&7CP1C!WSo@Uetn>WSpGS>+Ce2vn+XZy6;- zy4T07#h>4LXwb}Bwho`!fFhqhCGo7AAJ(UF<8re`TY6kPoP__9k#Sy8DqcyLcsR)2 zaLGhjZ&bxL?%Ul;iFyUB34s`AO4ADdq;CzJ%116|KWc9+yKz0d6bKIgBKf`>#FogW08pR4rUKNT^J66!k0~)#U9A_~@$2I8@GV!>L@PcZsKQCpJJO*WJ96 z^}bT4Yc{jfhv$<*`p9Ni9y!l?kL`+Z`k6en)tbF>3$_)+U!xgC6)0dhv_H2esHjhM zX@@ELkxq-nH*ME6N1Hd<+)=GEYU3Y54!Kkxn&@%E+gnHulq~uvF!C*z`n!g=I^5kc zZ*k}JQ16PZF&#B%Z5UbL*(S@h)2%9EUMa)bze-m# z=sueUb?ma`wbIeP#E^kTj>|RFnMCWfg-^4!N1pp)cqLp+txtB?UGbErl$(tXS zH`c}DCY-sxhu*H~{v24hKWIeXDf`{NDpvc?vX)hf>!y)==N{MR=(FWfiX8`yFQz#v zm8m?++3y>&uGM>Od7sX~2ARQl0<>a1_LcGiBY^k6h(9xWUvRqp;3#i!=1z*OH`MjPd*&TsmQ{?U z>)>H#zjAG>x@+T@w#Yi=60`ik8)fav($*Rwycv0tovdm4NgvPok*@C9vrnWnOBl@DG=@6_Opz+cv9|F zZxINXUgeu6?Pj3^1x)k<;#N010F(#SS-Y zD{ON(MSQ;3lJQwju9wI2?_HMF!`~Y@`@b3oN_%G*J@Z6yG5#twKO*}!KW8B#du+6c z&2)MiRgQE>6`J4|@4?tnc3L{g$=l;|hZm9-vUQei-p!mM&L`<~fkXu>i^6I{YI!5z z^wZV+(oGrHwtuKHO3g8r#F{w2VRpoi=p$1)nS=XXI+W?+YnbOj>RyeOJlr3sy0>Uv zFI`OdQ(FTvyBk77e!bZ0syxT@B>QWl-+ytWBazWimdH^W85p z+JL-WdAZcZ;e0O0%+VpXspK5s)j@I4w_EA*5ywNIkSp&p z{LD_hzt_)}Y0AQK_rDtz8>1Aj+~mw=ywRV)M~Lz!3>XpZ)il-H^2czVPu5I>5;{&) zOPU`1B1fKX^g6npQJ0jEMaI2SnfX}xVslKCy4036IC$x)bYJ^7yCh4>Zs|hK>v=*y z8P7giFhUz}2NYiPohs~2FikOJx_sw*cl}mwwNAk`=Qr1lW{kEjemn3|2({}YKl(83 z%DKpqU)~XSK#EZHfb2QICC8pZJQd}$JXo)YS!PCXMGZvvTUtdW+7PKyD+}x=+I%xY z^l&x9$$YW;)DzUDHDgO}ZR4-eB>f2XjXc-=4zrif5os-JOVIe1I{m?rzd=87;-0q% zzFOPq!$PW9QR1Cy+U{Pr<-$_^vGN4`>V^8k#$@8y*22Iy6kkB%rhPSyJJ0hJB~iRE zz|%`X^h)t>MAnTJiU6zFb}D{32|?ErzE?Kjk*9v`$3?}gk<)JbBf4UOj=@^^oHjSh zJ?m9b!V{R_M=m|BmF3;i*72Ci+EdL1-RPH~!RPoYsfbf!K8KzzsKZYz}nCgB784Jfp6=-ad<$7T0O7H(52>zb}t{>~T{{tfgtGXX0iurp({7m`S9jI^AOt`tIO1?@-mSH7hJ@vEPXQMcI3X zHPvxM;Dgq)#dNZJ+v`|EPjf#MP5CH)NAt+s%bZLon0g>K9 z3B5xSS^_EWH+kND_IdU`L_IoF(X%`wJ(-@}iF$dBKnXV-^Cnrc29A$0>K zDW-ohb;GzJ#qV}djJvt$T`h{&WjPh!)LzVH)jP!(oC87G^6)p}oTX4~@jO_2Zd9v4v*+wL&P);z#^Nmd{ky2XgEQRhd!)r=Ut zThrmRJ>MwcqOBho-~duYY7~HB)3Ck(@JF>~04tWfTO(1*Ces#)8a+1sHE7K3v}}LN z-%spAjY>}cT82cl(YyX_Lis6z{^B6^vQ$Wdx0-WsJDM|mLgSJRIq-2lie8<=IST!0 zo5_%dkgxyU2J2S2OPXEWpzk(0DbKJ+LCXQB0!gemXgVQ z{rhYWssiQF_OO~4z-b-O2HF+S#4cgbbLoT_Eb+(Rm)9e__`d#nm|rrZGQ8$PTx?O* zerwju5RLHHD)22csx|--)N%f8IRUoDr2cgM0exsnp<;q6jw zzf%+8*PBHhhgU45xQRIHjih=%90gO4DG@re_A4Qu*gZ>l6D;y)-=!$s6MI^-WjL(j z164TBxrvX{bO<8F6>>p7 zIt+8RrYE~sj5wKaF49wzEcQ!My+{9K|M zXR!hIByR{3BGe>ZX7bI~igBj>w4YnJ3>Urk(AW3p)y=*TV&A{;>VBi5l=JN&LggtL zbM_z>VfAgvP*e_t&*8Su9I^o$y`)+qgfu|cI)q0SG{|yCSMA@ikw&D zoHADvkg4_94aW|6rI0bTbqR-SKOen4OEEZW9KgCVTrbmmf`sI&#a`7aab1RGvg4&(2$*NakIfWY5-Qqn7zI(XLT&(g|I0Xg@REWPNZ zjNLwCwX=%}&pK4LpVMHZMy<0hODXm&f&M$E1^ z=G=h7iOESli_8i)If0Lv@S^yebNQs+A6{!ePjh}Tx*KwX7e@$Fz@ByJU$z`dvCm)b zml>5JtPlCx(B~d$79pGPM`(wprA@rxXBvcsdUq6I(r!`;xsN}{X3swy_Nd}=N$0#a z5O`?DA^6<5Z+H{qV*mD-kfyYe(RXa0@P=+(V({YxSCLcP+BhH4kF8o>{Mb+zFBiim zgV_0)jn?ZKp|j49?xrgt2?@0Pblg?_OE-jr?1>?Fg-@WH_zUM5wJz!d!+jHzIuS2i zg>A2VvgXo{iS8&p+P1m8U3hG5Uq3Z5kEX~q>S$mczk{wuDlufBA1Kl z+ZD;EGKwE(SPHzF*jxgnl+u=u$e!tZ5}y_?@XT{BhGqR-N=bXSBCelxC!K?BI_H%l z`h@NbfNcMT@b=#*DV!9WZ{80^i@js3me7qcCQE<9r zR=@m}AzxtSZ`oJmyDYK3E0D2N`mLsY?~-$I`yB##M^58HF0;@FQUaS7%bXw5kGl9h ziGKa^T9?PPeOR5I^lw~P>cYgI2T=|31w`jR_=`7uZlU(=OlyRgbyBqYrpwXBbwgsP z!g(NYvmAoeb#@T#wRl{|!YhKi=v+&?<-663wi|tPHwYt~^I#FmHXN_GS z&ej(G)wpjw!u9CEF}}P&<$g=NB}xyrcHOHzQQvf>#kV7=Xa8AOPP=qe>y>Cep=*0OOPvYMA@H6Z&1aYPFbdxv@6zXCf+Y{5EETykKJB~`t2sk4}BuTy7l(?gME5o%Y%s0!a}>SVZx-lgXoRwJ?Lj<^C9ue{-n z@^zS5v`#>mM#H+u^;YlJ&5q8`?6k!Sw@09w8H@jNW$;7Y-D|*^e2SQtIfbpeHbosY zS5wR~Z)ZyZij%QjOPdCD?*xn__|McV+VXQ=4e_$eEq) z5Yw6h?|qkg>M}zx-YDK=V0&a%%5BaL|GnB-$`1A^$#J6pLcQ|g=&vvNuP-D7^w<0& z2`YBW$4lrlr&OPmL2kSEwZnDFZP4djoZBO}~=CdO;H-%(f(`;lVNHF~~)${-V_r0F~07YEX zE@YDC`e65WMp^!EfW?0zK<{YhW{3;`_=Ilk>i>XK{G&VmTYmVT`Qj6i^MUTfoCfUa zLQ{N88c$!H*6F<4jJ|T+=T@X9$9N{P%Vd5|RD- z5fRP9Wu6iDHB1g=BBzba-2wCV%k1$5B!3_r+kf;YkW;XKxVzL(=Cgb6}y0f+(Yl1UPXcai}r66{zZA+tH5*%19Fz#Tw=y&8KBbg3{9 z{AdIxRTx4>eHh<{TOhPc|Gq&h_BhCK#6DRHQtY~cT=%!3*t#8nmGZl35Fm#W3{;W{ zbdzFWnh}JdtPl3NaLBeP<`SrAcOOZaV1j`sxDio|(0u&AF90T>-h||Y5w!bl4upf# z|KpP2gG(OZX|7;<+6(Av_A_Y4*^>u4(TBTfSMiO(K)G4FzuEHR0TK&FvBic$w+|0O ziCKfty^A1s*d&A6mxOWwx>BwiLm5cyF9G^N83{a}H4J!zo`-B1yo6~OooUC0cF7CI z&`l3+L$V^5PJBaAKUIML-@v7v8H~v87AWm9_`XB~E7*T<(guqoZ5az5e_PTLqe_HS>Y?cl&FgktOj{+nQ@6T#L)(PJiu!3B0jf8V6!UrZ)GGsXWt&~x-a zv>OO=rwS__)q|YcX8T` zepnGryXl>c(CHY=e?8I#EwzH|6#&{VFcm_xd(UXUhg>KRddN0O46`oYV)P61cn zh_(kEoeKP#zkMAQ3ix%S9&{RO*Ma|&e~$6%9gLQUjRGBK!pV@=K|Tdk9V?A1$Z2CJ zrHjcK*(4Mw3H%J}YS2i#V&Hlh|F4Hk!jQH@;tV}xD|kh~$>Rgzt1vaUeHjRFQHc4# z!k5a(gI0hYne2g)Wx$LBQcg@TH}aoH8o+fb@r)>Rs}1e)Fz}5G!NYVxhjq`&GR`8v zyOv<~VesOo?di-F1e&Cx8$&mDjvmF}(i0F_wj9d{ns+f8P=SN`{VKVZSr zuA&>U`z#17#pGbrfG4rr6N}N5#8ouKOsyLN z+Phc(?&=9}B5M#B*m0Mj)9(&mqOuieYla8^8M$ue|08tliv zE|LPkk;K2*&j}WEoK4^oBPU0|SA_4A%n-!w}FTF9#e_j7+eG1d?$v z1z7Cz9J=s>W-Z3o_yu^ivx6YX%Vf2|Mj?gr`oIJf=>I0pKoGx3*C+&b0k0d_RWMBzhI8(L z1HevVc488PcXO2yg=_&j2U+PSIVq0`Z;;!^2C>BS1EUARcZDdo{g?Lh04wg_w>9CU z*mf50gX#S5JAmb4tfYSy+2VF^2e$BC9*Ua(lK&^bt=u7lCxWiDnj+Ad4)oaER}b1@ z3?@5@5Dtrh94wC+aVW}DHc&!L=0Enrwy=z^RzO6_x=s^xx z{A|$wb`5Bd&1?L&)C!bhc%XIvfOC`;+#{zIeCf`EMZ({JdVYd)uL#Wx8*T<*qfJF7 zeK24oiB^ozRM37_xDhbk$p*Zy5a_n#BQf_ z3GRQ7Is$|10l1SolP7M z4nV;fyT%`pU5=FR!I<+=;M2X_v}^VO*=0v500&TDt$T0?m;)ms7@^m(L@(sQHQH-1 zq66}GjxvLz7_88A33}pFuw=B6fGy+AXxft`2A`8AFemEZy9S!m>Tt+xSDK=9j27vE zoLc#N{P%%j#7zSu;mds_O~?k!sC_q@+%{0|;Aw%;B*0qq4rm3+f~&Q{|L1Mmb|om% z;``l4rSLmrhIQbqn;(a>DYvGF!_=5_bZw{WS=)fBJFWe6$0XdL{^tXXrEDt^jE+ff|u`U4J?e!%L;iJAWeeWy#_E;=x)@M(0IY?vw)o^4mdCk0H*sv81tSyE6F0sh@PdG>MpYr z1iM4A&9v#xy5*Pw7tkyIYy9TCj9B7RfxgkFrms#b8_vT=OG>m2zB0Oi!j|GQH;#B> zcO&TvJX&i1seA`dPv-^ssu>5djy41FHhkDA5O8|SI1M9Lj2VDU-Q_xTxlf}52IQqV zSpdZSTseelm=4NWy}DqsS$Af7VU&@Zo#g5RW>SU!*v~ZHN-*D%><+|P!mngc$ufGv zo+F$u;ASyRp|}yUe>sg6d~PDU0f5@X59tI6DG8~aL5BbsmCAex0|sZe7Vb;`h0^Lb z`wfbX@LS!1PU*ig%_uEFZCKrGh3+|($o|GpSWDD!Iy^6=ng#OS^dK2EX2vv`zF7q8 zS8n%u5YfN`Y&vc=9pT7yG7v=04lXlE%!_D>lODU5ZRZ_rFrb#bA@nao6L>(49KYxE z62@^b<#?b?;~>+Fw}eiYV2*G7`v9^Ze|P7Qzh46{6uAHqgE-rcBm1Er0lKizt_hS4 zGR9WgH>g!=h&j9=3nP%B%)6Zwv2xoP(@3-7->yg!ZULEOlEt_r%SRq{5*cdA3|e4~uH5X6?`;K2;~5%f8Me6>qMj|vT=}jhXAAKtV1dD-Y%-YtUTGy$8X$e9~=6r zq-1!gW(ao!?SCi2Pa{>LhNpbs>PT_j~{5 z@xz?Poxxz{K%G*G*EfWZ<>Ko)-G%OHMBz<+GPitl?ZWq1@To0ZjH0($KD#u&e;34k z|1adn|5;K^lC8~J3ixHO12tz5gR=G+j%5Bf9XUrb4a(mgo64?2fTh`m?;WyCIY#7U zh3n(0at}sxmBF1^#@-76yxVmr9$gGoJI0bfb9PG&F{7k1gnhBXoKlTJiqn4?7f7f| zJ*l_*#Q)~up_kXV_W!ez+dYF<2UNzRya(jQNB_q^|3Q1aazKOxm5ji-Pu_t-PKT~Y z`~)n)Gaw=W=zy-cCQ?=8IU?-MPPG35PufYPA-$YP*}7@t&zwb){&h*a(S2uHf+57= z(5F9Ys-ob-E`@eVW+#tii*l-c^qLDS;b+<|R9YuEOBacve#My&;-tqaPs_g}eKt}A zRSa&GB$5s5tNPcz^2N=pm_#b!T)u^n`s+2pCX9*;XkZ%($^-G zCUh)PLxw_anKc^vg4V06=r5e9sC$2h)WUkdcMs{_n`T#J(}+H;rbXhJ9i(xS`SIb- zo>p(`I!`)5zc-1+<3QSz+wfOE(x^g9<`w)k6U|he!%)(s(1V&k#qR};WwNUjkQgHQS2c{V;N=7Mn3O%?Y6(c zQYikr!qx3`u-UU(^4me78=c!WM+(+2@V=Hddl-2~$d>KfBnR~JP8NOoWc0U; z2&Prsd4@vo>k!wQ%+&A5`Fz9AXCIB83ZXVl8tJv$k83#Zl1i{^rtkU;Q2G{?yE_a~ZA=l_ zWB@h?Md;ZGEhr6g+`-HYmlJ-$#dL*%Q+IO24Ry^|eWHD&VXuyn+Xc~H_=RQ6M^E+P zzren*`!6V=GBZp`^eXe8U@q_IAcSO-%Ghf1(NHR`B6Ij^#L=>XNTU_e+s*;&g;(ad z*Uaa!%lZD&@}d$2r}_5-wNxqwk*eC^7z-qwP?#tu!#fn zSFxMTH{(`cd75!}yomE*~FGi)_FOB~DZfi(cNeV)*kv_|1xe z_CY8C=kjXLHve1{cPf^__o2Q`Row>q7a1|L-jfmZE&9Ge?B~?AwnZn$*yqfjeV~OP z>#9InPCL=3rnAWhX%ZIe-FYxe5#fn#y0Y&luWwz)G-ofj54=#Ia^#HAR9+jNO}T=v z{b+R~Xkuf=fVOI0W{$qedDpUoSeZw!nqHSQ_%rQef3h{m2NbrltjHUx1Z;&@|5rID z5b*UwH{(M*0&k_Ub=vSzG8+9uixXWIdS&Z=G@XB+=bQ2ZEBDuga&d=mk=)h{PYX1} zv8+{t4;f*3#2!M7rQAGqavQPg@u=(db2)1sNk-#?V?AeQu$9_fRE^aLKuYiux&WcB z8dun?!a19Yam+u_TwlNr`HfHWQ2gZm*XG;H-SM7A!yRs)lIRs3)ZwetXK2dzVf-#% zFLZkqaYJ~#etBS!a364==7RYtI5#0j|3X?~@0cb_q2FV-?{U`G#ICwN@>iO3MmY_V zP!a2O0@E%w@LI!NoJ&E{i*KJ#=VrYHvf6f$UouwYoK)bc13dz|IiX?!-Y%^d?%a)k z^%>N2xt@MB^H=eCidp730+;i)&LpUCRet=Wy+K)3iOM_tlNgB{-z2&~aQ9U4c@ny< z`_!o!38_GnIi&sa%wpH~s?bSYBodSaDtlNkgs&06`#Uq}OhEx!z7U!W9A-j6A*Ux! zZ|WNc$>@kG)mW$Qeso=*2&_&-M6U|UYXhuI4i#lh#=T_kO(xYJGbZWhv~c$-5KhB= zYwuRX;;n*A|4c%GsxTpp*Dmw|aG>RcY9Vt_)i0yiolM>N(atI-Q9GaO5meaf2sC?FD=j0@)m=(0U&-u!3?#m za*x45>3W={S-5JM6_b}SY5nllx5h2j&5ZOrr!qbbJ3^br7y+7@}KJU0;*%xS}dL zE79y4Yx+}k-w*5|-;Cx-g)$e{l|S8jJp_?0g|S-#DNTIyVhJBZ;Y|#SH`f@XbPmd2 z{)R*1A!n>nO_u(?X;yp{^KaRz6^BS^#vLmutYNw&Cz(LhZVrOXoXpO1&*)xk z%W+cXhc__RdaBJJzZi<$zBVlYTSf>`lbw+kHD9*D@kFS1)$cCib2j4NlKM>bqnC$X zs>?-&+9J?DsC4tophva8kTgp6k;eeK!*~KLN zY-L|7El2U)uu>w~$o!9tbeeAyuMOh7=eA4Htj3N^Ri<{)vJdy^akypXYI)bQ=fr!M zm8Dyk$SgmQA^e3KInx0Y0wHrCtKTbtE-kaTU?K8);r7k-iON@9O!6((yd?5lS=V)i ze=%_{3k&yTv0}W6-fylrVOvVgQabKz+p?i{oe+>PC4Z&7DZ73_D@P6P^6JW}ipTZn z-?yU$e4d=O5y$Kkdf3g9ePgyBkWejAQ1%h)iRBv8&?A;b`RaK%fUmz&RW(5$7mb<4 zwu21=x9>S@m9KsDv*^}g?T2BJ^?2}*0 zc2MsqbGREk^!*y7Y&0-CxIJ3lB{w9mrjq&M#FroUHtPaGd}Ix5X1$W@02_;vQaC|b z_k>^UG#@Oi@ItI!uhV*>@XL8RQp>^s=`91HKRJu%;Dpwzzf=!^^*NW162!{OUgNCV z1F7MWSF#XVSgXZP;9<;ITCCO`a%Wg>75PFVbTe2++o*!}( zJ_n={l~BMTtzD)VoARqCrnvhlW_{-Fo77tzSGyW2ulut0V@wV!3oHwIGkHV*3@k{? zK=D{rbdjlSjo4-7SL>e#*I1cMmNlEej@(mnNB$PKHMigfi_0Je$W3b;f^(TqUoy>yPq{^_cPH)0_v?48Bru5H`mo zQxC`6xUtowjY{u!kp_V_5qg#xucLdpB)tJ$2lP-q?r8bCHNu1`88ga}j|XHciaE?3 zZAKmLzsg*J;N*2SeE907=hw!_!Snvu2rtKN`03qE&hLICnJ4_I%jH#e+kQ8%Yi^vy zd^l7UduMvp+LpN3Rudi>s9)6J?q}=uOehQ;;6hJ%QFW!&Zfi(O_qF)PFJGqLE+&tW zlA85oStxRZ=7q^1dsrpzp=0rhCs~z=Cq>_WJXQom@$8e8IW0*Our^$AWF5b3-TAXc zwSiu|g&S?*icwZ=wdQ!@GUe(F_xnwJ4PKsBXr}xj6^=1FCQL+HF z3)VK{;{iifNW0#1^3*U*dxkA6ZmW?nO}`>@%ZdNB`WTe%9{&4A8p9h2RH*yn71`tV z3{+#|GOq0urzikXIyurYR6*T!Hp>RLEbE}Jn-))F7DzJ*b2?uVaczHYk8Tm{J*lzK z_N|^{{l=6mbsk0EQ3m;#%0WWHmQS& zevZ^^KFdf^w3m)i9&kLy#|(-{+HON86+Zvfa-R~u51i(-oF2oe%?E4mL6p!fCekx#^E4@M|N_TV8F#?zgMMvi{0$ zIp+=~0)e4)nZOKEfVpm<-%NEmn#25_LZR3WFJWVLa%)2TlYjzZt_$0~IclI`Oa*!$ zLyPa$A5q+i{GF{^BY=_hVX5krlakMiG5RY)RzUPx;a079>OvP_xj^P%)o=oe!9L*& zGR&l-gP1CAwEZ_n;&MtFU)GbT#SJl2hu&^N*u->?%RFaLs`9i5&6PFf8oC+ztX7{M=W7fL@|31o*(X$Vv94&R7m1l^Z(6qEGw z^pF~xvzMCO158MBmP6Y@`b_RPmRH}FE(AO?bS7537o8l=T{5<@d ztZas3Se8*?yQ7p$V!VP&S>dVclT6-?ZHDpeZzXl3`;RG%Xz?+X2BD-eNX!g8~hr~ z8;+hgwzt=|qkb{uvBl+5+NQ!{XFzUc#s6NCQG=hC?b#2O>CAtsPCaclIOB97Ts5*a zul9@j)n3j+4%o3u%iz+G`iix7Mp#N?XtYGoRS;w2t3e=ft9Z5zqP${vyPMCZlb|U(EynvYk?H~0WS%~ zfIPxR@#Mt?b&_QW?rB_#f|DfznF^EKz2;6 zDZ_Gn&HZr*&kH&x^khf*?L1x zdmiI=npD}jW7R48VNamA0T+hDu8NCE}_7I zZ~`9WK^z9LPWSg^n~EEXu#}ykGg_dQJ#g;V)l${2PhE#j$Rn*{wP~X0@?kogFgujU z1KQECe*rwLpK-bv2hxq4#~D~!3Jkc;RlDGe`UTo#LtY1ZvNpF3Za`F!jgP6iA>AQ$ zioopQ1C>}b@pEu*Q}L^EqwPot!(zC1Bm6%eCpp`OW6tfT+afmw~tG8LG5v z-)X1&!BrL8i4tw#G#{Fj8&2dHL%J|eqaDoSPD?#xgiX0}5?EtHiF>Iu?DyH7LBwDk z@>}5TS6w-;1Fxq~{_1sQ^bYShx}-;Mkpq9E$Ktna3jI>aKT(m`zedAjFwz>S*&Q5o zo(5CM6O&yYIr-XRs!a3^h%}=Ia|zS8;Z|H{GL%Hl_2SlWXpQakf6kpR-2KmUO^bhpwV>4_{cA>lm2qwzv%t4;Fnc$EJ6-_E$&ok zXEd5E`80KpqNazNEM{u^0sdmUcc*>6 zDx|dla>8Lv(?zEJZ7tRJ;)I+GaHSOI_+fpl#?%HSWEMDD8f2ZL-49KYF67f%M~8})-0)>$%pt@*9%qBQhhpe zd%x0Lr-tDYY}PSu{h6mwH$gJ%NFe-Cf6zlE0cGi%{gKO_8!Ooe282->zA& zXmn2Uj2fWWH8HLd|73k};(jx>!ovOIhv~)X$qU)aj#g5?Yk7`FOqn?lGoyY%Lr&3h znY(DV*_v52eWFISJ#V;SeND>UTF<@UewST~EY?NLP=tl{PzC1FDo-->wcBEUK}P)m z@6k%06E(b<)J`ev+QkVx@svd+^u%H%$vXaJAOTYrj~UD zefx9l;~HLha%Z(p8?0=Ny3_SGy7G$Hu|l!RGvbpd|Fh%jtsXZZax|%$LFBXsQH_7H zdd{!rEGEMRp%CSqWLO)#uPgFt!7rOJ+xneOE-Gt003W{lNHR_0$lG<}(B(1=`Y?7y zZ{5f(L9R`fw0DU;0H6-zYP&gUoPBD_1(=+ZyXpOFI&s(F(jq#8BoNdCW$!z8~u6uel&vi{J-_L>nw%2>P7+xVz>G&My0CNgyGCHV$_NceebKQ=h zS)bIz|1rv-ERyf(t2*kvE4l`~m{7#~KAOFIdTCU*Fa`dmJp6FjI^sE=wJ9WfPe+{0tU@IahG3z$5 z+@&{V!-V&R>DBbODN0J)G+p}8zlMfAP3G+^<4QJ?lp6o*(5amJ9?48w`W`L*4}*D$ zi{a(P5jCFyt_{|wLgo_6~-kRMH^+0V*CR`JYrw`_wrLh`NgzM*xh?p}grmN#S78#SpP zM#WRM%Ea+7!oQeG;N)l^PDWv#M!xgXH+$G?pQaA&==<91=~x(D(L{?a(;>_Ip;cvx zV%{2(M76XFgMrsdbk9~MJ(RIgb(XOtGh~d zhB7AMKhilPbI5mu%2J8KHFXyt71du1 z$sWBF@dsu9q|?LGEG~Xr;SFy@!$@pCvI_nIBivWy1zqR!1SOu1)>;OAZ&o9S?x2wH zYCtC3t7@=x{W4V?VkwKNQ+L~){Fm!QDCLji9`Z zN5wHs)49^uR=KXiVt&)6=5rz+PQII6eRTr@^|fl-wOkt7sl^L)Lf;lm4Qg~x3m_7% zj5HncqR&axl8d@i$0&Fg4{Vw1%X8&KO#JM+nGu#0laMnu=|G&Abm_OR*z7-JdF!;c zKuyr_*4c%7^nWp>xbJ(9>6Db${-_Q6bG)bsI($DC09puF_@L2xyOPN9;QyjcCc(S_ z3&V5M+=`d1kEb<%^vo~*s}h}S*u-8qKL+^DicwY@9xIr-nXXqfAKJX#Yi>E%V|!w3(epg0=1gne^gQ4!1{q zt!|U<>gGBnIqsr0COONryiS%w8#c8i>(Jyzq>#ULCC#&T^@TLdlP=1LJ1HCOz!q+eRf)VQ)LX5v|Fw)^Sz@pW=a%;IpNwYi8$ z0w;<<8=);RFCa%(+b|a>v9l9yR>@S8@1>CojE-5op{JX{PgCgp*PQX>5|>b)+Fx%8 z3&TjFB3hUGVZA_x+^Pw|}Fe=QBt67%x&HM!4+N|5QwkSw-bX z+v=9|O4S+7?U~|QHa^H$%i7^cwX)O;TKoNhho-Mi5!fu2?H1SLnxDoYqEpmHpT=g! zR_=DH3O+b?r(Uuv@MMbHJAfv9^ntPK1yms*+rJt~oU~x^V*S>m?!r=(AdQk4I%hNP z{x?q-D_B>36Ysr}N!-=Wh6~}=FO((c*43uA)7eo+SBVJxDVhyE>RYstUd8Ll$tgCJ zimKJXmZ{D#6;;+gSaDzvb6lE_`c0%`{f-PaGeUqAKM%Z=rsubn_1 zh+1-KD#WXf-zApNS9rXoECxz9L}z_&nZKtUCt?x;0qCyEnswlCX(sMS6qkcglWvrc zS%{1YNGppAIWT7s!^58RfRZPs&tdi+m&4=N$z?X3we;#N50Tl4{L?nfzIY?xT3{oR z{C(&z6%f~u+gUP*%pMvPnWe+O+QMH{&eS@#RLBKEjyLcXPDe6rt=hbCwXiZKwY^iU z2Ws(0KSFIvMHTz4DW;u9L(Yn-6#)?iFI;A0%lMVayW10O$8y)LJ^<#@i77krHs~o3 z9zPFK*_qEl=p{T55a-}jFUa!U!8ka*3TlgYu1}aV}S-7}VaBmfP_M*!8 zs(V~phD;VQ6kELCXK0l>0fLhZSjfKoJjv%M+?my|gQ(ihG zyQ35DGO_l&MIPy^jdf#$4YDLn!)0}>rV-A$!&?8Sd>wgybSWhfB(6mWf315iyWSW># za!%B`Eu*`xyhr3mz~}LDIq`kzx=y8*P4b#!@FID2ESq95ZG}3jKYgP7t*e^iWi{=4 zpUdADIFChME>Yz|9)IWz@j3ei8tVLPKp`d3bTsP{RqO|}HoD^j9z{4}{~$=H-hnxe zV&TP+Fhz~{laj0X=xO5CI$6`&MJqMU;$nUfFJ^>qIyLY#?uYUL@F$fh_yrWy*FI`bci&Aw2p1@L1 z=bya`uqoMwM;eA^-pumc`l>%wWTH9hJQ25hWh(M47dBU#o&Cfm&UA@nuO0fM`ruep zAkQ!=(u<;~^JvJcy->fU&O=|YXAF0H>+yzKXKi@mQ>6IZrjb3-8J1Qb-sXQFTXbA% zJ5{wQUX$FlQCIqqo_4>*CBayCXY2id8J6ACPqg=9=^005e|g!F4d={}RbFb3h6hHR z$@R|44Od+OlY=j={aSdb;X<@d;;5fpr4rsI z4O>oz&!nKrmdsQcUD`ge5AynZ})1^HslMC`~m-#{oj}$8z7xYlWw~o6V4CXwTf9S7LbUYkJB@MXLKU zsZVaUD*{+WVu?1km@H}PH6%jnZhA8`U-koII>2oeb=+FQt{Ecv#>Ycgo5+nM=YQ)_ zJ338Zo0xd-!6sI}oMZG)5k`)?}Jys5$yqMa!HkEs63DpLRTBGAKg5JtQ62zSZW&zO-7B z0QJLexQ$@(BZSek;YQ54A0%;<&>m)PL|q zW@L|>`_>$H+0m44iBnpZBF_k*cAwpk!LFro__0(I&IAhfdllMJ1MUsD(PU?k(&vNh zwH^9&XJIw*%}b*qs}rek-(P7UQp<*% zDG-F1d*xNQQbYfV!BmtCy11;bZ^+c?&a4I;x1Nv@4eGPBL>>W=>La~@F$4xZsMyg> zlzUk)&hL*32JlYm7C2wK(UbC)`h3v_Wj$`L8}>n}Iws|K!9Q!iwgUSWr8`BwTw4NJ zcA^)i@L6Wd?rN&W`VAvD?r7J?p%=zBzY@Ih405~m<0&i~o)SKTkAP0gl$CSaj67%o z6+qbAa;!QP@)eL1KJgu>z$nHdak$D+MIRHDgB!t#C%GV48%`s=xE^u-GSTsr1o9vn zMc-cqD1Nh4s}}v(r|ktvDWL)Rr$F_o^)o*qJnK>lC%M~h^%K-oW%rd*oZoBJ2xKyj zfY4lGQPfOU(QC}v>VdjDvRVD*^N!IKscS^GMCj$__A|LD?&oh_d!#F9RE`Q8!oi|9 z)N~Z#rkiGA=8wy2f1K8I67%#6EH(Sxq~PRTTUw-{Wsr<5^wm<+EMBg{g*L(Lv#Zc9 zIqS_f4&7&<(CeBVhc!&as#U#T`iVCM$+*2_unvY2LJK`r&p-B>5s0E+Rvg{U!AlMG z-HF)Tv+XoS&W^5<>s6;FqoCm$XMOebY6M&hJx4@7Agjt<=^mzaE>qj2qEBww;}h*i zUsgv31?)(bobjq@3i&}gc1GDNMNW%*)t(kB@^ zZ&|570cExF5D&QFJ5aV`uW3dU;y^FO@I&@yUCWSjX{BhYdJx1$m68or-W@0UBYm3m zq4dpR;0ILXGJ`Y?cki@i};l8mv{0XLJCxc@2{KgkT`%k?0=8 zX*zDI-koT2jSYnfavb%PDQL-l95?kmq_1uV>d5&fU7j;C?WC;uegy+xP9NC;u)dp& zKFIL|3$qz)$h+fm1UlC76KBqT$DdZUg6*Sw4?3o*o&tj{<3U`NFo&f|`%Td^8K<{F zJTY|&uMDD`Y&Nk-0;U|6ki8~h8T)PlE{o~n5_?Wou0?D(MC2~$YAiRPTO>7H_NhTi0{9} zR$Ks4EwLtDhs880>Ej~E zVt3!_BS=omfZ#qGb~$t3t=p5=bQnX}b33c|f9K2h-eAA+;1*UDMJpBl5cU1tw*;*j zMaJIF-zL4MSa;m=UDY{WS9?NZZyQ*nHyt6C(#C(YmdK@p^j3Eq4gIv#1eGJ4I7jW< zfLvm%GgaO=C%X8}6>^TTx0HTmKBQDbn2=_*mpkRc{024?(-Q&!0<=qbtv)uC3{kK zS`RZd6W{`_@2D9er*ne2F35pw?}7!>mKXd95x_(A-@zF86q7e7Tvdjv)edfG-Y{&(E1{9DnOki}I~ ze%MwV_Jp0y%zC)v9~ z7c=vGlOVMrjrY6w18lFYkCT#G zyCe6J&2EAYMijFqeidYMNRvu9EiimipgUDX#MLkKMq?qPqz^P@7C z5Vx}a$%+2o{=Nv{=7;N4cl+Zz>7N2Up;$L|8}i_9Jvw3>(Smfg(%rz?L-^|JtUWAR z+;Ua?+uXdkHj%Tj6($|p<+A=>iUIw3?+z*tUHZFTPp|HGqug|sHLewj^dmS4rUL-G zQlz712rM(5-NmcI!TJAV?akw%{Qm!8rBX>!mQqZmP(o#^FwhwNL{G4_3CEMu6NtNVC=f8XW)-uLJ6_rDBl4ZA6%_s(V{v0*wn$Qj-GRXex+}hvrouuOO@tf!rQsY> zwQo)5G+fHuLKi+Keq87U9^EDikZLE$1@U^0$ z)L@bKqV=d4bxL;8!JvU~l)4m@Iwy2jJ$+^XI3+(uhFHs*qU#P6cF%GNi(=kSU0$AB z(eiNIU#Vi1$20vd`3-l-r)l1a+MvnIRre0|Jw2|^-rkImF;pNys$FJ`raHYiO(OS;xKBKH;`D{Rw@h*rx z-X1#>VywtBE_=Mu-`S~zF2N{g@s$0aayOQr;@$e0pBhFSD6BJAMbc|!5wdR`92^$v zD(j0pNu_Vbbn@yBey}S0Jbh;0EjKx+yz+q|=AEc`Mf>+07ki8_!>y)VEom3q{*G6Y z9BOF!i_GK6ihUOp>RFgh7oVr|Isx**&DEWDo0h`qM<;tMyuQ63WCc$6yLmHVfbV7& zL1XJRIBr|vos{5bc;&RZvBL`7+>x25lewe&y$-Qij-YySmx}WD8r>2*<*4sJwtMly5^S3V+-a)?q#<#Y^sMT24@0UT&EW#&C-3f$DH^vU21zLJl}O=S=ZD2j3Kr> z=&_prFUk+61p8mUa@hThYy+xqSt$5Pb#lP&kR3ubX~5g`H{Tut6X1(O|Q{?VXN;#eUvP7&BIz4QHA%vNh5y} z&#ukv^biVS$$G0uqu#q@$c=qbkXD2*0VNu~Tde$j&Uc8qD-v2C9%KhEN~;L?ki&)J zHET*P)TG!&6vpO;I#o0s1gCHo_)o0MmRcD2Q(`RJ*>|DxD=hi_0D&D1_Q!yXRYy>~5PfA^%8DA8Nla4#vFL@-) z(0$hYA=UmGRtwEFbYnDxo5E**|HYi*W-l?fu*airCB4-=-;OMhXj*syfBpKmXsuowvY4NQo;6yGhBz1P~h(%FetbL;; zs(R|_&Ffz2(Im<}(F?Rs!uMmww55*x>Mg81vUcn+e9tU@!+}kzJmhQmh1z_rvhx%k zVyG__-kf}oD75!nh>copWA{p*8Z?-dgDOnj!WOMc3mFBOGoGnEc-rh=jwjtadgm`H zH$1g8^e6gF#7qwneF0Cq8qKR2XDckwZ8V03hSbbrF{jH5m>lu_}&E=TJgpSF`|Ck~Hd8_gcIo>*|Suu7!syh=kO}s8{~y z0a$ci1TyB(J;r(*SLN*PJGXwlH%IN~dS!}whWRY9U&DFohGQSM+=gb9jKSnSudIUC>~Ztcv<7opu^$;KxwdP@WkyEc zi?rY5W?!U9_6BzL!-8)7xK7JCDNw}$ikzksdCh0br~S`aQQ`ERBUtII5bgOV`C>9d zQoP0-WKak8Ubl*Jvi0>2mynQOE+$e>u?x2Q)Kq`l%!NLu1&npo+6D zLoIxg9U*CA%`{o>a~@@)ciTXRi-vi%ess3xJWe$kYV?tA7;upt(K>svS+1hFVBLvL z0ib1*Tz7hxI_-WuAc7sRx5ZIpVy}LDFnIiD#B~U~o3mjIXeo9D>rdieLy0g9wBl;Y zzZ<8n{VowI7&fyt(6OFSX>G5ee{xatNiYT|5rC|DQiwz`tabaoJDhV+kxiW^obPc1 zn7QG)gDnovkoJLE145^mS)RMR;n{bB7L+-04hMnD-rdp(tmif_x$RG5HI z(_kFrV)32fi#lv#Tijeero4EiOQ-9B=Mb>)PAP#m_Dsj?Ifqdzk&jRdN)n2{*=*3r z?{Tp2zXc5ezUlr-Ia!9nkA9pf)>>HQatp+`OmG9OKhN+m@FVPfFUCzPbAE z23F0tsO!JI1k|MFJl}`E_3TY14iXObVG#PgDwm&s2$|(-^RPX+RQ#nP=IoKf>+U1v z=pFO-%}-D#;}`wc>eC$?e+&=TO#EJYo3as9q2bOS zD+aspbG*Kq3fuvy>b2dy=s|0BF-pxScfN0E##OFmg{7#`f!lr$*~Km!V;jjGDtwsq zCaB`vdcZSZ$;_*wEB}Qh^}l8z{;R*YTL}2`zVg0+aqZK0ch(A?B&;@C9B^K9D9f3X z6nI~zBAcSUz8V5>O`iR{t66uwm8su|P|X!(puQi2GmL)wq~s|7rm*|E{-V2MC=BJR z(0XAhFWjsLRmUY$ z$blwGHI{K|t3|3jIpp}HCO6Rk%VuT*p4zg_xyAxsSBx32NF zfCbk5^Jdv04&ZirzFgjyS|Ku;fkqPl6q8u$uB&>2njk%UuMv4>b`;IS%k(n=UJ}4u zr4oR=wug{YKsjW42tHY%TifYOx%%)Jj&&Bd6Kao3#4`PX7Om9-F)m5ycU&UK{Lklw zPnLg2EbGXkCSte$ni^*~lO+Qi$v#JXW4!_CfmNo_PlCt|W+mq>-SuZr5KL*zli=;Y z>e0JwEYd>Kc~b#1)?MB`Gow!5{y?K=A%AZcCx6ST$U2kzrnmlvdEmhSvn zYj*T)bK*wemqJ$3%{m&x%gM;VV@BI(N~1CUu#{~w37xm{H2Ct#H%o`|A4I{M(~OSN z;iF3$OKJH0Ho6NVX0#+sc9*vuzO%4)qTkN*q}@?=P=r~AYVys7t{e^@33U(E^gv&^ z8yI0N+M%kICZeYn{>TeAm^)MaY#&_$)YC?&@B;OtGY!z`8Jt}ixbtuwqwX)qjp^4w z&VnjIP4*)B8PXADcgjy9L){$crGZzf8o|p|DXgaee>eRT%h1?r?M)Iks%JsJCaHfp zQ1mk-Xu0X1O7Q3fbeLxPdjbNmok;j>f$dOv$jeas)^&g*K$!wR)Z@yzYZM7R@dln} zQZpIQ%^Y+ivGNmbh{uRCPFqs{Hhvuh<1;vWl{$qsbUY4p`l{|HoMpIxPkOexId=xi zC#V3C>!HqrPP0K5x{18%_>OZf+>xq1-;54!95jyk_`*BAesxnMd&B7RnC@F&A)4a>F5p1pyDz+(iczNPhc1M!CnWhpvcmnx@myi#x$wNYv=EB-VIgokUxx@Bk=_ z7)eft4{d@d94PsoITNdp>zDe#)|GGfxo1h*?iPsZcO3egdoyzw<|6hFI%H3Ki(Fh7 zIcY&r`a}zF_P||&f3p+op|NiaTkrsI65yvPBrQ+|j=}-50TAN^pfpo_rTX>NHk@h? ze82{ImUbG>8&iJZ@@4pBaNV~M%UI8}v3(c)DvuT9jri+c8&qh4tbN-_s~5H?ORM&S zRo7U=`v+yyw#nlSytgXkR?EF@Ju^h^r_{>S%7DOe9_UWTq6MGHY~Vr(U+pev@>{z~ zI_*O*p<;BBCJ+!M(3|ueBR@EU0o*CC*Hd1c=a)I6eP*1I-R4`v)PUOU$q>93uq@q5 zSwIw8vv(1|mNuHAzlA&Hm}6;CqX!@K$rQzl)e85$bx~gQ(~HxvcQxtJFH-wBbmQ!H zuA?1>wEDTZRn@(NH3N#+)Q3{isiAn+AAy%o%IjtA+1zGsNCzn+upgS6NY&2(0%j=j z2m0rU0L^%n#=kw$z_7P?2X3{}TDLr6N!vlw0D$>qEcl~K5GRHY{%; z5w6Fj)g0a1E-Ovskpx!I|pLGIakU8wh+tSu|;DxT5%V!vaD{KHQ!BriQLmJ)|FBky=ieO za>!g4&L1;!b#nz}n~n0Q0^iXGFy3fqx#78s#;5(0OE;d4w$2J%h83$U;HFKZ1a{;W z<~A{=n#dBeLj#7VeBVwB_z8M1*ec92A=)MfeT`z6yJ%4w{9q!~D!}l}DaVW7dG*xY z+xj6}pk^+}`a=wvEv_$hIy7JnLj-TQ_+t zeJhKZTig#iGZ1 z(s=k@7jCcuiXJolXV_O{Y2ZZPy^8O|2{AY z(9*WKdyB{HC(pk(qW1z@J<{hYAldJ@DY`yX={ywaHbLiP=H9QR!gi(<_ zH%68hbK^f>J%&-1lGi_7#hz|iY3k{o0f`pg%H(;rBIRb+@lKL%fctTio3(a#?`u9E z^;y8%k&IV9+B5I)ZoFgt#~ka^(F4z+-#7l_cB%o>>cRG3FpqBLtSP=4%$W^$&a%EV zr@Uf%y6>kB=Xvu7BX-8RL65CJxJOst3x+i;qE~L}M9OlmzYvo^xR$l$`F0^z1sP!T zd{YVDV#FC_4f8hCZUSU@?-~jQ>3Zn)Ma=iYi9mgVnZ~T|TGP)Hq&2;pEzX2f3|IIR zO(C~{_nnK*{+l_XO=HsMYl-$jU(Xx4X;^Xc_#M&M2s`XH>L0!c*55VAAHg^a-o?hr zgknTt#{75hv;Voh%^4&79q=c}?yF`T+>#Dsmc>l1hzYTScczAzq)03tSp=##RZ0BZ zH0w4XA#quxp`xXf%x2Ms@=`}`hsM20*H0#r4LKTdC??mUMEC~J78_#~a%=B2l$;ymC^5;z(<8YvR_1SUE8`zD{1Yu}2Wb48vaA8L|{+TVJa2wFlSaJh= zs1Fdl#oZA$Y9Iqf()}oE91+EYK(PIzhJ8aoB#iOuR0EGtwBATH1_aW6fg;J1qr9wB6llXrla6SR-8{wuw|N^+`8V6dV=X0ixg*gV zDD|ZApCpFn2(;Y#$p&>~r7cWlA8JVlk77n8^6EDx9x{KTa+&sijkf+qv#ZJo%PP)_ z89Bh{$h`U(bqJsa!Z#>sjEflMl{meRC|VQ80^yXmRy%>8ac8~O{3PB)^-90)Db{i$ zi%+A{><=~TN?$D8JhHQIHIDZnW3XP4_?QNTVl-0Rp7vjSI4Gl@QxMsmT;QX$2=5)F zrZ22~vn8A;pveXo<(P~if}gd{>uA^7Ny*S9mg!V&AyuJ|KUn!WZ(P?-fRy>(fKy2n0r zAt6L*lgQ%^jIZG?=f4~UJsH3?R^CIHe(P;aTAEb@c(U%D9dnP1IsdU9K||_ zVsr)8GoYDUG#tG9X)^hxr;flBiHetIGUV!0;7JfIst#wED`HnA+=1ZjX~x3hA&KRx z;=wBzhWUUsdoQd2F1f#DvxeSJtmc23lnjCVddj?+S&fj>*y%xJ$x?X2H~bl3 zyWbse4Ji1|7Qmh6`@<q1S*-*=OemIHR&Y zVT)y}ZxTT4%}RH@FogVHu=7e%hn z#4kXE#7XFYWT}~mA;13Z$Nzhd^V{d@9GtODy?Bsf`~oF+zh^-n957aTz4#E`@(y>D z5jBqCXROr6pm+$Pb{}nx1TTCeoTi=7nbrzPE*o##M`V`Qm4PRntAzwuJhDTy zm_QPH_%0`qgkCq6BqE@+is3=N0sL2G41Dr4WK9gk7NW;gA!qglyXATZki~f2wd@PJm<#NoJRL zAS#Ghnst(0=HE8IY7~N7aG)A`dMmc0s7C=N<`yxpq{`};_mz%7b^!)H`%3jgj`#r0 zi7DhQAc92LroaQhOsMPPS}>&Edr1`l!cY#^W!0B4goX6n*TKYP!Qwa1u;@PXMv? zc47u^`n#eFU`YH>%@LM_h`jV|9~G@EXjzBD>;8aZUr6{L68YwAvRw*11yz*&c|E&sOW=zW?2Vt%VN2q8G-M>?)U^z;MopYwD||ZkN>lQE1#zBUd9gf(@=~jB*4cf zYlmRy1KS^@>0Q871NC=tcy@DsGaP&oyl8!MiGN-o1pCLOto12?O`A}iVSfpfnei&f0kqR)Ji!58R0&?zkr4=(pY$fFS|T}^un%hg z%FC{$pcrdFY6?b6*n0%0=e?YP*C5F`FoWX zQaBD08uhkrcn*3(dSi{NVP66}rxuY>4zVtTSD`i~Vl^j#jbO;(r64elqSm8!08c7@ z77+>7nxQ-@Z@TIxa=ONW0~##(%VGXX^-b`y81uNd0BS`?1)z9d+3TY6WvdMO$l(FH z?3xr1Q{q_5l4{~zh@#$GW0;V^;(h)ZV#I#-ZNlzsV0B`5L8HNgAod1Kfeg5Zm$jzL zoH2^y?YAH8TxA%Jwy_QjY%TNWV43gVkKKzG0JLFU|E%)lqw|g{4IsH9*pKO^4NPl) z92>x2Xt@V`e?wfFpl^Wby==X;KY=;1U?~6PP)n~uFz!6u>X2g*TPG}!hVJLU}b^5Jy-N4OEXyvEShlYB)U48!hI5k@Vc z3;)^jz*KS@RH0XuX3z||K%k|7_9bK755a-DywGSbFD#SaurN?8iCAA@Nt}BXB=qp||t+?N7Ozhm`A$ zt0(TGIEH31-pu{Ekva3*?HNOL>#xNjdGt;Um23jNPSE4^#HPKVp(3a2gc1 zSN|XVct|w(K(#_K=0_PmWU2m_q0V)>%BJ=gG#(bVD3Y3$4AL?3Uy}S)g`bnE{8FjS zWX-n(zJ47Ca_GU4R{QYSsZBjAJl=Ovelh05G_$^p=eoYcV$3<`6@CQo4WO!^yC<)K zik-Xts5s*VXSjmM6gcR5@$1Ib7dH<&_j(X3;9rTyUa5JDo(yCX7Znb31?pr2`?m}B z#2Q>{Jom8pmwP<1Tvf0vx7DOrzB(*UOG^wP|DL$qQz@9$oMPLj9PMzi)dP+Hk-{&! zk?l8ewF$o(1ZAdjK8a%f9%`0d_sOW>F4%?PbbmUJbo5Vc+~HfSj{+)ny#)+xZ%y^Z zOigO;n`M-oO04C>dke_Lm$Qf%|HH$Bs{Ec<&qpfL@B^@d>B$Q+t>;)r8Q!jCR760k zs+dMcP8h{``JU#eM2*G@A3?-jL(e0A@J6w+PvFu#fk#HmbR(w*>N>GuaQ#MT*5O(_ zj%hto_|7cB1#D_7RNQ1k4Qlre)72OAz^#$r1cW7aUAGn!M7ER zYmXF|6lRT`Nd&1s&)ctSNe^X>^{?HI6m@sFdddGzoX~F{K(`{>RAWy&_ySP1A#Uxl zD2u-ueJvF4JLB2mDrEgtovFe)*UZbfPvE;sod0&^bcty5vSBIFn&cmM4;x_4cYWvv zf_}OLxwvfP?EiCrng36&r?>S{M$G4lmL2_JU&pkXSnS8oB zU-s;8=P4}imL1s@^*gdW6UV_$Lh{uBk4#9mrmT~N1_J)g+%6h%s=x$< zqIEy{tl!(9L&6eKEEkH$i(x{?@$t^nOSV_0VRxwc5|IdZ8LD-jYhj?qKf|ZIl{>s| zX7f9fehN=XqCM-bup$zXt?yBL7qV+m7K6L-s?=s9>M$@0)!V)Gb8Ua{-t5!+5(CU< zoO90D%UW=pko@LfvPVjNp=s7)xf$YKH}N-c4{ZV{ZV{Vr6Vx0^a?O6E44$$s;&|D8 zPjLN=(?PAH-`|a&_%MM;%JkbRQzDtjyPX}fFsRSC0-q}njl4^9EXtTK@v7fHRp#FA zvt3P+lnNnm38 zOUs_q(e^H1D7mT8YQ=>>S^k9YVcn)Tx*T4j5B}p#6@nVfK`$>C9i0z3QgnHtclHER zweW1feHoVE@F|8pv>_ zf9P0~zSW@Ktv}JTChfSc^+x)_?dE+>x>rthm9X;n9ar@PsV4TR;p3RMcS6TPZ`Bw? z8tL*5NyksPIw@BSID_hZr7})8?TIsCV^y}foBqOmyZUZoBLD2$yS_=+SoYo7 zzlfIZcwB!rId=STyEL%$UK!=K|Et5=f7Q;_<_3(vdgtLAfW&M746!LYnC{eoC4ma9 zz}}+Y$9vr?v_Uy!IED|EGh9w-uZ7%uk7XZ|`lIUc6C|iOK?^N+ds;oS#wPP0eEp?4 z`X-O1GbSQeHNZ1clC2b(+OIhU`AlHeq0m8wJvyU}l?T3sUF;K}#c1NYS$o%wts?C_ z{r!j2k_6sDzB4AK4N_`^1+ltE9c>n8=f{ePfC0W!&V{U^ z>CN;-F`$}&9bIjh^oL|T@5~O9&6~Yna+IQQG|1v3vW<=*#F_a*OOWV5n=pg@H7pzT z4xc$)M(v3lBsI%M?pwd_x)fo&LHxNm*-*e_Vwq9LJ#lZnUDy@bcD|xLFE>_s4?L(5 zX=%y+6{)G>2=(1K&z${mZ|R;@{odDwy%Ke8{O%E*oxA1DWZ=$TG}7)!rHp>6-+A{|+c&ZU&nZfRe4>(a6au4Rk3B?9SmJfNIg zHsYZnw6W%Wcnmw!!X<0QRrGY1CBCSz(lQnr8r*PdI9jd_YSbd9U|#xqB3F{PG;M9I zk9CWsJ|->HR{k+(bp~I@v|5%JiF0e7S_0ULQyWOnId2ErAzg+~`ry#q{Q>`sQ4gNinirE?m58M|DCrb;obD)G6w?k->u^v(TOYI)685RAZ26kABecXN zuFycdQLLY3%krAe;pY-QJ_ey%MV!G}n} zawMpl4`=_}rg*(v0i>h^4D_E~IS!4~uS-UXw_zDDxJh4HKO>FDi(GqUDn{js$fsgk zXN*f$L#5b;8@ws+LWW3*yk{BVG_g7~^^3dPtCPHq984Cv+JcQLhsAc zxrOgygC`rRqe~sbFIzpC&TE=1rabxSQsR5!&0wvkc|gZ4i~_f9?ii~8ugRz)*i@tKu6TmGHa*JB@-Im@Vm{1H=uAp4=ibkeNSBM^PD&z-qnIW^uK8C&* zXFm5*76>HqR1bh%+z=owz&c-jbz8#=oEwOf{V>}jpb{8{yM<~YzPWOvP4LDB%YEam zUCzaNwfG2aD#h=M)G1%WtVMW-0GF-f5i+e8uPGY(s5~gI8}mf^P4xZnb0JGlINc`7 za2EiV%N^!5YH@Z}m5Q-hXfM{uBh}1Ildk;hbTIHT3B?ry6j2aX0uD|*Qg=KzWh`_u z?2Glz`l7KM1N9#l6)OJN-p$g zO;9AUNWvbJAv>WxpF(?Rh1GiQ_0%9h2}%5kz6 zJ5#?5zu5@QHTXWF}ld1d_Yd77(q2?cA3(&?heGEyb`-+6PGzZ{s|D0=(_;^Q`dXD692ZW7EcSx|Dpu&Hd7UrBFB(k4yo-tyk_$GeMG zUP3>(s%XMeGX+gei9!*24=dqC6{reD*bAaHC9?2NRUvMyraoF(lgY_C9KuEi;tsKg z?@vGrYma<^E7X@3na#WI!lZ#HPe!Tf=B{AsI{TL{0-EG(tR|ny zU1Cg|AebKN)zdlp=K8?f=^rI-Y|z4CA>(Oq0Az?_Sdn&vld}5h`EfJg^)0r5ejC)? zeHs?NkS`M`?^qtZ%!KnG|D8WA34}y6s8!%x2-iSl+#7~y_e^EHGH2`qX}D_MUEbHq zO>lhf*0*~w_fe-Y*2U<;bCA-n{MVJ5zlS)7`o2OnM;VVmK%H@0#e>KsQ9mG|RpGaA zeK{8-%Ge6%bAbq3J(!1K7!I*8^6eT_wB0b_KXF9Rblsz26*FFlK6!)xn{vGT4TQkti)BnLz? zr;lOlKeutPUm@HUQb)<`#x-#FsH2f=`x#?u_@98P!nXcF%_5gSR69@nIfB1})F-jO z93nWk{{t7Kxqv1T3g=|n7O(7)S*4satw&9MaTy|ho+>n40jE}3vjd@o%x5wnXWw1> z{6RHx_vpuN!z_*oNBluYUvU%njA5SW&3m!#Mg7C6Mx=_F&vovIS|09d(&3_(>uuE| z$NDI1l?GOXBp1la$2#p#`cTB&%_Y7k9<2CZarz^K45_NAyh4a?wtToZbEp51+uhk1 zjiyDt>88&oFW;aPtD{0WJY1uw%{R;0DIn9C&Y^ar2YEF2xjJU;0%M|8ZVy4w{!?9@w~aej~Z%9^sz-Ccx=&whZ|e0h(+74ICplX=Sz=eoNIaPJ76`?5r+OuA--?QFq-v$3c5k z{G*#_j$pixOVTwj`^WqAW@*)^hgnd3d0x5?@(f+)3zMABgbaGd6)I z6$jI*B-VwPZDx?4FHeiZC=@{VjIT~hCs(vBYKNwrGzrx5CbgtFo51pnyflW;g3y&u z&(osZBqCh6x^tw*gI2=h+M-o!1}-ipDE8t4;S)1R$!?kNg_YMF9UYRjh7~VPDGe8W z`WV1vJE=8$RYXVJK89vgt8=vK;%wu0Gj4tF<+Bw&e|ioS+?>vzelpGMJWes4Om86W zn@f-(P+I~AW!FLsp5<6$99u5Q$*>v`JbO=m8d`p_MJNhQ>`7H!fZJE zB)<=H)(V=7cRX`3L+Y!BF6?pIk;3>Ga1TD-`woz~|4$BYvy6w+2UqQxrq*pG5$AmF zh0hz=B=R0(hov)h7wV`Zx@IR)Q|ewK?$!&aWw<$EXL!pBJ|p)rX+D3+3XWQO`5*PF z{x|DeZ6gy-L_&+N6G2cW+Lx1vTJKG<-omwJow8ioF&QpX^J7(DoXCgKeGRrbVj$u8 zmm|;c+9@Lpy&>XIzC5+C%yy0)d`%MP>p(?iYZDa3P!Z8uh+M z2DZ-$y74>LqA9TwI*lvhZc@{e5+BAVa|`YkgzFVXbk50_7JYVzI3sj+y!2!)=MO<| zF0mcZOHaLB9+Xbtb<*wB|BU+{Wys8vr=kD``F_vKnLp4|<2Z+i2UV>ntVx0DGdX#e z#;4|Wg5kjH}Uqjh40?zDW1)(dtS;JJO`0QJ4xClU6x%AR)qy> zVFP=f?E8>q@rZ+dRNd~qRk-K}O<=MZaBX+I0yWeI0uYMvV~f`n4E^*ub7W-gik(pY z;H99}2v=^SvJkfzDb^c}vxi49gDob73@5hVSV*Yz4ML1|EC>L{h6a)f%Kvhtji9MI za3}D^$4O>cx#u5Pj!)=bo3kw&jlBAx5u_=P%x}8l&Q2{=Urv6zxknMW9_@mqIp|Se ztdB#Myl{MP6nL@{dVFt^)ehvdERlzgpbLjDa=iAK=FIVYpc2?>sqe4fsj!AI=GZ6z z(d|GG2~bUVm8Z)TZ(!aLxj$Af=3{?U(^@u~U?=87iA^k{vO`-lk?Bf8ogf!zY<@~} zxdgM^*IC%ex8(MWZ#|Als2O?P^_N3y3>-RVa33?Op#*Y|?or5uN?bx;%-=25VzoTA zj2cx_Qwx7_@(9O0gnS&9ofUxz@YKk09^dW4Wj`!bt9!`Q*aAa|+KvGbf?^QXv= z>sWlq@33L1JhQxdj@FK@p26jc7!R=DST<;*3R zlHtnN89{FQr#f?4F5g?y%b4LpUj>4pf~jwRSoVYSFJDUg@4cYWaVPn8;4R7I??1lP z{R7~C1i<`zUTgdtn*YE4gOgwsQ44C(f->K~i_z*R{wim|fpltBVtogC=G8IiTuggT&N+;dQ%CyR#Z z-{V_Hbt(LkddSXLgP1eB>BreiR#1)r2LaoM5HOc{*VX^m9Or*kC;E|lU%e}GS4UWi zZ+=tgNs}6kW5rbIm)3)leT*;R*G$&xytS#)l+Z(JakqP0{W5Kphz=#?%#)_Mx9@6P z(>U(azkk2G{I+TIZWW)jv{0H@Lu{ejrbuPqJIjYT3O3;bIjnb&{2KqnHRoA6yz^go zJSrDU^r6k!;)Q5?H@uag6g&8E$we7#rq15Rvv~Ef4-3})3BLC?0@8|YG1tvIQr%pw&Sce?zB_%te)xflttYV(=H&ccT`F+gHxr?P{08b!p0)R<4Q` zaBrbhlZ{^b-DYo^nhcr=!XL2u$V?pS4I#4a2#%NG%)E?y8?jp<`~b2by?-v|N@Z@O zm1VKw^)dTEY|9r;<=-=H%M_niS;Z|s6*wFiCF21xqimP zgQhowge!hEPbpCjEWB6VIhjYgKR&h}9HR;zr$)yo4QQv|cr2QfVdqxTwl6qBE;+Ttg$a?n0bu@Y|fF$7s-<;)AERtK`e5{C<1>=JFTI-*&D}I;euR-ncPcfnj&A+VeJ^M#1SySAGavW3%M){hfGL znm=h&RpVdmXlu-Wbph`+Ub(+*Ol31Ycwg6yr}(GXG(Z0P&N>c0&*vq+I30WUK5Qnz zSYlnJ0RoYgQOtV`UVl`Te`I!ZeQ;1$`-=jPU4Kr52X^X0dOVZFa&{b8RKV^ToHO51 zNzEy=cX+mGZ%dQ4>3Xcjy2Nj5exx#x496t3W#~Ni!C37qjEQxYba`ob-}2mBXTKh= zcCK)9Zxmt=ULaXJb?}c(f?`k6XTo5%E!{OqytFiabGGUEI6B;=Po42@w&5PU6Q-yP&pZDL zU6$!u!4hmZ3u#dBf(kd;OtV&aC^yzMeow;GgX6Y)6X&kWXyIZCiM#Am8WKc0j_1=H z)06&kJhs#CXXlQQZfc$INNkI4Cl2T%ElE?)pt2Ii{Jew7&Eh`!>|w=ah3L*@p*Qy) ze=vN#3y+5RLo0D%-ckZEZY=akQ;kMxam12?@mN3SMs%hyJoyp%JZBk@baJfn`iq`I zA9S}Zwhc%?thiV6WBR9bYHQ+^!DJ%AngjzZC6EQnf%u#e1DJZLy&|bKyH&; zWXT#1gFDI&%>j6J8J7HQiz_O+AP#9o%<=l4qYj>fg2+grS)w4G&x>4nS8FlpON6~w zv*z-2L0PC@jXcJ$DC*~NK;+u@$=LfYBZgYh*a`R|#SCe;FFXq-)ggD~GeHry)lPdg z_Nr_+u}B0}hSn%;IlV*6lm;6S&EG=&* zBEkzkpkvSHi?Etr#aSfhPl!c{N&}6+mw0`@iZpGzxqI7d7t9Dk9}e$KL>1=7R6cMQ z8dJLCJQ}Yp_Q!WR)ytp1jPny!iJewBImA^H9<=NLte29i7?2%M`FSIQYf^i_OLj|8BdA#ql`_co)n50EtIsu!Em3)Df z4IkLZ1K_EUv8~z`(x|aJ*b3bx9OaDWqHp3fEFe^Sf_LktLBNLBVK#0024BAMY2%e| zALz8>3~idN0@dcADA=`6essbPsPQa~7zY*|$oVtJO2BHk49S#Fmvv)!7k!T?;;I3F z^CmwCBPOP|`Tz=`IxV5apo6FBlR4Bk5jgM)v zR)8HC>;z)$%awM2e7nts;yFAWsLTBF!ivcufboeS_{8QvgdE zA+7W}ysPx<>nPN3I+l*20>r8@x(2t)20qg9NO99V#<93v>{uRW_JyV2FKXA%>3Ysc zCTG=!CkLMao~wYB|GzTI{#U!G2zfTJ1~GuD6%~p1CxncziXQL$d=>xzZLje^CtX*L zplLdsUt_O(t_x4V;;aiDzEX_Gt2%%u@rhwsab#}Kxkkz&sSsjs zgN2~cL6FEaVaGvl>2|~&V!ACN{&HM}{7DSOZsLY;c!@7q6RTWX#RmKwz%4#za1JWz zm*aK+dj~uqP%O&DdQA|*rsV+O8K~3lY15hA(ctVt+ITFEiK-qVaEA^51z>-Cc=w3SAh^-fIr7tBPvaN*mcDv?vDPk1=4N zR4XT#e;cK6_?0Vehv=!T5$RH8Pz$G3&%3ApU#z`(T+DCRI9|4F8PP(iK}w{uBwNiO zNrj;(qy|YP8MN2b8>wiU4iui?oc^LDJr|PgB}W&9qEYGxPp@&-mQ;b3ga} zJkR(0+`rfF&$;G$U(319bgI5XurtdHRRDAg@Pbcxwzl{c zGH?jIrWdb+6%EX0aka{QWJDSWY<+lw@eFXW|Cf51DDa>1j@I0hP1@hT| z$C8er|Jo7%PaTD(Nh|)RA^)l8#9W2Hjrd=t{vZ1N^URL}|6_?;e=qT;%!P8<5R?E>i1LPu1VdIeh9*L|>NSa7 z1cuc|iKFS%H|=86yY$~(3T6w2a)}}nEgpuWD#3I|5K|#I`|L0B8}O+OWAZLsL}Aov!!VFuSi{n~=nh!jEqf?Z(>K19nH$inE2L z)al1Xt0)=i{$S{Ep53wI_%mjRtQG$c{J0)@JJ#-u#0nB&8A;&(G5Vam6>H~k?Du`0 zQhoh!18tcSE6WT2a}gKTC8G>9Vr?xS{JR0=m4IH!PGV7$=NOW_&0TRWKMFg(SP~Hw z^qW*1=d)UOptq{z6bze_O97rNFU@%_ z-C023=L;f=77C6auUbN=Woqk9YqKg(5k_D69%NipdvP{+^8QO=&s!U?`4kGq9Et=k zuYgth^>vVvWARh7Q;dO7&PyZj6vy_|kWWD^uH1XH0;Fei+%o>OqP8n(rf2?-AL*ah z%$wSHuke=C1T`2ZhnppEWD{hw60XD^jd8m;h6Fs&uqKt=MwEvjEnnD~|0u@&zX>w` z-6!QLP3PCS2mVica&K?R>wQ%6mSRbevZ;uK}_r|{^l%#1dH zUe93}neNUYpv-n6c|6~?G0Ee?&^mv6)09N@2Ac=SMe?Pa2Gy&4G>26WcVa#rMp(Na zIqeqtVcpB{vbMu7h!k<5zTKkM#I~(QCM(rm7Hl;`ocB#_#|LvzJ~ag+&bYi%5AXaw zK3b$rntIt_1HM-Jb~4$f1wZ|2W4T-2ptWS=s}A5Dgn^)=hV>!o5OZ9eT#6C?DFp~1 zf7a-&(J(Ndg#ko9g)wpkn8W80PrZRhn;YrQ$n>NDHiQ61tql*&HqGygVBQ9#3}*$ zWZ*^^c$*rPS0MgVO4(;6(!q^{JJ|k55`qz33ZyO*X=8;&GI29Qmn6&0gyja?`K<3b z5j2IY)V~W_?3mlj!3Cv2e^g+@S_yFqc76yd2O{~{5v6^c<0HOFG#Lszw?c6>fmd_8 z16S2GtY0mj7&QjEpi;2R2{P6@CW_3%G%=bK8oW=wLj`J@4!XC$u}d4}C>g_M!n7j< zc03CUK`PsMGJcCjw#R)ADNFbwr>|siNsC(_k?moK{XDU~S^KAzj(N`jM_vMe4^+imiwk?8%X8gE>gL>5*RFv)aqRP95Wv}n(*?`C}{*U z_cVkjxCo4SrMaA0cy3rGOgr59{-DxJiLVYSb(QJ(YtM*{PG|1=DT(KW3vpEx>WUD40>WV(K|2o=}W$ zot?|sC?o!24c&I2@>I_3Kc!4J;kmcoN#-4B1v{Ibz!J&Ah*v>N$vhy<;gAvp`cbBL z9YaR;zr#XiR~54;=s25e!M_En=4jZS+Gpff1LihS@?i)vVg}u|2Vk=-uc$qg9;b*l z=(Wy9qxA4o>sv6xrtp&jnC~0`IG=P$dqA{sGgMdrKw2VL`ast>cr_G0m(0pw&FowZ zojzRF9>kPHP|c;L3t(?R224irQ@U-K!9%!(B677Th9=78&;_wGrt9l|cPk?+3HbQF z0w=Ur2rmAyxCJ6_?6@7=;&x>Ws#!$&I+3`(?j{Z>TCfhU88fZB5~NN;yy(b!(FTNq zov?+SeLM^za{xAa@E{>ciu@C|m<(q2m%t()rXYeaJp1tP-z|UpJ+nje_tcfBJcB`+ z(V?@<@^6S-rx4}|KEU`kqy%0e+ahl+R$(()ba(K zxfTFmJFgw7a>ENY=187Y6IX{03y;bCAaY-$Mo8_|zqRXQf)F+OK&}JIHb{oUW7a;a z+17az=ZT$%8Ud2uV2F!hj4FU1ol;n|TUrt+M?kj`4@QJZRfE~&vuKt0xj)%wHrk~K zkA-MdzMmrlCpmx-#-tI@TH31=D29Ttj|SFIC$32$?H|q|{75aqF5!Np_A^DKk%mF* zXs>U9C;QCk6ns!HL{ zyxpau4K`iXoH@JE+65qux={o+wI81^hRteoo%nY~g(g7Z@0h^-QXpXG(Vf@)DYav{ z47wC9vt58v4&*wX?g(5GSVNq+ZcV4$+5{-e*(GTJ@ya>Q3h-8y3X$`)7azj5?Zx83 z{hjippC1ZfEs>NFqX1(miIPxJ0~(!aD|~<+kKx0Xw5EVlpvAB3HxX+*Ac#5mGqm2C8ObW zup$*F2=ZfoJa-%{CVHeUYE*_^&c(x@7H?-sOHgODrnKew$8DKsDmbx-5(Kdhb#G`|zJe!X;>O32E(<7eMXXXCj z>~QCiO`h8B%SmUSw7ahgN9%T4s}1i}bzZR|iMC4o(2)365W`k2qIS=d?Z(S?wEG}d zYd$>Xur49;&4@C8_rE1OZP-?v^vXyUA~QJd+APt=9B!KTvQ~>89rq*L9s-7SC`0$y z5elI_Eqh=F7vuJFu<1-dnzkFsY0h=qGtWO~-?O<)W4ie!b8N<3Sc@+~mQ-Y^2Ug_Q z?}S#{XUn_JTDoMQ2W%9KRAsxx$=Yl38}0unG-7cz zpHgYp?=po!O~IBgo>4n0-02W$V{Rk;*6Y`mi_$qOWY9Y?BaL1L?+5$RGucB8}R_0&ZlP-tka%vaD_5 zk6Jcly`Ue@&CJr^)O&UGQZFl1dv@4Ad-|y0EABMB!%omU&ZOSFiLiR6deLsM6W`ng z##r?cQ!yW%bjPjdO@`hXNz83f&kL)w$F-(Js)5Ji{ro^*bVU1&Dt-{|E<;J=EPd0n zAI%32nVBvHH-<9pfJLj$QpySUTCpY7vxac%8oyJ|XWsj1>uFWQEUSG^~1%tF~(Ec zRk-ycGB>8AgJe;g{gkA;UFT@8&YH~39F14H$x`_bO;%0n>{WYO(}SLNKq1N6X7K5J z&0Bd#^ScJ=&*v(_kBOz4%Kt z8hm7z;i|iZGv8SqGuFhA?ZuEXM)tUyQ|4{wh%dPsi>`dtYCcFRpzf$?dUtGT73dXB z+oM8eSL|!WHziP7x9t6FX*%-eXvQ0Qu%^T!e4#o@u_(~&M+ z7V}pYo?>-u3k=p~$(Yw^)?BDHQFv1$348fRNVukH`bxDdsVvJ&VNz!#qN4ekbYwwZ z_G5)dT~3xS5B*q4NO`_!WfCoc&OR%GLbe%ahl-Z+-t#zjzB}e|SrI=k`bzBtx>^`% zP{I%<9R0!KSW|8xuEYgC6owlYVj@2@6kvJtb{vS_S47Nu<29JsH;bnLD$j@GQ}A+? zyr;Od;3;A&HXB<}@$l+{3dY{d;PLytD>58cBcD5l+mE{z2QGVtHd3(+@fPkV`k>l= zCh>2}{rB?O|CWZgl5M;9AoB%88>a5V?3_4PjrOqQId3;Oh~5}#c&BtiS)osj-BGfZ zu7_TIiqbut>l&ud5ga^nu;dHzK#rHU%ffxvx31lP_8Xw#PV4q#yRhxMo?|ZX@ZlaTnSrsP zPubtjoGSZ9(@yy11x3c!{UNn#{0KKDB8#r#Ts!p1sJb=n;-$U4D_7c`N=;Ok`u1(# zoXzu!E&|V?REuCP%d4z4LIUpjbacU72op zk{jL?LR+kSwn#nb*he?3vfW9+-7T*cPH9bPXS4i_e@=%N8I7XlQUM269NR^k!(H;| zrNE!f3gPIH&AiXi+wPk=iNiHYjQcA0WEp4|?kLP1H6Z^fr6+`HY}Xs0jy5Hta}~EU zH3=!%m1IWp(C1usGxhU*|2=qQO$e6yV=S;g{{3=)@r=|Aopa5%3KPHb>A^nAwRV{| zYL8Z*bL}O{w+c!th%1ZMpAwwm3T|{VOT859T&;_UZR=v(+EW+XqWvslCn5t#6`;wL zDQB$^+#Y39@6s}sJY{2+Rm_|$dgQsv-Olbuj_fVJQkn!D?MNCKYhV&sw(W z-dnToaMk@f*Sl-kIkDQ2>c75}bXz-a(2JXjIcKe6DucYE#o*+~=G-v>)10}Q>3Mq6 zDyi~%k4r{!o{vfC_xr}Hu0eI@s=k8><^g52gS>-u-AJ0qi?dsDL=xxF!$e zn9E60e)K!~g%-xOb@eBkdYPklGV?pDX6-+vAMq{U=B9!0{2pEEAh^R z#lgJT$rqWyvgMUmAM9UB{_Y=bw=kep>cRgdFl2xky zqh#5B=;$-rc$xuqd^8JDzT#Jv#mp}B87Pu2Y#O;^@TnY)cTYL*rfq+8DfS6< z_nvh%iJ4%KQiuArDP$|IQdpp>S>kY!Vq;odiLzqS_O{cVE_(htcWf4{JnO%RY5w3^!@FM-KwljC zMyiTOxIIC*^EfAPEjJC{e64jz@$pPjdh%CNO8KU*bHdkx>L5cb$)Y{NM-;A*FJ--N z+>W199N@qoK(aN)uNcyIY!0|R`khjV8hsTh^mj^ei_*xx{#_K6VXs+>>@0~(2WA37 zo;C4YRpP403m9IVe*h=iWYs@?yrZ+wsT?8U`3KgA1(KEfeYZl2(n^`6N>E!27eMd;te-yh1I=2{u@Zq)m&M ztfHcH&Yx08=o52kPa${tTOXU^gpG9~ovt|XRau+Uj|ti3*Nb?DuL-&?1^qTp@!nIp zfG+QeCa$qhYO=yp-_Oa-JQ!RM=h(4nv{hgA>)Y8Cp4g?Qhy1F;^wW(kd73n%j11o3 z%#vHz^|uj(Pq2LtR5R|<`pDl09t~m_c@)~1a~&T=UpQS)JPo)#KqM6;*kuOx!%l}@m`tN)cwXy-UPv?)4Q;-nw>pN9 z`IyOB<5_iKPo(+qg|(ubT2jb5V)V$KfYH3OxfK0$i~T&>ZPCdLmnn30q(d_^5}}PGSt0?!R)6>~C2|)WLasnH6<|3Lwwk zmB)lv(VgTnR4JtH$2g&O#8$YElRaPa^mgONEDu-ybXB*7QY-9Y?>=c9V;a zq|2jplt;_p!<)H zGZ*xy;hA`PswFqcO|&VeQ73S= z&l#5S?WRRAgvlz0Gr6%X>LmUPA><84WGc~_6Q5|Mkyo4*tv3(l+O&3{?_GUUhNyW2 zO|7`^9gFoo>6>^)db#0*l&ZZ^>THDD@D3>o%D*f_;~!|MJI;~A1!QM7>xL3WyRecmH{|mLf;!b zPyH0CGN1nRbz23GiNy7yEU5hUAfK{)}*DndZuvBC!HAn$wy_6-=8kM6kXYWw8-Q7fiXOs{SlP_e~K!hpSTk# zV_JO5tjnS=foHE=dgXNzDWz3FD#=8W_2U1O!Y*a~a=myY!C~56`x6p??WYCQPE-b{ zn<;Lo^fr3s(EoVMD;`YKzN}5G_|1Bg2RUj!K&)ph$G(3?($%LPz`N;q<1PH~d~rL{ zp_Uai_XJXD0776!Nah?L`$$?q5dSkT=!nxEjpW;I`*jKeFMgPtcb|Lr=7$|W5c9S? z@!P)aPYDWwZ9Mz#oz!z5Lrd>k5XV#psPTr3TfIkZV{)Yw={;0#Y z&H%C1$M?vv+3>39nVe9C+SeWQD`qmH8dgN;Ap0b=POt=5PU8nc$RJ&n97hD=00k(E zJ_tSoVPMWQ4%`Y=Vj;Bb4V~Luhnao>ju>?80xG(Tc+i8h7jz@=a(~|o>a1`To{yNU z#S+=+qb-2YwMzsaaYgbmBG-Xm{&g3AE5Aa%#-VpZ%c&xb*RQZShv^ds0<}?`QfyIF z`3e{cVZSFK1L0%~5qS@&%E~N}(hHrGY0#7}qjM8Jg8oQ*fDA+;!Jth_2BWg&aL+1lh8}@eJ#Q6*MwumKEh3JaNB7-{R0rLyB`zb+BNv*^D z@T(=DwtydR>VW6q-bMeGKaf251zm3i2YxlFZJPe3Eb&!zZxao-Zjs3ZD`Zu&34VPZN_VSthg`2y= zNufCT%*?PKtkT^kg|Bv41gu)xeBgf4XZEFqO46S}|2+wgWU6&!!o@3-=^GiRvY@^I zt8E>xpXoYF+c~8#kD%;RPTuDIqS>~^?9I@p^6oaydb^?zUafQ6#xy4)iv=8wm zD*m>f9orkK;_n|UY`ObcJ^{l!8#|fy80v~B2hyGI!_f~up3-EysWNZSp1$|)_5CvI z<`z^-v<*07eaV!f-{B^|qpz7wpYG-*)KtkoDBk+*_C@Q1_iq&Lhys7lP*4oSmH6S3 z6yF5Kj-;oZE$mDUY${pz@x}`U%eB>+`X(kPHZ{+FQ{|2t3L>{OX45gvgJ;U{EnZN} z-IX639G{e(@u$=_HP<6+&QI+*Fd5Ir-tE+G$R1tna!(wdvCq?upT2+UNTL5kRK0`z zllE7e%kx;$3Kvv92gZRn!&lNm+p_l+FOK_2oftZ}LlWvW~> zXlklhJ@BKL%Kvg> zjPNV*E93(#vZeEG2FRLRf@0IwvE&n|7<>PsEkU zm04@|g;WgbY<0tPj?E=@`Yt~-LdS6z;I{^!Zz_}yv68C-*f)l)RrM-0ola?>%rJDUeSxN5iA_Y7*B^gy|^m zsa)|BciYuJ73ebMaQ2Ygey96?7t{ld?o#-O-8v?-;gpxYH|C>jU_^~+7#NPn0l(}^F}oSJz3)>mSeB#O2m z)mxx=+@?LQA=>#Leqv)OWbP}46rT}rcV2UY_oobsA@t|a5d>P)yNE}u+F zIVC+O!AtFf*&xS^HP1Vw6e!;IDYXSX+hMp%o6CtpW&$;QnW{y`PbufH|>ruW`m4~&NgkCvUjYkMI& zq9j?$`{7L8qvk_0+ul`zveJ-|G~a90{in_6*W$;%`_*myb=GM!Z5?&`a=?bmmWPi8 z4zv}RmH`1r#BXKH=94zlXIk+5GxJ8D9rUUa9Y%V?W{w%e$IV-9A1)prwo3W6X3xnL zvUUA(dWCu&E(%Bc)-Mre*3J$Yxb<~*TGjADLeYBorF=U7%oG%jN0hbI%J*Cz>|h-w_7Zs`v3eW)?3QoQGI(9wY1b*?JTS89wG8fzzh@^uX^c5av$wq3;L=s4xXdkmk9ZA0#_ zEqU>hHN{`$=1M3ageGtN!ck(7DB8izWC*Nw%xkQ z?zz0fAuZd|URI>~GiGYkr{|5(^%S~rhP)riY$Pghd$6Z1anWF8;-LN2ceUvqzp9Id zD|51P@P5|27@c5%1O{2#Y8~nDe6VT8E5?zO?85(|e!H$tBTVa|$4L_@E&i;O@~-q* z7`dl(y9F>uWg4BT1bWF7CVA zO?yqBzGxm44z6)(BN|&cEgqq5+VX99>U6_0+@l|Mul8}t{obRM!S0_ z1`ajY$`&Za+YIH+!0zZaD-H&{-q26_khfEv1!-vj#)BZ#CS4dqTd^aY-Q=Cn6 zW{P$t`MfUvB&(QV9UR_YdTKTvkcZCdjA6@h&}cG&{L9GrRD0;(3L# zV~>PMat*=iIWqFneK|_Fb%H{d=RPs!ZfbSH(%ACakPQj8@pwzhH=hGvdK|PWzbNqW z8#}YDx~@j{$U2qTZ*9sZ$e-B!FvJ-B`5^fjldo%xq z;<%~&dDvCi(H)&uam%hYCWk5h>^KAe}ERT!|l??9Vs`=N|l?C9KckYPyg zbEg7fp3ei#wnSxXd@x^J zcIExaBV&iY&(@uk;;ae@NW350g=!6cXca6f+kHa0Km1%>54WSVt(@;;|AS4A&U4Hg zdlmDeM-abbblThI<8bWu+`Ia&99=$sYu%XQ?TdqXE0&u6bXDn=xgEzSl55CX{`TzP z7S?|LSk;yLMFSn?nOhH~rM+6V*+j}&K-ibf*rUM*9-!+w}p7lcJJ!! zs5PHW7UCC{@Q@HZdt<%ZuSiDA1MOkkCfun2^l+t?z2C*>D>-{ttuIA5+TieXnfdNW z_s)V{3eT+h+~Lj}g^i^-)0&6+rfcQMStzy$5-l^|EtFaN_-|fA|I1pz#x-?~cBYm& zb!i#8BYhVBu0E|Uj(@O^%R4@y?gd+iroiY=DUIZwYH?^5OT6v0&3k4T`fXXeD>{cM za&_<57F3qmw6-5V8z4xZ(#g)mzEZ3W^yHlQccYl4el&I!Z5Kc4D&#E}#C=ZCX$V{; z_+vVvu6dto)x!sSMxFef*hS=zM@o!-5zo6974j3?BI+*x%yzGssD{!l^_<5G43Vy? z12o$>`hv65U^*{aCbpS)%T&V%Mq0*-R{Lzk&h)eWDdGg)aGO_cPRvFKeVD!eCJv8O z;_s;`5vM%XV`t;XPUv&?9(%x zoCq1^T}Qae49Mz`J(1CBQ2R|0=Zc+=;u7eJpz)ASqfgJG2&@n_3^5!u<-dOyJ%r3* zyNn-Uw@NA)i44JsB0pUC72n_4HZqM&_KSz&Q29|trWCj$lqHfnAk@L_DWhJzr0B6E zC;;b-ofn8-4o8^+*}s1g(AE<{U37*B`FcGfz+(>6Ook%K@mA^T~nO-kJ%c6 zj*Di!p(1tul?9W3N*&u+E!r?ji7(pj0&^d>W!4B?hC3nlBC)@io7~ecR#e4e`P%yt z{^e)nt%6BbH{^fEETEuxd$Gx?7vCt0u$aI-Q-XLlzO-Z+GUj-eQjArprtEtc%zyp|Db;e{Lpi{hHS}e=-g6! zA-amNXs~HfvWt~k-lt>L8r4L~9b8!!tH(dO(lgj*}D5?980J!Hsh@sKKC>kA?N!SK8gDR{Lfd9r<)M%5g<-wGV4#hFLe~ z+T{i3&3c@Fs{AoI@8(WdEQP+2Hx*3hS^)NzSyFD}m&l0ff7cD@ ziUsY_-RJWO3~GovdfD_2sPTMV!M&w{$gQC+WQZC@WgrXT4D^mVSuL<6F2N7%<2!&wml=7ds-d(bGU1x5WJOxkR&_RT&e#`SqatkU?+n8)aq^&JI|d%=7!KsuA!6s^ zRHMNAwi;c)0X&0I%*lZ4FEFKe!TMZ@>(#7Kkv^e>I07uDmq~w<1 z0@C+P0N6Cgi!QoK5jcSs7dvs(@b@?*NO&wyXD1achm$_Ug(vUFEUd)OvyQa@@H0{3 zp+2t$+{{n9qbsM_zJMSkww_216mdL&4^9Liefb|PpQPYeWOcYyGz=dMKyt%?-x(YU ztSS+jLhxKS8ECPA;w6D_inTkqG3a~_BIv+!tLcmA193hUuuN-M=FJV5Rx7rtSRMhg z-UW_v8)Hyqn}2FZhm2A<5$FV7y0Ni;ODIhcKt9)G)P;5j&oze2P%$toS)`v^g3N4C zg9(fO^WI+hz}=fygYZsXgtW8^DWZBk8Kg^svylME#T;%bdM_r3&fNzz6~@UG>C^sc zob~_O0I>lfUINf}A7B)-1gj+o;(?Uzk(3{K=>WHC=^06&0xTQA@f$*PE(yS)1lUL` zADL1lF7CaFUS7##*)!{lz-V6Odkj+@YnQzU(i2VtnsiQ2{HK3D`G7~ z{%J*U9{;ic4=T5g3O6sw2hdN(0L%$?a8UqAR{sS^8~zDMwBJCA!H!u<&^;&)L3N)& zS*bgvw6akdOa>i}gt3t%fE2^{iNj=7xDks85acD=Mg!ubiIat$%mq|bc|IQxxGhJS zev1R2$-fBFF}w@$jwyPX5`^bImmJa~yl7kQ@9%(U{qLmkVK<@whNTwV)$yeU+6+=Rz^+fvk zk`MLWPC?awHEcsyw*8*{A0J^q&`WOBvtxon#h)d(C1nP{p38*|Rhe<`0Rfl<=&wpd zm1Wr8VtML(xPN>mP&O->Uz6Mv4}nV;5159^U+`v@&}TFgmcnLbf5Te_kYMlcOBoH9 zQcXf8z8x1r3x}s(f)^NNOW=(aZTpw+lCU3Gl97sjj|B6pJYOKgk>-}>mLUtU!l=K- zfqB`mz}QHjKz|J#5m-%KhNAP`fR1R1fv{kvU!!u875z1iDT4)dEiU7su7!j@ zGBzOW0MQmBoVw3ppuRUGTRUOgB~v0X@~~iyYb9&+NbJItz$jOp=y6g6yybhOERKpIuycu00crp)fTR2C;v6B zq~-2&8Kh+Fw-f_O!TyqB24I)HToxLp1X72mo1`WN!ag96!iHBe`dR<>v&r&=d%H#!X0Vwx%!?>E_1icL1!}TIZL8LxI?7S|vq-W{YHbmoXDaB)ao>8%NtoZ|v5_ zXwgzZboPU`o}xU};yo>n^=?#Wk-_on{>B5}wEJ8jpy0wq-?d=#@=1QBy*}hc^!1s0 zZ&JCoJyAOa6ZPN`K@hx7B#6@*&KkX#=ujBf6K$kKZJw3bqkUw(3BKhTL)18!3Xv#Z zzG4+c9O|z&({aQbHS7!z-8IpxFpd>zS5sh-Rx)A{T$Wz9+3bEsRB|_#xjQ(wlMj!g zlS)h62Vp64U3wHKdGH=k+nF8*cFdBAB$UhNp6{QR6P5zB=!({!%vhETR1 zVOTXg5v2`$^f&HEp-Cex72V@Fr}ELTEnw849iY!FR`@nV$Do@nhi7a8w(qv?ZBWm(^GNM+Kg_>K`8r@Ev6YEKkHPyF`kXulTjL;XSpmgwuI*(K>53#`yjgZc!4g-er z5~*1?4gVl-7S!MHuI81rc2G{G6{uWU8esUd?wJ4wq%uWG4^S`0RF!!1Z z3PjOHQUrFwt`AteIYZ<^1(E^|I)NZcl~^5-nDpD@4m)rLk&yV!anY%(> zx~sIyyIgNZFO~CV#4>CL$w{#k-9FmF*LP~J zk*O}_dkLbDz5+ow?>*cp2xwZ#?8b|gsK7tF<{%K;F&i8#rT`0AJrG-~8>s%L)Mo^3 z0}08SLLbQUp`aoxYDC;$yBBMOl8S5SuG%CPIpNjr!po5EsD4J;84I8b-a=)WHf%LPwi9Zn340t;} zN=DQ)x{O)@KWQ%{p`2p!@#{UfB|^sy&@q1$Jxoi4fD>2&`>~-NtS_}3FHi~q67Ucy z-Q<&xhTDW8TwURxQqkH21-1A0Vt4;*JIokzisvm>loZ^q zVnwO~(shyr${m|p{VeHmnrLf(IG{n6AQeW;fUVS0O> z&mfOh?tpgcLfmH2y{eVqqTz+j1Rn}hQ4>o6BP6*?YQ7?+dV)$c+#7sxMkg7NsDoL% zB5~eL92xszMFj4F_p5!3=K#5^T5oN*7&wR5xQ77jK@chR}kEIIl> zfloBtsHO%`NgSguqr(lmi?nN214E%n;)Yg(@bqLinGByV_yscAHGu+8AE_eqFzA9S zctP0I8LinRjL)NC4(L^xGOS>AJzmhblPo7l!S{7-A7zxb5+z09Zop86WCg)nd>?(7 zN(t4cq1%oEW7)<#wFHMBMg&J(AO@`P6XX$<<~ntM`BN(HW)p&4ENP9!$%=^B@oQ(O z=(1QCO@($HMk7G#lXBLKo;NJqd=QkG`l2-gdRtHLD7wrK8kgI5fQkI6;0P}$m*D~@ z`{3Pcpi4=T+SMlAB+nqS+FS&% zM79hGt~;`>#%IS_0KBtTyF)!msXCkDwX0=3!*$?#-R~M6v;HqG*Z=w-Movs4WxNuc zB(cL)LbE>cLf$*DUl@|*qxz0Tui}fDPwm5<97u`HZKqC`?bh(vK)f^Yv&2v~BFW?A8XvFV1XkR-;gL??scw3(J zefGx-F*c1l`d?nPJNSQX(r|lg_wH0wSQ;z%WX0w87uuGqxko(aD9_Yy1CbesMoVmg z&{Du*R~i|(?f=$xl9|?hvBUp$%bqg|ms(d|C0^9sCT3oq*jc05&Di8e9X#5l>+gi- ze+A=EE!>X@kU1#%8Qcs7F|DW z#tkWrK-IR^^+PreYbRLFTM9!U_xJF zZFKwg*cn~?&zOpz>pLCi$IPT zN!OhB^VrYPXsIX#f31`}nL4akZL(b8-fjJ~+oCZuk~DW>%DvS4idSFF{?S#zT-G|< z=nzz=RG|1S-6BX^PU$2)Ezz@ok?HazE{7mUeY5_5@u`3v8jvG zSj9k=X=RaWEnnX=Cv5eWS*UneP_}TMBnvxj6=yU8)OOnh3;ZdqLVLyxO|W-D1nEoo zh2OI8WtmEXo1TNGg5rN)#_L3Oq=BrHMPYqL%=hg4U`{V#RDD(&ey#H?_G<6v?nEO7 zcf4nPNn&nsf_APT_&H5fOm@}Q_@ymwCf#2^5?-NArd!}!g9zLo=LApL0sidBl84rX zjp;?TNqcflECv>pQ>VT`U}w7v#`v_KOqf1Ze(?QeWrJj=)aJw6R2zd!jx48KvfP($ z$aS7ww*D?Fu*Iw5u)d%7tuu!j_`Wp$V=yS$ zNWcO3xBh+V^ml57F%s~4z7(m73>qf4rL;mZ8t~lvSa{!Z=ZMC&yZzoW=lAf?Di28B z=Mh)x;5Luu6mZ@BDsyR^?<3Jb&C7!D+xw)xP^W4#9J+HJX%=?lLqGYqY;TGeXED7k zePGqrrmU9(-e7pE?;I#W|%nmmjy4?O_XMpg~2a83Q%Ik!$UxXkI(AT#fF8+kf$+b|kO^BU!>dX;$H}4cX*4ah2I=GU};~f^+&tA^j`{jVw&nWqZSS3nU zBxu@w9;-acsk7gveZ*Ht)zXutdl_^^C{L%Bd~%8$(Fv3u^g5Gey@THDJ+;jBOoOHU z+5XCXeZKg$q?Yw8ClsG&XIfqzs-;DQAYb#DfjBRWll>I=L2N7~3v*BQdeY@+`{HgRWopb=?S&$ z9vzv|PwKp`Y+pCJ|FNFY_W4QkmQg!9 z>J`RAZVApYA7iu4Dbc*}dwB6Ru_=lO3cY*M5>MHyVJJ$F&Q^M51?4b_5Q<+V_&KK5? zye$0i)VSPmiK;4L|NO47kMpN;52H5Ezlu#TZr|O zN=X(^F63-2zBDQ_Pq=t57kvWae;9H8WR>nnWtqK9<96{TmzkiUWzLFnZS;|()606( z3x2viIP`$jHnrnVDbL}eEG{P0b2L=hKE`3L_;HNf#IcG22jHLJJ%oJI)=h1-Y%Qj(z3UO6EbjdxU_H~*TTKV9Az?eU$3ug|*1WRF-+hVtupdZm!;grZ%~H0j{qMZgXM&se+>S zHOfO33{Eb4i|hHmjbv~c!=u60cYLat-8(p;~@6n*m%q7;!Yjxl6MbQNMh?Pp)PUW z+?*BCcYICkNZ`~CUE$9we@c0LI2T~zv2xvq9i2v|&mpHqoG8FEUZz{`KL7OS)dF)W zZ_0N&wOXM7tM9Ef_;VU(FJ`q9_(SNUw*#@(!QA?i9AfL^9kGG$N!XU`R4=nvULgVX zBw_SbUoe#oq8;#zDA5i`x}2c?KB%PiM=D=f8D44RYTn?+$yt|1>HS*dh!oM=Jp!mk z#^PH&{>RyvCDm+adS;e*zjiez)y|qlo*%jXY5)FrCHJQ2y-|>|d|8T9nosAn2VKT> zDueA1^5blf$nAZOUU!*IwR#D&)s7bYqUjgZhtPX>_ROI5*ifc!Ow!<2T?j*QS8U0^ zb8;q-QD&|3Ncg;5jhi1k`|5gi6K}3k|C0Q3)1Kf9w8`U5?*lbmKDb(cQj90fiT>PL zl{OW#C7>)yX*>>LUSbJUm1Yy{H@gHXCd}90pIWZ`8r?csmj5t^2&ZnUF%sB@N#XhimuBM z20_1tt0ofC%EFoQk0Wy_F_2|$dA?5nJCM;*N7c4MZ~wDn3Ll}!&HQ!pWGRq*bPS5t zJb`Go(uke3FNg^r#z~}R2u3aCRA^iYOR08E3z%l>}hNiQC#AR8 zQD627+jJ6CSMx0Vngc<39Z0xd>~+q?b*0#)S4Hag9XFnM`h`vyD02m+UoN>(z|Sl)|TG_FI zPIfs6i*5AMnb8Gb?q$XTe@882b6Hg<$@a@b?c9Zm#D^oAU#wpW|1^(2GGndD^~~h@ z8!sHC=!{92&FpZN-M4t%{XVN}#H9<^zLY}Ou~OZg*HP=)VUm{}-HNb|yCNknwl0BS z*oP114M*+Dk;Dq;JgTez8|h)M?slqauhP6br?iy09NJrSt-d*BP!@r=Gn9NY9n^?&T=&dF$3P$G!r**vJHKP@fzwUPAr$q@s zt+D}XmDVu9xW59MPv6Is&jG7RYJ$8jEn8-_lmcgS-iFa;8{TJg0_+VabUE^G69^E- z=5C@?6MWRE#?OgOhl6C0L~u9Pb(llR>i<0QX^xg;75fFt_zGD<=mLmO383c92JQPC z6liyTC`=|Pchyo>G~uHL)!+Z&z$T*sk4o;*=%<~R5C4pM-8Bq&=2n)|VH4NERQtQv zB-rmvC^X~{i5 zo!5&C0BfoTDPlqVq=i})KPHt82xN*Y-y&cEe^r3W&gehP${EkFiN z-f541IExTi`cd&E|5xBF@$nCo&1l2G9tuD6JU+(i@*rO&g-jSK%*5*>r!{ih`J-Is zV%13YAa39F$^8)h_Ql{nEi~c*_G-EA1938xElhp_^(O z{rS<;{=M5{D%*i=D-Bf}TN&Jsj$&pIylmOOe(UWaDy8A(->bs)>kP!@uGz)UsGZVD zbq*$DPr>Q?1Jr$cw!1mfl|KfwSGhv&X^OzLth`H@LKIxg)qHPWCx*3YH2$}$Aw#r=AoDEf>XO({YY|yF-MeQ%&iOhGf=IzgR*Ymv6 zR;Cs_c(cXzY>$}&iF{;HtEJ&MWis}P`2_yS{_afiZJ_zpb$xw3is#KL-!5(i z?CTjB2elGBSOw1`PCM(w2rEb3YcJwZh=W4to~^2wh^^OR#Mx_yTaTfYvy^-U8&*1P zFnm%U*ZTuXi+lZ-tgaJtsS5GTd&Cu~KFf=wW-(*s(hggXtag9ztG{29rP|i-yvaM* z8@z#Ra8e|=CKdU;TxwGN`o6&Fh#zzaj{{H{YUC6{7k>+5VPX6*QJOJn$4K)H-r?B# zzF)LhjfjohM2XNLcY8Mst?;*MYDV8$JsvBcKZPTUJnJ~Vog(*nd~d*T3_Z6oAfN7G zOX4O=c6dNz_YY;Ao``7Qk<3e|Rgn%VacdABO*desTL+Punh+yn5i<|-lm0dj2j0|v zn0j8SE|6!R!WJ)c<9|Fl;h>*(Do_puy};MV819Bhdeu8Gjh35#lawD+ddSquHpuBd zP)OtEHhF7b_iH*)LV_L4fU>(6wq!Y%9E1JM8UvBbUGi3cf?te1NH^3je)b(@5&U3T znSLt)ri?(-RgiU?i~n{obv>0B{ykS?}v+WR%Q=}T&)J7 zz=J;Tfh}%1iDKng{mv@l(EB%247noNgH(WJmJs5WYcs>!cg}gTCi>JJk>tu7^x^yf zunzEV+wGcllFas zkvn(#1*U&OjfNJEo~>l)pv#3vEw=1EWzcx&yN89Bt2u1q7d}ZL{p%gvZ#-%D4Eu;+ z#4c#c2%3=-BVgkQb4?CWL=_)$sVut#ww|6){bniOw<7pT3C^V0NYy zlWMIcW!zR#t1)}J?8G0(mx9XJZDnQWe}@e3H#_z9=^NzShp%tb9)(UEBth0E@(zD_ zWJ3Aosg9n2P2}z!8PAVAaRIsh3mH&eYI=k}?a8(GZB|-9Zy_p&bH6Xi`Mk%`VBn?& zBpeooEt-8qumi-bd_|^h40Qi##p1(*P#D&vR)XZ{D@Yi`#5!hH%^R zvy&kvWVybn_{IbffEhLbgV_7v`7t56NW1N4C^C9`e)1j1jk_elt}R{Wn&j88u$N}P zA!}_4U{Ank{^DQvj|Z<|2JFo?rpeqK$B)AnQ|Pd{tbiU^?XH{#%Npq(aF+{g{mCWl zB4kZNxw{U2+Xf4wEgIr|pW~ccw;%Zmyf%Afv>NOHXu?>0y8?3^zS^7Y^YiD)7lFI6 zz;$FS_C%08CCLD+8aZTiLDxSVCs_xE47YYb>3LiiicCUMd!G?M>NXXp;4x9LHkGs7 zkOK~;KLDsp_V<@UG3EfW88%#%@ScDu;|f#P&P6sMNNCY9jofIawJ&c=&z)MC$jPH} zJhrgVi*tu_XGhAAWR>BXp=gQATJxDPWtt$2eBnPQ4E>*er!1x584izfnd|gm@Wc%m zSrB#E+S6CZO**>w7rCUH;qBXL6tD z(ShFEn) zxL5BTKI0%Ty;g)BayIN>m(1wzlpS54i9K%D zXu(M<;3^sT*2i;AAVM{{fIPZsd7sp%nh=p`NXSjlD^7G(*o>~j$Msab~BnN4V7?Dt}XI zMLl(?b!EC6=$X zeinUUN8{6ILe}2yvRQ@-1^R0mel>`U-4et=WzYf+2tjqV;v3MiFLcwTo9sVtL42#{ z>!7#n>Gi}R*x8Px&IuMRf;~qu>mBIipyIii&_Q+Ej7M-{TKDs=2jv5{^?es@s~%#+ zEqu1stJ5KG$VP?iAy{M?lq=|T?}g10kwssMrDN(~O7v=rnR(9g@EO?q!?*L6t#$u) ztxM&Xc!O|Umn8@>)#l(IHcqiF;_lS*yImg$x;f;l2AQ5^|@?xt3sqYCw#O;VP1E+(t`r}Hq(G4$4q@g!3B@pKU*p8t?R#|dE#dZ zg7CqmSXTUIH`9tD97<`A@peM-&BNB@^`&c-=>sk^w-Ga_X=IY!mXGrY6WK`S75fz- zp_M-sdHg_`I-Fn#X*0i5V^y7dqrjn$6pZOy)d8gr%Yd)Zo(#U*rw`D@plGJ|=D3H9tiS`x z)pCrY8J2#@LR+&G<>ZA8L#?~KKmp`f{VP7X7Igtnt~i(@QgX>>e@-c7Hr$$!Z^st$ z>+kkdatyvFhWD+Fq0Nvr`M|2i@o)U>IoR!v5Nz1*Crup8Y$nDZ5Z@M*=JE9?yWt+q z>XoszaAU}<(293r_FK0qVMkP&R3)-~MqMjOhR`8}P1A}Hg@K|kDAr%%N8=C^b281a z*R_G10F}~WljBOzfY9Vq;+6USP4&I?Phc)Rf3Gl3e@~T!Fh1f96cC2NohlV?hZbueT>gI2zb3bo+82U zW?lA-8+##(ElPy!9P$H(#v$-$NseshFOWy04k^vPcf&P4pYEvp`bjmVSOtQhZxcbV zI`YO+ylyiL6=?xD2~G>=Wxv13nl(WGB@~|y13Rbs#q=*ZOt{Lv3G!%S zPtNFeyD39jD67 zSpN5iDnc(CXkYpcf@ufA#`*u2mIldRy_zRdTCf}o=%YbGg?`qirVRNFi+!b389~l> z@A>U?4*%SVp`iJLJgNA`RzoflBoY&B=z{na+cwG@mi~vT7^buY*+~Kh&9<)jPV8PF zdMfP3aAJzR^CaCSxvh5OP85ddTmZxg$_SndHt!Sr4+qCZEPc>hQknII4e{F>_~9y2 zK%15ZUS~z=OQ(*aD$<|Z_kKSDoE;bUAw&f-(z^(kL6>W9IE#@`P=fPg2DsS!N;Jh_ z|D{E3z%eeRJVgH9b^&QvyD}?#RH7A}MJQrcV-EEDn%hr0H1zp8@mE8KkzX z)KcyR(AW5x9KebqnH*m=1EDyIrridcDwE>RtQ+&4#^l01V;VIzmXKhf@b~z{x ztfFUty`(|)1hf1sfIH^*D$w0fq46_Amvq zorxAj4L1U}uKm0M-B}J`(Mo2-ksbOSiW|E4GAL27Vt@iVc-4+l7@Pu#RZ%PjgdIC?HeB z58mIJA%}M?!<{LPW5%U|Ia=*x49;_OXH|yqSHh1h0-5Qe2Y{VW7z@LQyn6Tv@JUOU z{5MJ=49|P$QMbmT;VDE+NJ-{jepZpD1evLv(&X81K8Ry=iRA)qxBI9u6~Y6rK@1qg z7enC4ug#yxW&FXvcntOs9CQe9-#xfz9?ZzUFdpO_ z<1q}*3ktD3OJa+26VOie;G%I%9(V<*>jtP%IDu8?*vAwaNHi$0-I%z>Q_6wtLIik* z^dR}tN*WEaHcR^exn`3iz@1gpAgQ9WOUbKtx(lYt)sw73HYBl~DTAOJ0$7lmy4Fu6 z5lWYKxh&#_U>s2cXK61%p4Mz!3CSfJN!{Vtv;k6~hZM%cTTmO6%{g#hKGzC-s;m#! zf~UiGCx-Ae7YWr=vLls@h%;9JL!Av6LipXBuI^*~NYM>A8>T7Q3cdxsDctrNRfR1y zjAUx-!yM3u(A$x_B(phDQR@Lxw1xE6qrr(ZgE(yTBaCfi@3uD7+Rpp z4M_fJ%4I*bk^`=l>pGxA_JF(wZlAt706M;!*zH^i*a;&#C{EZ$EKkT4dk8Zmi7g`1 z2z0{m5x}$~M`5?6LNx|L`k-PfS#|^+!{lio35pY&Hg$$mohYO2rzG;uxP@p%19;M;f zQYgB;AvYUzSrkc7^atL#Stzn;n0_>XDNHE^-JpXxwo7=6D5OZUU+01zVR!q;5ePVm zN@u`zHvvctqkP=$gDj-{zZ}`E4dciJkC=Im;Km>~qnSy%z^e~~Y-p>4hYa>}+ijh5 zo}X^ddlZO`Rgx5#XI%dy{VhYuZRc2>Ef}b7FogsVQ1CAL=-hxB4{JV4q2K`P?>;I9 zu?IOHMYm&b|LZ^xR}YNRqpHFJ^%lYD&%r|jMRhSQ9gs-p)`pzycIQyEu`|VQSUK_u(&5L z?Y0eCd0m3Wg6G>0p6^r!f2JP-&>&?Y!VPkeWKVeIS~`VB2t_K5czXh%7Ely2^Z#F*s93@ zmfq_3hTc!n^rG{HFMRbwg3($ak&un|`llYFBa^-$4GK7DXu2beldqKiAvowI-T0+{%JS!q{mwbIneirlh2rH!mk6ZWr(S29DeYN0 zOT*A#D5jx~API5>$Tf{qtYWBmLZ#J7$kw?4Wsi%ZIm%{b5Wz5 zgp*T2{6kUw&8fZ{>7g1|vCfdl%X1-LigSmXQY*naX^fzZpbr}djkf)O0tHOuwpiT) z%w8cPd^P9@m5a&QtbBIc@P6(J7o{Jj&b z(cVZHZV7m^Lw7QUFkBu7g!IDrIY?8LvAX?DfCIyPkq+wbgz8Sv6-lQyD4$@x^PFHj zA}8p|s`RhR!Ct(#kt_8oZ1Je6pG(BCu+5&$Js|obw+HC}-_Ku5achS}y#imrncR|< z%TqHT4@ZO4xIqDfZWXE=wK76c=L>Q}6(9IUMmtX=VUVc-)BJIi)Fjbd{? zMzM15gJ{Y#UC3cJ;KC9lJoI6XT{rp(X06s>Y6Q2_dpKaO{4YV8&;Ar}KFG7X$8ci` zoJB!KAPk=>u-h^4v`U1Y?!0)lX1pRRs5}-7Rv#$^#OWiA*qun(V;F+p~|# z{tMy3@b=CJB$N_kzhAkq!OxGFyo22LDvX}^c=ijiBpxB!{#RX|@G(T#&yk>IK;##? z((rA5;+FF6=i)II$mTY`%N|AY$t%cztGkshMX}uM^7pXtLuuAj_`2PA^>kLPZ8qC?M>zaM*R5SzPxw)$hj z!Wn>$Mvp|x3|rMNWK2IuspnArHwc;kALZ%)xBnY#s3q}r52*O|(UQ;*$;ZL5O*=~1 zXHh%1x^gB{DR?MK>UB^{v$=yqO8A~%Si9f=Y`+I)W@&Q(Zx z$b4vd)Tp8b{xDm70da`QolQsF)}mR6nn;>m4~!1zP)uK4Nj^J}$}v;N$+$un`uXP3 z_kJOZ0eN@p`oY9AYae_gB{JoZjAzkS->)B)CH|Y*4gbqi1zHd1xz!jhIumx~RWXYP z7A{AY}Q4#n#EO_v#ne*`z(-D%n;igwK?DyDb zk83#APv$s18Cvr2I3e$GGV;huxb&wtSpyXNz1{zk^LqCj1f=ft0x$WTqR#Fm-N!+* zNlO25Sd&}Nz{-WXGsl4aEDRVLH5oX9H+ooN1ujO4kymltTNNSd?fSSEWWbu4h%1>J;)G2XX_^r%{Bk-8t_Wou@rff?g)i6P#d@~c-A>90{rr<>6xqY- zAxezGr9g3iyXtf`KWXvwM-R_uY|2pNem00WxLX9>qr(4s*Gk-{sr^j3vM3_-7~ht7 z6D)m;ArFftQem6ec2?&&y_IM$gGt5Rt?u`#-}}ceVmy^a{_Em>oxS#`@ZGbeIJJoT z;(K6oUwim1O0nU_KVlX~AFj%OwWM~5+dc|B;&{k4BXahdO{!8VyjuJc52*G&Sr_$F zIiNVj+XEKyLJ6z*yQ;VN+t=^kx5vol{@8k5;PPxMFvX#cF~Mf$;Y5`<@3CJlO0B)+ zHYxgyAeWPocrTDxl9*+PRYyNZ(b>}unv&JoezA9k(rx-Txt)fhqW1}c!jm_I`QGkV)9sXdO9gD9 zAd~#DpgZ+YpO5>aqWT$PeHI<^loa0cdLCDn0kw&NPDF;ms{U;3+4yqO#Nn#iLu+4u z(k-3Q-@iGydeG)G+MT+Jw=t zQK*5hx(`+4Ve_vso!KV^imSJUXCa)hAI!4^afL&=xzMEG+(PltH^@xOJAb+_^xO&W zvh}?Q(K3c+TLpjAT?nDg`IR?i!Zl8nUG#8!f8pP7_6wwo-Y1sL*u2 zhQq4;w%6RR-3))`b7QM;xNgtIkKTk>ZkFuz%7Ie`Uy~gq^&~#(7?d$vSWX2G)_S-E zC{aXjbSv!8#a!OH8rLir#lMTGhPWLTbPam?<&DKF2VpJWzNrqss9&1HLB7*(Ff{3A zZM_h=o7D0|KMN}pM&$Y|yStjR_64O3>7B&DYzT8e|LtXZ4H6HLOe6gHl1=y2>^)6I zBwT&l^)z#=1EVQkDuWB3$?e2%HKz+p^mFUekyJ#R`(ro>6%*C$@trjrKV=~Lj-n}DV!kk{SIBuJ!B#`$CM|F0Sj=`a z`?yj6abUz_sm}b26Tk)_p39;cg}Q;J+o^$b3@r5_uAO~dyLmKgK}>EILAOz?gi-3C zmq^2G!7Z@E8~+8-mt%2MDvl(ut`TI_jrt=A(+9=LAV&8Pil`A`(ly8l_9X!(gla03 z1lV2+A~gN_!yUk?#u7_Ev4G+F>PrKH`C_+1S-+CmZxRh&-^SktIsn2sFk%h^Siv*E z2_%((PVwVNFs^!Ad_il z7*#E57K*P1Nrv|}f`xa}sW6Zod6+Pg;^DIEAWN~kccLKF0H~b1vI5g+1?d-z?aiwS zG!8zb9mIq^uh#@Cr{MDcva# zam>fQjt`V8G&jbF{{?V znED*i8Lut9Fe;n*F>?R_TnGNojB)M%1ndI(XGsgNd-zNB_5al$#{( zb4&Hi%V#ztz23FmSRPYGTWd;sPr(J9kbFw;C@cO!^~;her^lQ1_sVOnz%KZ_FpwD; zVbQ@Xa>Ez#29h+=>!%cvQ?b8X6^b!9x=vB?v>{c?W6uA99(z<5GsY@<&(*e~)fiAe zm>yrez?L2GE<7XRbJy=h`#r&GFRu|=tk+*@@9|=ZCS*vJ8aj*uYj)dXAApals;2tV za#q9nKhWH7u>-di&j%&EP7|5iR_WpLox;Ybh?zAbMV3-$wyDDDVR_>g7LFB(1r2$5 z((I$#D%*c3O!V2+CC_Gr$lQ?nso=tHLhNka_d(+mIka+k;spWfh-bc_)ThT1 zmXJsO+N*kvew!a&a3%U9Jr5an>fGF}Z=WpsheLwt3g1u9h4((VSv3%wriE1U%tqQ+ ze60IWkw0s9;l#3$dt6Apa{LsnU){5W9y)Qz>^|lP=C*p#R{^fi=rnqzKQ{8R6*8Ei z=n&xBvX6>=RnmWxdWi2WhE+PSLQ{&sC7t;3e5Bzyq}m4lxxvVu)3X&JzJc23m${`m zbm6L~H|*kduR=fRb;KjyMgtQ_aBsto_q}yUQtF(#f019xL;5h`npX+WnS6&Y3vU-MUCjO_T+CAzWE4jLX)zzR!E_vLwmXCho9bCoJZkuJ^ z>SZRtUeBPewVPzqZ3Hud@SvolMrG9F^I$!R%r!}`Y<720PU#uTMRPtDU$Uz}7T* z^8A;CfMdl28A=(T+PhtjALrvfrjso(yePPWwy03HbhiUCQZ#w`!HoaqV)ADMWAz^n zIV^6=vHdmlgmOJGc4>hubu1wCdu3Hr*Y>2G!|9YR9K&b{;0$=H=rB!=yIdfU+w>jF za4rRPL@XraF_@vmkY9L5Q+$2WZ+tnECH~we=yOQG{8fIAg@8{D(oUGS1n(uU@>)HJ z+{}5^SBM*oc9>~#agu!opNJmanHKOqVm60p>C_@^^rqX^EicVBVdA%&q;3+A^PKhf z?JnnrA*Xs><9BdhFY31t>+=!X++qzkBXB{->%Ltz<=uingelWf;Wj_kEjT zt2vor^k&D{U}v5eUdh6;wPNbbrn1BD(_5)ycRx%w)H`rVFbkDlE3j1um)Y2LfYRM6 zp%1;#5tfMHR10&-AJT)-%y0bZGjr-%YeA0#E4**o{h@0DQ~)-Y&)z%>yJTGhkU4Yw z`fsF{nZ`&v3yB#1FsWPvs5TdveaK)I7v?15Q!&>kvpHC^U;xWO*LCrLpu#GOEXJ|z zW4Cp^!*w098j~TjA9_>ha)|)I_LHNAK5<6NyoSObO>0DDWGF>FKgyjeY69~)wPHA$ zZqXw8O<-*eQZIeaUA+G+tQxXNVzbnwMMXi?uD8f#Oe_&{Y~D(+GRDx;ynh3sxGJHu zdQD{gg`A=eJ&Axn;>4-{y}Ir#J!@S1#HdSaov(gzUiXO~neAte4+MP+=zrqjGJAgh^>vwpEqw?PC{pLU z#-R-Yvgv6k_J{(g5ax=dLiBrFFdeJ=$|mPQOv9o2dpT;W2beE7ds1+1KP!j5gsj%$ z{2_Y$r|6PQ6mYN1@4{v(b9buZp8{5%GW6Vlx15VwcPb8U6HVUx?2xTmWm`P+&Ij>& zl|4&wxYr0_B}kOu!kb0%6~nyRobL~5ZTiKtc*XZI>U>DQCFcASpHn~1PVWHAucb&v zw)NGws>DcXEnrs+4=j zun}`nzCD+wgi84y;i#1vIpX0-<;OqSpa^k+y;T(bsob5sVE+6p4msZGQ`^}Z({49z zb8?FR4Vib(gT>d|H_q@Ys_YCe7cS^tzi>SjUh_E%VSah-9oN>mKF|+&vDh#XEq=CE zEGUWVILkikC)iGOYsL%G&B2OyAji2!&ugLSw||1R1PzoGOq2Txc>|`^TOc!82;Tvg z(={yZ`Bw)n>ogXc>7df0@X~5ORd}#zd{JcQYIbFDE^Oh<>GiNJ{+iky3i_|>@?=O? z!(r<g@gRKPG`=f4|93S5+mR+AY@nhF~0! zq0Gw5U~@kq^&gIfUQnKuBly3z zZPQE)RwcEFq{{g2!N}dtOtlXz(8E4u`l!s@qt~{YLV0>zH-q&$AB`1T*#AywAqa@j zKYobdtd^?tB*0NhD`qW7;X0?nM|i(y+-kK>YH7iaN(oM0k%NfYzpM4nHN-+SXoZg) zO>5qE4pj<_U%0PiWbGW`ea#k1tgF?f(^B7;#7QD~9L?3)kbdhykftVcuglY!?K=E7 zZbf7#W!q*?eW5s5)nl$gq7VmNT-Dt&i(gV(QUGE%yNrK4e)~^nUfzk$KR;JS^%Pb# zCFkyaB(3vUJ@c>n)7=bv?_Uu?{c4ENr(W%#_|yus!@VWyb}oe68vZ6BohW+#_Jh-> zO~W_yxsOd-npkTMa#sb-;t2b2I~qOJ_vo5 zVpidpG`Wz@>9EoAow7ko9%~6G4{%yEONd&iK=bP&VMi_nSE zkL&VDP5yl0v@z1V^oJ38tvj{6i0P5tk-xBGvz1I!#V{fEm3^yUjcdKzEVdruy70%z z2!){IgXP$xerBLwwDlG)lfkRQl?<7;hIWVa++dzpzsWL#7%HqSc=eWC4P7mR?yFCa z4c2HBq`#VUG#&N7Tvj{QdPS~730FOn=A^Y=8;q-F28L?jOsP7F8StkE@(UgmjD-vU99)CvaGS8f!eP z1O!bSUo<`?Z1u{oND_(Uhl-X*&;5c}-?}FY+Z=J~@ak4*f%4L&3+OK)k|3(nr0aOP zs;+;d)(KLkRbX)TLr((`ZF)a5Qi&Yj>ERkURi@*_-MW{+R4L#Nwp2RQ(0$6%q*ua) zub2kX56FO5)=a-p-tNMSykVtA&79F+T=VFs^s*RVwP~0il)Vn2iT1$+0WWv zaFd);n{@WogFF$vHI60yXEilOI>Z; zV1$2es5hRjg?vsA(w;%OY@2(xmCl%Z%(*$7T?0<-Rz(k2X<_8?Zh8OD?Je;ZKmvG* z3VH1nDC=#Ibkua``TIP~zykxNmulXRCS}*_e%MWKzX;j9-y5Paf>@Z8sKI_WJazRP z=3sF;aZ|lesciC0+V8gExGQ<%`jjb9q4(Q#z>w>EB1jpp9hCo8{Fdl)g|wXF7{xNK z6{ewsi*u>Pd)rCasH<)VVXw=7VGMPI@}Mbh*21x;XC^v7Imet+{v@z|Ah@t>?iV7p z8t4Z>)qa%I4M|AI4%3vrjooMSvOpJfY6UQ{nEJBz~gKRV`ZWFyng|NbLLdkwc>WdT!J@3Y;@9U($8wW zIo03RMPu{On{Ho$ecSPZ>xUNBuAFZg6aSwl>)&vE@?J>qq0~zckIM;kFE& z7l0&0dA(hW`ST>F6U%O1@D+0Tm8Vjb7h=L#$1C4*F?cm4PB-gXJIS};e#pM7t;~>c zj>$f4GoomABhkO#NMQu(<28dCSuOT8=`30Dw^!G(K2!&nzyjou@^>UVk z{)AyT?rX0L@;AhW7Ds%4d}+!Q-2|7UN3}|d^lqkU>}}VkoUZs-b6vsBx(Zuu9OwME zf-H0I%pOLcqU8Lm7kgG@_n03Z{@@1(VU(y%2L9l}m1y~(8$>x#au$f$e3s{IdTx?x zd-rv#a7AR>O5KAm^zCdbdb577b9uealF@+M>HcTbz2BT{hVNy^^`1fePI`@=Gd|Zo znZa+waoB-#sLr4nT^@GYjByY*L=8k|v%=3rVG5)=0K4WLzJl(T2O!u^k zAi$%cC#av>>iMDybn`y*AL@WVL>i9b_as1UoO$W%m%BXpr?YaUlumyhZlneRojS|k3<^5y8LqV+rJ52zIi z(WzgvoNJUuUFuAG@FU|_n+$1b4p!RH6J{Siz6zwBU=~NI55J|Guab=W)CCA#v2vx^ zU8KTj=|S5un#+9|r>d&NcZ3h9*(7s=d_mG0xwoA~f<-K*51RP)8 z{jR{GqTh*E_}^Ze(zbi95ovW*a>dF3fICKL5+XWeF9nm3QZR0zMdV7{4BN|k`KV~c z&No5^pPXo6M{ywlTu*w6bKqN@r)PoREU?Ia^ocpit31Oyx?M9#&^25VJ9Egh{EPGZ zHtMkOu-RMY(Lo#Pu`(*>Ut@>;r%^~xFuZ12tB?bFP1-z+&l6r#RhnBg~P(o=ML$gJr> zn)t_knQH#7I^FXp4s1X0n`9VGlq~#7z*4#IQ_$f`6I%Cri)=wjIS+|XVus0IB@cXD z(_|R5p-+x%a@{XKG`h~nbzZ2@zUz|M^x+oElU;KdRsrSWi znBZLDlOk4Ev87@<+cey{K6$KibUlb+_AIEj!HYrK)38wQ`=g!qs&8~e`LjW@**Tv; zl|;`&;Z#X4uwM*v1f)WeOT7_C8?)&st={QK-89p6X0>rp{>(}1+C_e00#tHPfq9QK zVHzygVs%bs64JjaJt#FOZ8xIU!T1o;aqO2RO;=jF4c&rP>rZQIgg!&eQ1~btQI|ZkZWSGmM&7F59nFz@}M+KGvL(% zu%ND=m>k8Gzzp;!bo|K=&e8qyS5M+=e2$hcFas6@^=9ffoBUg#jksma>E!`A558k@kLHKBvV(-4J_~P^a@gYXJW3cO59gIx5kXw zOJ$9&19`Sw8mz^CDA-h7^faaa7IdOO_o+BJ8LqgMCidKIBDaHYlw(H<{W!NNUmYI_ zJ>q`(vsC{}5ZAt7T2u4Cx#C$x)NDM&z#q0DWEIFz;0fu*9>y-4_QN)kRX2D8*_t&` ztU%z)XQZyYM7g^&wW)VS0;8)aS@(GH}FE!|XsL=}pd4>-6UK(~&D- z$YJ2R{{Xj#Ezf;aNO4=Nh1TwmyaE?rdXON04 zNzp!=L|P3UPUlS_cISQ&%;KHq5y z2Uu5*$8oPWEJUSH%R@+8%6f3A6oPzb>mr&yV+)ck+hfXcFOU0!w`~1Ux_nx44IRiJZkyMJ=1)2mC*+(YTYDbXa3gGkjS}W5T+4{ zNNWu^uN?@@^?t_Z0+8jA@Pt(eMt@ZTG>qXozIX@gLuu*SNR(8s!haqZO33M z8{~C<)(hQ|EA*V4#)1hyi$+klWDwfwowFz~Zt&D*bZ~lk()dp4#BUVHg!-1KzR-Y} zD!B`ix`IiUZMkR@L+>*UAj~`R&Qc8Z{t`<2EEabd`J({Ep0ELQN|)83d%P`2SW)`@ z8<_qL2Cu@ss_)xdiYsns-8$I00F+w2djR>fEwxiY$o?hwglhZky2#0XctfC1;KS~3 z3gyA*t*5@1f12k~`%PlChtzrVaQV$&8(sW;ZdLle8I|so&j#d?d4>>c5N`(EPj_6p zjc)71`#n-WB}~gpk#pgB(;@Cs875<6|8T~V6y!DvXx?xOvt#KhHInCE{z9!-@IJlh zrC6Z*+79DK@`?IAvWDz39>=QQ-G?ok8MR_w{!y)$W;eRI-&9aNFMOrU+BtrU<;#be zFU`F0gmmfb_=6!GHnl{8QW=3k9QErY8B>i?02}O?He?jqMKp*tt9ZHe!rx;|%bsys zdmHP2#f^~$ArJo$w2W4_&R8zz=zT9!$|Ak!X7w&?=5tJaVzeJ;dkcoX=zbfzwYb7Q zO29oxW}jtGvB@M&UjE-%t6?p`X722*3OyQk4zktB=x=h*a-D{6>fu?W6`h}Z=7Q{! zkz_PHhvV867qj!)b#@0oPdncI;hLzj$DMS6m1)kYJpKHxP20&hFV3MPN0&tIx3Rz9 z`o7t@Ymee(y}lZ&!AaAhNc_q1-ye4+7*n_O(Nw9+YWz~yWzL+yC-iUvhz%-ACITdj z3berq$g;U6g~QC7tmF`3b2{pdYTU7%TZ%1c`(%0GjH^K=YUX56oi&LJi699%+{+9Y zjR98GcBfn3y4HhI)Q~9HPQ4vGiPe>d+bonTUcha+tynBvK`&WpBCX-!XG_A0P^@}@ zJW@O3!N6i)zGdx(T(OK{GcNEQKai#6ku%-}B>@z2wd(36=ivzSU(}R`D2(43Mv(#* zc7m}k?Ku6Y5t3l}&~B8td*p#RuYKTzFc8RVMsmXD(dPU)Grzz}i8;%>tk4MC5y%9I z?-2kDmB{rN-*S{|%4Eerd0MyBPXLm92_SIQ8^Nq0;mvGYJz$4@@7^WxK{5%RV|gX~ zAmFP&hs7bA6#by7>?{E73d5L}ffVr0UdQkSqTwCnO1B7idcU?`73&T3SVF4NcWAoo zpe}p74@7~af-VD1)2szz_e12$AUF@F?N^8XA<}&PUg9$I<}bWIe=Oz&|3Gj-VK+lE z2%5C|*smC}rT!=%!a8iN-85Haig>LQs}X2e-H(w|3)X^HYn`B_xPL3v&n@_I=~Psy zFsYe#jRLUJ52#oyCBFeIIq!NIC+M=s-%f21`n}zeHhz3j7i?<_0i4~wl=c?Y%BCV6 z-7Dp)GU=`#<#sxrUVIAD*7kbm>QcptxMuA`B>27?Bn`34>rRt;=wij~seOW;% zp7SpYvCA$~e=|-qACR=~4e3yM!grq4rC3oae|{D*Q8lb=u zKa(PIz_x7_;YJ%V)##)tw;00Lz-*pK@k>N+A@lJ-~dB zN5?&^{($o+SiHzoplFFHR^PgjuGsM`n8W&dp_3t?Sl>0MMQ_hhngB9SE;7NfI}o?b zq8)tF0F=8Du*CLf!urAcZNP~4gB{YW;y;q}^v#KN;>23iacI6L-)EZ3`Qww5eavyP zQ~t9=Qpy(MXABk844*CTIS~VLb_!||vJSkr)0pUby^;~1{Z^~jm?vrH?*0Nf1x>0% zRP=_EgvFg|gL)`!UsdHtRdwepxzr-z;HJ>)3eo8YYpE6Qb6ay4^RpI@iS#1ICJbC? zADX|qi^U#I^;1$MmP(UnXhh5;4hkb!hGigae}6hxDRQv z-nUiqQh%L{5^m+ddt|wr^!Lzqp=;|1HQytAy^YgU3#;-yL^K6=+L?V)%CGQlEtVSm z?%0-jLaw0U6$8kJT1vXPvutl(ya~tJdQxoysh!OMz1Fu;^=z=X++gD3ib9csi&Ug( zf{8Bbw5{%a;>j^Vr^P5+;V_80P8jGHLY96eVHByo5AR31{~w<0zT&v!GmF27beD4+hUR8Ttd||_aF~QiY zgT9caa(>mDQCRQPuWh%~@2EU#(o$pcPBnh#;xkP8)L3>@PY0%gAj`88 z6SitmJO`Ryoc~yqbu$ZPWlTAT~5AG8%9vX zihN&8{gZ#>Z$d?UgJ>FK6*mK3n7~rQFeVnl>+oVxEWT-Mw5>Nw&1?U-6sq+Df7|yN zYJAJ2x*P992ejj{@4B8ye;MM{Fq!pzfoA)v&nSzkA;mc6LE%}PWnYQX%FQ9UG4ZO@ zLo0K4fvje;9Ou9h$3X6bN?qcCYW73o_Rn&0dv!(+_v~~1LiAFP-1n>w zMrg&pE`}a^NQdbIEZVolT*=8UZx?1HEaiWjV>B}JRhp7q8Dpfxo#ATh$sbAz!VV$Q zwSz0=jO?m3{ThbwXfCrXU za&w-|qs#c>eJMj=nA6WB5SR24cGw2oX)gLiwQx80<-c!lc|W(Vugr4Q`L)y&_@{T_ zcBd?7yAnBc%RNX1{c|||B&Mq8Rr1Rb7cVbN;;G2n6K@SsrK4~K9)cNJQm&So5dUpS z^)jXhGfSELLVIm1()B-xd+)d=pLcCAh>C)UC`Ckw3W!Qms?uVki3lh~YE+~Pi1Zo- z=|V&V6oeqXN9jG%MWmMi0YZXOq$QLjkn-K}cg~)(d-j}n_x)@C-~)NcGtWFTbKf&} zyDq0xSfd4hrUvrWR^i3W9ADQ$&MUD+A*vQu`sy!NoQZeEId5Wy1Vb zXDVtJMD8i;Qz2+1Aj@1;PKB&><|ma}srxm^vr+sq>Qp#kVK=Wn3t2zBY^JV8hWyb~ z8V{B#a;|Vz%}Zkz4}RRA2%mDFI3NG*I1ge-Q4-zu7r4vnbrmnq+Pyj5+u78h@K*Q3JLz-#eic&gCi zJJdSaYLju8)Ra0^b2byFH~Oq}6@TaJ`zf8nHM8eZE}q3nyYo|iTR%S#_{vD@2}r0& zgGU}kF$;WCFdmHwT{r}z>f=_m4))+nbumJ}MZj0OytQyA>W%o)L-1=kJ)#I4 z+S7FEB8`vj+tQv=ZG3ezdv{`aPVTf;p0x$Hno;%T-jg5aeYbe)&CH^YJgJkBqWF)g zD-mG_T#AU-ltxK?E6jKOM84`+E|o~9>BkBSeOujIwNOc|9&}Krf)n6{BjrplWvY@F zqC7o{rtq$5uSUI@I&@9vXFs+3`4&tJFACYJ?AnasV-0lXTFQvrur`kp+J;F|$0?1{ zl+?5?l(WaPy)gaC{$0+?uWM5-8?9S^QVN_oKXhI6YUn|@lPK3XLt$>sTx)21?Fq%g z{qdN$N=uAeZp69E*?B)<%EzErdXW?-^5W_U0Uw?u}Le%!lq9EsaPPJyHNZo4BErr)n9sUXq?GIM6Uh= zDWQ#zbU`1J#S>eLr*(>~mK`J_EcY%9UkzSn(c2>VLedc%4OG&>i*+m4MXe+{vC%CG z&Vn_2KHJKH=Tk%60z5sPrH!hFo4?+UndG~BML46U9&rlU+F({f#0%1ry1Xn6vZJa_ zDaQyDxDan}>aJpWcrUChsD-pwCEqT?frDB*~~KeeThcKuZx~XieCtw+?N}2gO9D--*s+1o-lJCXBq37Prl$6G~@gf zrolN8Fj7>Q6I+`b3wy$+1rKdT=?b=3dbcDR)jy^$mnWIwbCN41))iPG4TV<6msqU*i1P*}Nfnd&SM(k!#ve;nRMt{a<0%xgi1YHp4?M z*i)>}<+S2ni@L_}UK{zv4y^C+bjO`9A99uBDNg>E8i)At3;0Z_3BH{^Bb)o9&QbT@ zd55+C*m|>tk}OBnh6Yaci$<5@Rk~EoYPK+`oK7Pl&AEUuq_ycQg^z7% z+gyQw7P0WpIoQ>0ksz;tF&>tE<_Qv{>TY`sS6AD(k7&JeY|fN=D0(~M>BqLB#EDC? zA$n(w4o2{QcbQeJAoZ90`ZI{1nEcSxUfd_}>;d-Y2So8QBS@g3B;XzKthnQg+FRF; zJEYWc!QwSfj;ebF5yYD|mEv?Z?PJOBMyne68Fj;E781KBm1#ps(9TS$meZX7s_nnb zaA%o^`%qcy8P5@$!saMe53-TsM)SsOb?e6BSXgr}S4uX^- zPmRD|&Q-N)xv&5xM;2O?Hgp7J(Q3*%!TIN7Ez~vl>}ZINz9aW_-DDW;O4a1s^kuHy zlJlW|tOZ%@wmF18KSQ!+sz6Z=eCkhwHKR4iUbXK?`^F39*4q@bu%|8@m>$-Yj>CkoBa~K zCCAjqPsRsnx8#2)PRJEF#|H~)>K=a2jeCFPEGYTbV+tgHcylRVm#D~EtS|Xu5%%jX zAK}}|6z{Tb-C18oA7u}FBpYCMe? z&@*I(RUK8pK>@VsKM=afo23Vy1C#S}Y~$L6v+Ifu&W^X=#-kGw<;rZrej53|wMXDW zK}xtos@UQ0SLumlI)+mGW$YQokA-p@*oy|K@{4p6gv(^;7E^P=j-@HSB?yky4si!Q zI%YZ8z314E7`S!Ew6Z!~?hUYlGjDztGz#O)=x@5+GF!!!{#?0a{az_Ra30*d|Ha~6 zc(Oy+(mVn94l|?1-#_p4YS9r&gWq6IT@T>SsNj7Cv`8nky;j)H)|{0Fsy-^&-pX@) zoVtLaxLiJT^$}Ta51chKqv+Z19mR`qDvT=FwCMYna>CkU&_7}<@=oy=t34K`JWO`? z%BvVegrWV8(z%zOl}zKS5B`GpFfKwkt_1^KWpQDH^}S$I{z~=4V-rO0m6e^MQR%KX z>lcnn02ICop7p^)xF*lTS0iB?Dl{Vq?h9%fiIEK8p*rHgJyg*)P-3_EC>Y7#?dyy?VzYR3n4Q#Hm}lFg`j ziwgOce!(9V<*0sx6NWJImUuja=U44_)U5Z84c6}VM_g!+a?)hzPt1{d5Ey?r#a|lE z?0O@Qn9nsw7cQG9hKOQs4nOnmJ^5O|%t)e%_h`(i3#QPQ5YxRX@9xLnju^kv*OGtz zEe9l8L+*0^B`DwjE$_c%@kvQ`3N%#DMg$2&1h_sfQMY$n_ju$?jBixXE|2)STxV8& z9&jy)F_Wr1_55gCngigqgl zUp!-$G47zYM!xM?03U|O4q!uU4t)u=2$dXnB(cJS3USVY4XYG#ssZfYCS&(Ii*HLr z5N5_-1X9u+!!mEHyG#n$&UWnwmYbO|Qt zzRr*%%AHuT<;RflQQk`iGZ~$$(Ia@P*ADV01s7hEE+eS^8KlIeP;yDG8kkdb2 zVT*&XTtpuJ6)OB_%N741V=|wpScrVvh4cs2VOMXfs2n1?dL;c~;=kkZ%*8&lCP&f} z5Z|S701}_IcB^4VuT+qf0ZgAR-pxF#5-_X3mim(gOBCF|miVU3S&6V{5JMYn9Y89X@+sTg5(kHp(xcEi`5zmzM9rV?F8)H=Niw?VAk_p z(piI{r^6Nw=laCmO1&9n!x{{@OmKMqrR{7NaifQQs*We_R@>~A(RH`gCKVzNum9|Y zqp2df%6^ozXX(OJH$Sm#EA=al0Vo9+U3jH-5ayV{p-YBFDRtM?HG;Fig(@q5P=SB{ zZUD}+k`)*{fTvBpRNjWYX^@M-wYJ}@ZAnx&_RtvnoW_2l^YnE=`JBf|DeU`wid8*2 z4RqiwWVVr}YtPlM<=^tgklyybh&zovM(R1OvtU1c+GF`WwM3MccFU0WZUOpdwSJ#lkOPjW(~Ct*Zn+ILEO!uXt^aIMGl^t>sX z*!3ktwLQ9WUHzD)k zhnQ(+qihB@Mmhs?YZ_#$rp6L7)IE2vs5K!kpWP?uD39r<$F9$r;yfnXZ@`Z-ZjLxM z)T_)Yj^>S`ed4UO0!|2W@5_zyWcH683bM<}I*PwV`wVhnY3&xIn)E`6g~}XhkHTB0 zlyM=y+rntLMygv^komQ_jRa%=b|U{kBw^@jSZ{BAkb%*3PDmE+=h^okLN{d@ukGH_ ztwgvbkl$e353O-cFc&NPVW@6h^>KoGV&2x9ej0vZL)PygOPTmZouA~6qaXl1yTQIa zGg9?WbdXm*_c=Fx6KAVyxEG}(cN!;AuDM`0Pv>}f^!&zypp8SWEvM;9Lqljr&}r^c zZj1va*5;Nssha^m-xa|!Ku!ChL&?y1H+2;>%}zIEHYhG5a8qCRh*YA$_ovAjOu@rQ z+A*~1h^C0k9{$_efyof9y#-WhIZrn*ZETh;p0`!;{J^3AQ zGK*ELBGfK!A;?Po)2so@jFy7W_*+CFpNEPb$G`$}-U}X*sk)q7sb*a%k0?ujI{eHJ zq_M6PlXj+CL}<0d4|3amS_r~fSE)EInzUrvQ1YV*a?ho+x*sisna($HBSNcef&gL2 zgZzZm!?;F}(c9)u_u+QfdpM`BACFP0$k#VG2mAgzRO{Mqye$dSHAaSYI0s)5h>Fyfyb3ydeSjWjM9yCJp?3g5J{L7G2VD5ldkTZha2HyDP7Z)X|Z?aKtYvWmEFyjs6 zk-r)-HJWi_e&A@o(!=}_^&6f=hpG;(rG}KF+-=S_ZE3QWMa1ZM$qj&MSpwqymqylv~rKc(>9~F+Hh_e_+qQD73vbQ?l6Cr z#mDqTheJ9t1DZR1D``Ao>LPP}QmuPOI73Hvb&=;Igkfyc^r7B%u83%P)Dx>)J@~9D|c1P;mZ6CW@!Y`$*{I);n!NZLmut{0;uioz zFdJ5?ooY?jdon<

bzf7M^)W#a_6mRPME;8;_edA>@g<3^t`J)NG$ppC*aBKMVGMPW>{>aZ1 z7xZGlcXi~IAtG=Q2?ciub675wh4Rcy`+K#g-K#9Mn8}YSX)Us1hU;1BHr1I^pGQ-_ zSYPFCf&gTFtxihI z0g^A8C_M2i4STOPC@NOgrtJ+ur>J=LEV(O#5Eo5osu@2Ypt(GA-X zn8GFeDQ^(h=dx@;+ex(LX1cK=g4|9{{FwikeLM8}uAk8Xxj2;2YA^>OZd0-Of$7-6 zRGCFeXtLWv-~Jni74)|l(HJV0>Sr!ja!WFtCCE6g&m|TAV)1TM#XAvMoO4}s=cj0v zMbhu-BC(z=)ewA>+BS-pwQqpT{s5b&DV}<7xVleM17=)NQ>z{DT8qM_GJAWs|JUhH zs1FWBI186|fEU$0N*U#+TtJ9>>{DW^lsikUJ+@sP;$d*|!A#4&h`^CiFy3-H@Aa(kxVO*vj@D4xhEc4N{*mbb zXZ2QlX}ctMqx_AgMv6m&$R(T+xwf-=++A^{k(;NT*TC+HhnQ2}@6oy~M-$k$=;ByV zk-DI(W@;!xv+Lv_xPSHt3X+UfdRTn{4Zx^a@`@@ngYN9~o@IQbTp96OYrQhd3_p}1X+jJb_5@KwL0jPOnKvDd28IXno?(pZpoK`arfD~--<5j%QEba zT)BP>=*61P=%vhqqNiKnN0294&wtj;3*l^Brg&bLNNlUS*tnpzOB3FDC@P5EVEC0Y z1TkYb6!ZlO3U|Xa;$W%JWIh}${DnS-#bKsyOlpAg2Ln))S=kC?r{vv=(Q?T&R{<*s zJ!=y&l#v?`h56A~cQ%4OvaPIWsW-`GGt;C_LSqnKermRFJ=6Q6-Tvb9*2!0j*i2&v z?m&76L)DfA1#F>icJ8PkBKM1}+aogbPZoa9rTNUN2IrlXK?VL8K{&e!h?p5jn!W)g zAU#AOZ?%V2zXB?<;g_y0oMiq;J@UHN>_P4Ga%fu%7ilrJMQq8k;I+CMx;1ygMgC7} zT6wKUz!BYlAOO@yt15QGHCu6b10kavy57hQqga#1FztPS+jhpbTe-GWxiP480yeq9 zJus!dGzndpdcc{3hJci7KmksKM+G~Ut~8j%6`lVakgb~tj~=V(*l6Ucdvj%1cs(h0 zLGv`jGn@#Gk=vf|wu%!rsbmdRHl^4js;qVgj``jAX%#DQU2pcIx*cEC95f>B52>5G zvj*jpZ{?iiOsV)Fc5Y^d@X=DEJn3TMH=jq(LrS0o44^FxuLyUgL$g~uFiv~0v%78i zqI{P_Pt<|}=|=PFronB8!Q7^=x{2#+AI?0kADb1Uf87#`1X$(l^C8{QO;-p(_#;a& zSF8^;?W7`Z%D^!^J zP_hovM!FokF>JC*-=))jQ0dwi=D*1AwgnJJdDTDIXoCDt@~RFfxk9zu>~lHMO7?5F zY@(w8?oJ3MS~~p1|)P?uKbE+0VOYQ(|S2j z*kZzRs#RWX1Ca?(XUwT88*U7VK%Ne{qth#2mQu~wj(*PjQ-l4wag{ksN<#dSeE0I2 zD|AY#T~FYBLrHb=LRs-ZzJ;Zg)J)^+hM=b(DRS#MemBBsLXYbEUchIC&w)RiI9jY%J3LPNTkdi392G8- z7<)RV#N{gdv87dRuSYEYxNB+%%PTu{#S!nZZa!uCq$ABO``enh6z8Ftetp@YFhj4{ z-Gvo`ZJm#e8TZk{)dt6{B9d&P;DAsd-MY?_NXC2h_cru-gbrJcw@u5yDi;Kd%;8C&t3}B>$7G_@h)l7HXzicL#zHhIgvp@%2ZM&x|MhrcN35H?_^r%cOQfBll!u zLey2^(G2~P^AVmdPbbiVd%O8jh=E@~GlAT=@*>;jd2~dIEo^i5azPzJkwrOwbB6s%(xU!A)RGr=oULNzEpyMEo|KOGSR-c~ANl-+5dv#?oM+(wRVawenKs zcJC(=FpHmzVQE%d0FLewq;YP8Z-8iJF}0yJswj-})=)>graV^Y`G^0(}L(t~#0 z(=~l|hnvl9N0e|L5m<_V;E1(<0{6w7+7@VNP+rt;588=;AgYkvjqZkR3H+sJY$z{BH&kBYopFD;2(Hs^rJgj2j@y#n`Q2 z4C>+i=lQ>}dUO!0zvr$Y7OSH&0PNoPKBf~pw(!3=82{hOhVN_!0TarVkTKgZHG_#1 zyfyymiGfagD)yB6=BGuk>h#V!dfHCf#fN66biGlG^PtP zjzPEXs$Zi`$^=RLt0C{uZuov32H1eP5p(;%uXYi}aS_dv{!Ss%$D1Q#uNl3_>Ec** zB;EC4Q9G4|WN9lJ{|HYOg>tOgkW#Q7N!fn}*XS+q7$2qX5kdX}XS$UC z!FHC0Ri`h0ryC^Af>PJwovDAlOIIUZlBUyf=t(2C$xEh|K{uQixw9ZgUvU0pZcOT) zs|{lvdnjzk=^rX`SzcW3T7bmId3asEPOlqux_@lDoE3ZRR`TIk%L@gM{(|p5^yR#e z9d{rs_Cj{+BLE8WT?>-Eis0l&U*zX|*3?Wu1`l2xk;CY~3&^%+m zRSnlZ66)>M9*?V^2yuiqeVM%_3)_eaq#pUVe7TDN@)yGQl5H6H%`ff)PISom3sb;* zesGP?jqlF|Hsx4@unaATic=mtUzO3mC-GU7Jb)PnGUiW!FMunL0`Cuj7G7On`+M)u zvi8v51_ATk*?kF8UFr|$ca~WD$sBRsYk`Lk4LgQO>2A}etm4q}5`LQI_Q*i@w{LR- zx66s`M8hZ_*eP4uy^#l2$YT~(A>|AGS93M*1{iN?&U|k1*;Q!#M0}~r;uhN1RNDqS z{g@2z2xwC9$oY{XZWHJBVC_QiLQSGrx;Lm{2$C;3?)S|7;3WiCur4-OJNR*kO4PZU zVMU8|8JYXSBQk5Fg9=gH0^f@2_De-QTL*gKq~dGNO{Um z%r3nCQ+Fo5thEyoNb7{K*=i9G@u{wJJp8V+lEY5+M~G4*v|)qmY6mCGAhcQad-HGX>cM2!g{@+5o08zDsGOSL-& z*%4|HGLZ9w7nNgu_#iXc@!4Rm_|mmx_#hv0oVLMC30E0cJt)lD59ajWr_Ct`0-&3@ zr!E{ipc)G?$bQ3V3AheB!7cag6swHb^s2y&{;Qr^Xm5Mp;_cDT>M=68w`fKDi|~r7 zeRzs1&6-1)q4F3Mw(f(m`fL{8C^=O@53i(s0#IhpN*(JF)ceYIdz`>jcDOnvH`z|O z55vPbmme`hd-x%NZvVBS`v14j_k9)FwvcPiVl+8h+GFw!6J~l_+EDm|_agaHT%Ejk z!O22Exm>VOKG1yVn5zD%CbxQ?TMt{5NeLPAC@z;4=-684_{SnEjj|4%hRbIXmvoGR zj&b{=zrmq@phta1X;t#hiO0(=uV*SPZf17pWsQTRG(d1XdL{l0XoG)8Fcz=Ea7p zv289~+)BuUdleD zvfUk!+rHNGd>6nG`rX_JB1aUCKRi^J}(SfErwtBgbiepvZ8Q!=q3ZvWl zN4iPQ+1BShjNj{ywUs-vJeW{1;Lp0pF?0%K+P}n>&=6X+q=kt5Wr4Vg;kGHMhvq}P zHwzgr(T7RWTdX0dfScyoVtr^kifi|a|07)xlXJDbI;s4*3b}JB=c&zM349&;^98o; z|4oWgK4V)1`N4ctM9Wde4C~FGc_vsbctg$MmXK{uDKIsJ zpkVzm88f;TjTZ!L9CbevLxOC-jyRqH0B6V7zlWr!iPMJ2KB~b78W9Dtr8--kQlD)^ zm$m#$p~9MC0L7&HTYLy?B`R7m6>#5`o0WHy3%YbR7{|#kz`PePMFFVyPQe@AfouHp zhV+}RUs36E1szZt59I+u>JK=uIJ@0Zp28nsiT^_Q4j4Y(jr$=RwnX_M)rugL%EEv% zU%;GLxTU$l(31gy)1EXW>(t@~qVpr|>n3x9mXAfEHwqiLa`1&{Hc(p79L0*^0B~j! z$bTTG0W2~Fx(7Nbz{C8}4eA<iZRy&W&kY$7>J+B?TBaX%`!yJScgwh}292~>vLLOS3=GcE;6qK&oh@S7HQ2U5V38mFj(>XF+uT_N znjCTM{20n^*2&9a<#H!$B-;LXF@4wD=D^T~qWK9Dmbme{MP~W9OXzW|%#PE!N2$EB z_W{5Audm9Y%9KY^)u&_a1pYrl3LcclUL^Anq9R)^`iE?LoLDw?@wFOvqS}jxXQbXt0J$Q)Guu{^6N=TX>&nYOvfhftX4 z<+`%IljBTI@2*iE*2Iu*b$nGlvOc)uCcCGwjrZb0(TRn~SiBHo={1@qAb|J_KVIFU zfEYRqXGlx`MeBwDf`JFdV3o*`6`&NZYJhfMe1opHAsuYD6hH>_fzTqzq#fEV!VsVq1(M-q#2&R~lELC- zm_n&iDnL~7fb0~X20C@r_svP21)^&s0k<256;!eKJ9iKCuR2+Xm`#KnBtiK(`6`vG|Sr+bJ=yEkkt) z0OtU{%4rOaJySrv^{JEI{sXB4wJ8x0L!c2nb3r@TXc;ux%>^XQwd}Mb>?rFz5BjzH zr3P9FCBB;xJ<9{7@_8|xz@sWD2}|m{bL5@Tr!cw(1Vk#igR;F)K;0eJ!M6GFg;QDv zqZr>|$6-)TYZE9bi8UVn@FOaP{a2 z(9KgY!j~#`fT%9nVaPat9$WxZbXagr2HzIS0g~H4VrXmZ9k8leCTAuGltKoRD4NA3 zM)^)6EYG2#;N{gMfdfS!^b1KC*gMV$)@%_$ur8JG@P>{+x2640B5)QM+76WTmNmk` zvPbRv2ZEo((3p^iU>18w6M&&qr8}JUdlB>wOkLy;5|(**RuyW)*3IfO#nfV)!$I%5 zU-2-jfH8v{RKic$)kdlxgsp*bWQ4v1Pmk;)bu&3NuR?8(fTAgH6lro+5TMlScL8}4 zhcRq!D}kACLj0_q1NN$$BaoL-3=ozoER!n}ygDWc-1P^@ov<2M65AL3@qlhF3K&i~ z|8WIJ9)i(Tk#qzFq*Z_R`i7uxB3K$al)p<2;Px-R1eHm@Kz9OPf;Ao4PeOyyQUJAU zSN~RZA=(}N%~=s}VD-*@0&v{_>t2#_y(u$ z&4ZyaW@6|Dh{4)9IXcr5l=c2b);4cRNq8vPj9nQ?hELo9w=9Rj^H?j_cd(^K6`m1y;F6S2fU5BBQnWsTV z4^}R}b_T z?(SaB0(f~pV1$aQqqFR^m!WCkTrmHhO!Syi^dUKh88SQ1^W}8ltKf(OG_WeBZ9Q1$ z0ntm%y$BouAbQ=}CXxT%FhaLqdW7iFq{RVg|9d6*sXvMW;(?!`tvamv6MxlFC$!2C z!R{|b8g~Fva$z42?U{@uE2g%ia+@>)FyT?8i9mCYNW=-lk#;cDonx&;)RZ9RmHJ7< zx(bk3VIV56b0|QNKJ&B|u;wcfyQ?nzeFKzV6D=A+h|^+t>6f9kw|900ob>^ zqq^ZDn5B1r`J%W$boG}iqy)5L#={r_TLie{E|jIF2dL^CwTh{1(p4}W2-vG0> z2MFmOV={Cefe4$DWC?Qp?Mn_32K3h6xrYDq0tC^nKLc(-@bQe?4hPf`&OB=hzOaS( zVBpkimB9cql98wmOgw7)cGUnwCL8qM5=O8Oc7tvG=LLu%%{gcYJ2%h){AYUAFuKoO zSXCdM$%~I=XU!Qwe|zeKmr$EGXCA{DGGxH`()#2W()f79_AMYQq)ITj^L!9M3QRm- zdmPEd;3W%r{R5E`y*TEtJpL6C>ha#dtFa!B}$3hx@&$MdBFdLV4SYw zec$wtP%74cH`IJm)F?RFl_h}cLhhXfPmPF2frKB^@=%3f_(zdCxUPz1uBRaDZY&OtZzH}7Pf(-Z;4Bk)$% z8QF6NFnG-|wR+gOkom88nt%aVw=b1~H2(L|saqb<*dJj|vd+ zhtJ%qKG77fUrm*F7xffMrfX|xRvirKq`Xo%vVRRI;CL06XNT*e?S5Tbxw5Uf^V7>F zZt%GFHuO(6i_F>)iIHhW{BA@9G1H8(&)rH@YZ+T01=#MB8>|&dMV9TKiDf^&&+`g^ zUPH~mEpJpsUhKHVbOnHgX4^SJN7!1``&6iiNlWWRj+5-nyRaFc9Sao=V!l5T{P;^q zW=cGd?fNcaeg-jy+<;nMhCI4A;q_5seUV<3A+Otk{tG%Wtej@ZPesKNPKI(A21*!se<(ExMW{-yNu}qMs?EYKM>yp{~7AQ z9?Rea*F`BhcSkgh$ z8Eg-NyW5}r)WS|sJ3`kLhLu=kYWDmBpV)fW>OF0X#@j9LV|&m2)}GjkPNJ_d|J?k} zo8);poZh4M1JQWzOpd#|gu9@K82pZNSRw&>+M5d+rJ5N*cGZVI9^Y;@`!QhMP$P_} z@?L5DQ1(00o&}TZvv}Y)y;-t+i6l2wvO3J*ZLA6o5KOB7;gqh1SQ=an9JxhY7Ow^5MRwhm7Ju_>tG*)P?-jhJ%aIXc^( z!!Q|K4;6M9K9*<;zlzdr^w_ft#GKi|$#yN~L4qQkU77?BdmZl;F6sIvU-2nS<3`c@ zoh+V{%=}}t3Z4VON>({;g7Hd;u7#`8@9-tcN!q z7g%=M+j*SodjMh@XIxXC=Kr}a*7=OvyIl8^T{F+Wd0$<1 z@p}ydm49im0s?nwr&+*qBea z`a`TotmlPxhgO1$TSuVh65G<)f#1w>l&j3xSclp85cjZw!t2|jxtS33)lp6LC6IKe zE=de=__3l#8gJb1AzO3}E@kjem)cc(ni1QIt?c*W}E@BO8=5UlAsM zgH04=8D$uYweqFmhNhp1bR5x-m~{Pya!<{bz%%S+#47TSy%Rjex*-5ywt>!MRK z1N$mj2b)d}WpNDsFZ`tclMnUZ{HI&!IQ#)co;S9q>PD2FDpO~zxD0If1B{?5uc1WX zWBL9V5dFuhv}hF@O&;^Jcfa_@`h(|skU11!fS$i0 zbB0rFqL)>!03LhFAn|*)!dcv>9!X!#_pbvyIuJ0T##r}D@5W{CMyMQ1@>B#mwPio+ zq#eFqrIx%KM+J&IB+z0`KJgW((2eIz- zkpF>%m2GQ0XbZH*M0z8R<$smvc+g8s?|B+FOE`Ad(WyrD?l0-?eTwv=t?aC_jfgZ> z3ECyC>^*f#xt`#5NwEfLG~`XGi3~!cenub=%G>ekn7>E*${X1lC+?4CwzUP-DXzq8 zKNP(3&`LK<51Az|Uyhi4B(Vp7(uDTt z8izjJZ33jgEBIhz!pwsXGcwM?|J_!*c+Kwm_V+XIZeq6jf`9ouV2%I8hA%<6yw%_k z8kZ@nm+lvxH}*`twqZ>6)PfUrIv^?F8J&?kQ4yTDdgoveELI)hjn7FTd7W(y*GpML z9JqO>2R~R8d~-eKmFtftLGn2Sm%oB(lqtC-S+gX&M>?>q{4>BB&#Lp?dOD4p0XurY z=J{(gMK$dDl&q1tkj zn-hu-ltDC}yQlUQ5ph!D;Bar8HzaZPLh$D1?t2ocMN7M+UNqnRdV z8=}71B`pJ)%Cmkg3oL~p7uvvetP-4$cD~EWP07Y=#1Jzo5i4UhuUms(xQ<#mr{iZh ztGB6wXQ?L5+2?_P6^{sR`Na~qophl0ib~{Kim;=(#fRcm-&qE84+3+FwoAda`fl0G z;+!}CtfwLz>SHggi_xXmXJ6dCd#`awcv0tfc%tP{>n$q5n2^Wp#9mu`D~ zX<5Egq4=uP&@o0j$B($-dSuM0^5kMmJRELV>=fRbRvhqdnL?Uwa-m>WYJR>u->DCB zD7=1c&Xxd1ZIeMCr(mhi?Q#?4<+4gpn(hvL!W~hC@JFgley*l3)A74bgG_LC+-6&o zR;Z01TD;U!#+<*??NWyiAJ;?Van9b;A=mysx%1gbRv6l7Ys^E2Zk#$Id#U^&v2IH9 zArm&WH)51Box6UEio;HYf0V!V_|ryy%Hg8NRljKktz9+NKR%&3K0>uCN7}8 zx9k%y`Ta{sqr3;fggf(Rrwg7Tjhqr@#mJ@uN1v4kORof89aoAt;B#0gc}OY#*!T7p zB`esn?XFeFMr8K@f>Z@6VD`@KAy|B7fGpxY!-AQm!x(f0%goECfM!1EZJLHsN4-H+ z9s388?p+O|YEA-UH!Ynd2<=Agsdxihk_fEoXmr+vES^Kid5*4jDp+aD=1ly_2`C9z zHxb?B99I117I5~g0X-Xw_60oQ*BMV}b+Cn(F2KSL0aA=K4JU^aqk%Ecg$5kW-bj`n z+7WyY!4d%O2>9MvTS;I~j>iF4akUiVF?lp3l!Mkt8r^=tK$na!W$~OwB#0^?H-dQp z!|K7sJ08CSOi`4V;dS@t;5aA#9(p@AT>Bw0+byJwi-15;|=~ z8uEwf)Q!GH;G70KV1j&XTW$kyRvDLMC25Xx=r|0QFfNEe)AXYKwt8-{MxPRYM!ada(WHDmg-n(+EhVsjR4WF8h@kts|% znm!kILh=;q&CY>LU*wpKj0nra@uREe2IXM``PVZ%pXM1_RM+ykh$NjF@8NB9+bquq z6eUyJBM`EMhqEvrN8k}@PLom^&ZigR}I;nzD(;l?d6vQy+~1Y_{jH4 zbo2LCHM=PPbu(YPjKVk1J;RFv%3HISR8Ex_lb1KPsf&S>^oxjyb(m;ZqQ?o{u&{@b~U+8THY(IX_b_n^i+M z`6Igg$WOJnxJQ=A1ZytU7mIAHuoZyB(hpqFh= zktAFDRN6FU-E_=`%sLf|tPF zVcDC0p*&)M(vnSo>Rb1IW2?4O@6@^4fohGFAXxrr2{fVln(K{A3Rm&z;m>iAHZ`}FoU3)zKJ{?YJ$ z(ubzO&)=h8_IqxHthY36bRs7Q+TnaY!Xi&R{}1xsJF2O#YZpcRDJlYj6e$t~1e7LS zDT#`JiWsqgf)FW6Cn_BR+Xe^-5fKm(q99Ev(tCnbQIXzD=)EMAB#>l#7wT0Dgy!XK7lYrumm_O1q#7OQpO6sh* za9GejeJX!X54q`mJWJa9n?(_-pGG6!FHk!K^zJ0gW{|c|H2IsHnrC!fNp;M`MDotN zL(b_`mU_-~6_uOres8tvH-PsXWZ{2YNc&X=%D8Wg!@(I>+V=PmO)L~;T+VRDs=H1S z)W1Hq>&rRn9(?mX$4ZlAxUGim9PxC!m+|J}r)q=B@Ofj@Nlf(TSYeMBoGVYXqu+DP z%4PHNGCHqP1sij?vlA_^5(D+-u80B5D?60dVI$uD9DA8X<8<@Q#oJ}RYY96svi@)LP4w~Xp? zc3L@DGH-gDHj7IS4(c{}lyv{8@jju8ef&k|J!wo?*a^5?Hr?vwV`arL&uq%V1EY|u zY8RFhLmwV)+cPY9p!AVtuxgn}lVhvL+}(d5v2~rmpcRAeD;8#HoF{e=OUvR${hJP4 zS0~elT+T{F7(4*V4BAk*dVt#v;|k+nVzFd__75R~zejNPltu#d<<2lh;gHjxxl{Mh z!zRmrIg~D5zz~;$uTDSF373D)@S=tqF+OF~1$?aY@NhX^@w+$Z4)dgrXT%?7?!>E? zzjh>>xcZik^My zGmo~iV<~hq%7GctnP(b_)iTrj{9pJbJ5C|iTPEVCurw#VR}TVuL@tW7)C1>q=Jm$s zpB$%;d%VDG&Ibm)

vhbK{K1i2kfz);as3i+lX`oYKCa$64&rSNr{2h)Vp7*n8|U z{_>R&v63aumoXq4qR#!%zrJb-X8K>Bd^SjY5VL{+5aMq6Lv~X2LHzByc6LVyFFjqj zA9u@KsuvBe_?>J!`IV#Q)iVzZiebLKxJpXH0cSqR$n#(1W;w)nT-|j9U0{e>mojn% zHP-^Soes#7SowcB%uvJ?_CQu(>VUIqzwsl9g+!4m##N|0-{?HF5^uxk9(qUVldwSm zH_H?3|HJ3ayP`59Tb@$)ZE>SDV|g6b!mix1G`0*pg`PZjK+S~tE6jFNl{{g#XW^l} zU1GV>ujJ^woKpv4K+5))8k-Jp2yq>}>-5k%FvKZ4Me87o2D=8Z@HAo(`-X8rw|dxm zW&2ED3nKmz0i;44;N50*fk`;RrnBFh%lL9xAOrjgnl&+a)~NIqkUyMYrF1E(kQzEVi}#-DFQ_Y2H@ziKNzl-NS>~V(_dM zUzuF!*-976*VP-XK2+Q5|7y!H)+YT|<`3D*H-GwVU9!#|{dM9l)^5Qv5ctUo{t&V2 z`6mz&{^dApp&)d^g5lrz>t>h6Y1_d7Xo{VFm(6|>%zFAo;s_&P4BBV%$mf%-E`iTAi1recaVQYcIzXw0$|GhhvV+$ z!1=_L&NhHQTyA}3PyJLr*3xhV%1Zop~M9BaeStkzl@wDlRfT z*lG@UgCY@BaKI9-m5Fx7P+lYU;gr829D8whvilQ&zfEuv3wV+2L|PSs1`VL)-cG@) zt9`Bj8@{t$cKjdJ8SW6d>NI)}fT{b!dG!I@>WjzEtfHLJ&qvw)MbN=o)Cf>yfF>iY z2T0yTh@HN1yNY)l#7IP~B%^-MJpM~Ie)55Rin%$mmyScU8@43YKjv+_(m=6Xg22C3 z&i#M>;d}zOlLcIhNF0oqI?}!FVx3L z5$Yn<`)@nN!;;+!>-4HOwRJy@o{b!zc1jrLI|LlYrx5pJND#^4Jfiu$A*HjP-)K+P zBZ&;2_$dYX<1EvZb2C?wKEVf6N?J(tVJFr(HsQv&?uc{%;gIu|V#dq$uKcc|1zu_C z+?TVTe@$(E;_!GI*{0?3%YJ8QJweEMM43ve%L#V+rSRXbYAO|zA^u-d2C;LEy&)g{g_XC3jd?fl&fMXfnWq} z@rK*=m~0LC`5(`H^*WH?{{4`Sh+wI>`v~&xqoT(y;ndc}PM3)X)cvL5#Q{3pmZlNr zZb}+2Eh2uflJaC4?#N(<((U~Aw{d3O0lOo!j5D2fUF?z!?-)7Jdoz~Lb!Ox)kW+S7 zs6^hoY3IMug6%zHleVBaBGZ*S-D6kQE>jw!V~QgCIG*?I=0!2@hgW@P z-%`&?V8(Zg#xL=X-Im?_*r_hY>@#N;R<-F@!VD$U=&f?9zF|cV>zJQ|-F?k8A0;6j z$K{Ux!)wo_D!%p4@4p0(Wuqc`=p#pZbAN`(Sq+ zp;VXDX0WAcq6>BuT~Nnn=V>+PBcY_>g$13;m!1nwAmeiTW0Dk?Wdm<@wx=vKl z!N!X85Op2B6X$Q=zL;#)GLh?jclhK+|JtSsp}dMTqI0MnXZI0A-OlFi>ms9R|wC;Szhwx`K`} z98BAFccjm+aJ2;beEH#)Q}{4?vE)+PnRg$RmR6-!BdoNZ;Um^t%(i|t9BKd&7`veJ zT2bk$P3VV9PNgfjcS9gZhajfku zO|`9j!vpi$7+N}hPrO$y&`db!`uY8|0rT=@Qq@Q%eieGOkisa=ToAX@r*GUm8)%+jeF6iXOIohZQ? zCH`{gBg@qHF|}55E3GMcVOS)B;c=xsM;#dMy{@(-EB)VLRWJHUu3;=Ed>I)^zp-M5 zt|vd~LCR7`qVp_$jcP46HGAWFNtBp7IkokxMXoU^hx;nIUj_Tf{+yZq$ZJR;MY`h5 zI?_rB4u0+zf$FoELuq+L+gsQBfk%9#ATH6>^~!Fql`wPU5qOB&u<^D%vGOGrZu)%Z zJ9gR?c_G@;$Q!UA$t<2~R82C^^O|JAg0`gxeq*IW`lkj|f8Ty_uJ)+x{^!Amj}-XT zGxU^SRvAlFPaLr+b{dOVq|EvIYovw7R#k+>1Qa#t`4H>a-_IVz?V?P_jwuRTv@I^i z4PNWWORk+aQ&?@`$Ms|{50RGb^<##L?O2vwD^;d>44=9Z3e<4YYau|--lS3+zdRYU zI0x4*Mv?)PT@zQMWc=y$RRG8N(Gw~=O|Kj{4(whSUTO|iQXc|wf<~w7YA>phD3Jq`OeJ_sVDo)Z7&T!+c(^* z(PYh#ulM%}FV?fGLmvIsnc96zl-7E)Bbh2{ZxW{98dLkj`^AC7CFc(aX4+E7VY(_~ zn1e|tU94OhMT?sTq{~;~W;gNQ`3=*qBTPO%!IEVMXO2nyC@gqzs+E$M;W zyhjNNHq3xUB2dvhlZ=(JUBOgy^=7g(tJ^e=sLs0`YE+!q!-MSh*U+du#@>IdpAS$m z1&xXyB}+2&Wuk2;>rSEudEv5@t~a$%(J}>Fq9kvXc9G&AvUol6k)* zjZG^LMqYgI2NlRg0vVSnca{ewvNaqh>!cW*oCak&l~SzI((wT_ov$xT7n7wE|GZF| zeShwExV9PKWWR`u3sOb%L$zAzjLTe>W+A-*Ku;bwOLRz5ocLlh$?n6DWT`A5x|)hv zpgN8S#ayQzXUB&KN)CHGriV>h5HOmzZlTPs>3yAo1QseVuUNw4NoI8ydWgptERNTc z`Uf^x0qcFbd6f@|-h&`(=658=)(6PW6G`s{%1vSn1hUw@CUvVC(?~gr*HUR6USN(x ztbZfv{0%r<>_u56s9iE8VnKMb;F4p-&>VF!Gb?oFA0)b}C)_!-%=-2@FP}6D$+GrT zagA}OfBk5+kSv&3J766es91GQmFQAQ*8y;264~KbzWiK^`%gEc z%W*v;TN7834F=-lBE*dMHI0}`YCEu}q>^K2NZdi+D0YZ;h`$v9ZhI;=*xH%tNk?8nWK~aY1$GKn$}V`5Yn~Y=5R;S zHY{4c_}t#g+*vOEAgD_P*XQ9^E|`R<2&|IT65OGkTc&< z?uO$rNXMN#K15BiYY$txYmyN>R!Jp?;|`@^tQ?btA}*Jz%~^PQ(*i^@vt@zvo?YvqX2=m^?>!`;FLsIZmEV)qaS%~JBogp2%}5CeJhRXXzc&z+F)(v zb&Vq&f)hl91hB6!)*5dxZhY6Q_`HQ9*mf%wIUT!{mz%aZ72-JDj&SyabX(}3H&Id5 z=sM(X3dksbDvPMwl1TW}^bodhPPaH-@sq{+eeggsTBh7he(ZjD>v(`p;^9A`nSt*t zEt;fc8p2W4Ww`Gk=n@|JcmNv=kZO?qBfrYYVd=P}I6he2HyPs6&Ah>jllBH&BJ zR#*O6F$+4qpc#+-#IO+2cuN&*`RP31Kd0?f=D<{V`A4^Da6cl!=+A`6A9>@kw2&;$ z2%IN<>;7_ri%x$|p|$o;hkcxJ9STqTG`*EaHve)|50v0sC|kXa9kZRrfqv;$PUeQw zT#20fWJI+hJEak^u|H7f&LWVw(-qyiVqd54 zC8eO?a1Bh8r?}X4!Yj`JG(SVw(O+!2cPC;w^V$|T8wOSd-^>_l3LE~VM62OKqtLX_o%)H`y< z#$1j|wx<22=kZe(p8pnr9{;aYDnPaJbyy5e2S5S~Nd@+}wds`x0E>vq+dJ<%nV0wC z;h&{o3}V1~TGBaH*t4All6-j%@lyR`TEv5(7rocv0D3jw!bQi2?V4*0-&1 zsVhq>k%w)7?=$ng4&edI8%Lp56K?@!F~$(6F>5HNyngBNbYdS7IJY(XX(=jKckPUq zdz^e!SD*Uh1SooaG=@0CI8C)D`2dxNc}}Wull>f}u66}piNI_Pgwx*xrt*!M_HhJ$ z7#8sbZL%M@of863ttFK;11n-f3K2sfg$Qagi_F_@n*J2eXhl%sS&$x5hyf8mwW**e z$kS<J&kLjUD9g!;(cI znphoHpfZIMY*^#9?)_79`|f|FnovcY=P2gQ#2gsI5()Y*wnmfeoDc@TkF=yi{6y zNz}C|Bo>&8m5FK8WQ0=?VO9Mc-Fb#3i#c+xh9RHohoWifgL-6_S(75Sad5US7kmgG zm4q6*<_H4yd{BZtcr%|tnh0P6O@Jeya^L~E<9Bc6rY&B<^rzBIUpz}1A?AmmWV7T6 zoAWrV@-RFP(y5hGnd!Yqoq4okamG_!kx!RVUCzQp>BMW#P)ysnZb>Bgi^mW{S4s7)*2N}+SHE8D zIA$PlOY}dgXZ!c#;tma@P;b;B);=Is4`U?d)6Ali4aUrA{7vwZJ!VuO+v8gcum z*m}&K_53kxc#wGSC;VmnBF*|ldl56z?9cpg(3p3yNIO7sX6}}>v|_HbpOrARVH*3& zn-vJh0ghEa4Ylz$L5yOFCoU0sEu1o|kp3 z7z-Qa*?@$#11yTKBd?!efsU(1+}s|$WQH@A2k;!SsGNWhlmiCjtKK5hvl0NC=0=Z^ zw*-+#0aHaV3)HLtsF7y$8bjA`m6t9@NmH|lwXn=mxj!%!85@da0h(C zZ)O0B4vO-&3>3p}i*%potjDvCE6I<5OgaHImcNeOl11>7DDMD7yv;O07=ohuM^t$* zc|w6%SeVt8+cnb)3i7443;pw0GKt{4ObE~o^0tlxOazdY5G-$ESjrQm*EYMNBXnCL zA&kI4gO8TjrmkEDH^flYzzx^IS9Y)80txL5mZp2c;25^Pm36QQ6r;@nPKD>n)^Wmi z)tUh>s3kXVe|wFyrHPKT$PDT13}DGJ-b2g0z#(teSPusGd&n+U zAEHuX#(m&@OVH-Y55NfUqb6f;msy^Ppf(~e6@2ao)L4-#P;h|4P>hqnXivU(1ebxO zsQD!0HL}7g4?MQFLmfQUj@DJ67bJi?kD&9Ef|mv_&Tx}gfSTnQ&lz)VV4iP*;j~!@ zi(Tz3t=*;{jsCOESz);8ptp`&qkv7^i(+g9%7WU0UeFJp0G}Y1u|my{0Hyk%dFmX% zaJo>~gou0#u^tm+K;yZBFKc}chdD%{DEm>c(sQQcK$E~5V@gwFyR=JLTp=E`d){a8 z8HvzXVGe2b9Ru8%V*>Qi-2ZrBVNO(T*EE^ox`5r1K>}t_4X`1AhcZ^^_V3p%%$@wt zpSA+-2L1cp>`5hQNmbX4W$RF)DUxxri;uwA^jc2?z2U&pbj7S+W`QG59s!CFcyJ10 z?Q+$B`+w^yXgm@urris^7f<3wB4E9Y%J?MOZ^7~n(i2AIxzmZlUzVceH|Bu zxsHL30D2$~2D||?1Pqbg55WWHx6c2UdtI-C!w^8Z+TdRui)Cq+5D1&n7=ciB&C*|v zO&Q`F(8yRsN%{w9`VGqz7I>xTH$ zJHF7R{0`4w_642U_o4;{29CW5SGUSc?c><()N$_`NA-V`c-U6w_|N_1X)+?Hxx9Ov zy@L=Po{IVnuo@{cy|_(1=TpZ~?@hXD{yr?vxNt!7rA_^NPxh|Am;b)30H5(q@6M>8 zY6S<0r^cST<2c~4-~%mJt?8;jWEH69OCDGhU%@`0N4Ppr@hc>jq*leC;S3yMk-eoLY}BD{ z{@ab>F>$xUK%7)$@Js#qxu#?S|OA1uT1=w(pP4GmPRdwwQ9}5(Z+DR|QkHFB*`a z)?g>HY;{){m0Qig7abshe2!BDQ_HvMFsRhZ&k+H(G!Em|O;3RW;PXYd)t3WN2Dlp3 z1Cn6k4G?Ms)N}r1lM`vfmUKMa%DOWDh^gsY!-G`zjsF9f5s~MBbT3S}2rg4G!2L!! zU_=0O0`rO}1-vD@z)mdqfWRp<*Z_b&ardxMBk54k0^6Qw3Ej!$)*)On#hl@NnykbA zfe_QZ#BAO0eTsx=M7A8`1eGg;r3~?JpH1qgxDK-ic3qumf18j;xW_m{y*xY-8HDtG zQbnkf3C=i!$(=l%WmF5M8USG%SUu1TffZii@Q1tz&nG>RM(6 z_+@@|6E%`K-VVrDLH)~~V6Czirv^7soVMs8#2+zGxn9v<4%u!#SpX)+53FPE*^qDBTj`-q$8gti>})MaY0```VZ|6~8g zH878VR}RBzjT2s4q^QTk>OHyc(&aI1=fU$USnPwOpN7}1lCzG2;+lcYPMXFH&7C4W zLI2B9UbZTU7-8NVd|woQt_@7|?Xmf!n$S2SGgJ1i9eA|H*8!XnQ5UorcbbPQ z&oE)Fzxsrw>%l}{vJ{F)s6?Q1B=^MAAITlqvjb!-f%y&3_-W_!5HsTUVF@o%_JM}k zoGwZjoN_(<8o%xcl@UgqZ-o70bN1BpcVpm!4CV~`{jJ{*TuUCvs{MwZ_?!N8PW)DD z*RL%kIUyHN8kKLC1*DU!!FD9si^30Hwr;HfkLwZd&;=wbl06?ewpP{B zx+dYC1_Lmr^>}umTBE2@nAfd>;;x**TLJdvLh_QEJteEqA!V|m^RNh5CM6z0lRjSy z9F^+L6lUiu&yJ5sv7{>BKR6}3M(W}nxoZs4?JLY!Gb`IadKO1CEi=culx+N-l{YO% z$M_=X9YJ2$tWel@y%fB4GGR&ML7P=Xb$@;0*`&r}_#&4D6Cb1NXwA$1K4meWAVDyC z0)1#Mc`xNp+7}m5e7~#WF{eB%JQ0f?Mbo~Jf~%a;eCx#``Jqx&Avcjhf3{8Y}DzquDEbo|!@CH=Kk5^yBq@vL`XeYkJoJ*{w9ssFaEG-|a4 zRTbEBy(9Nyx9eiYGm$F1^6%2(il~cLfNFOiN&s0!SLl~7YALOr%v5x;Z?g)Qdf8Bv zFZ9C$*g%1tS-lfu_NqF9JEuZ~kZpN(E2f(nr`~*sQv3vi>gqOeUUiYvHxljxS&NFI zJ3C#eq*w24839cst{^F=kBy!={s>;oW$Bvo!Y*m1iaDyGVBFXT!w|{)v``B!r&ZMew?=O+KF2Rihf&cK zATz&})A$^^Kcj!Vq39*0Sxi2+S*-acL?W^Aq|1#j2Pj#IHP&iWbZ{Z`#mT$GyCN+< zcG~)Gzk&S4H+Py?0csNK@F>pn<`;N>?*{fz|By5BAZ5`)CjP*i!6uh+oyLH~xpAjM zj3YhLWL5xy{WEVgugC@7MMZ?;biNpjD+|xZksaQ+eDyB8xGZJ9y2v6Vja{_9q?L*d zT-*o&z!2&Cz@D(2KC&Ntq8$W>g%ce`%rJ=-$yo18;%{6xr{7?e9O7-=`dO&JUqW^k z7L4?sg$C;rL@Ig<>=QZ2tqdPGWxsD}mwC2NcxI#(cub)6KvQ2{ChGYH@dC%`f#XAi z91L3BO_pXBpV{Jyw59hRNA^FfWv@dAzIEkkXkBUe zGW3rxC5KH*7yaJcr*gBio0;L&)7dsZ$&nME3VSb&F9qQX={Mp^CATp07|NOP2 z<_FEv($TV$@EVp`JmDkN2&?w4o5`<%lx8F6k83?T;DM|qD2jjOl72vW@eO9=phI7y>^al z#yHPPID|>98M;+fFFrjqgMD8;Q|B*~muBSYh2R^Sr+1c2`z@tB<5FSwnF>FJnfdfw zK8zF}}6Dj06Ur{@B7mwmD-kW9wZviTI(plDP~5?j6osQ?J$-<_(&qKEgEl zx4rjDZT6HT?TTZDsle*28H0RQ))vG_fw74 zD&A8`us5}~lzzSGw1%8aT!xPLl{RsevrNbFJm3EjHnXt$O&Z|W55%e>Gut46hXd#7 z*y7KjlMh0dA(C|+)%d)`35r8fTAfflX3LOjaHyK#We)bIuDpdInmvn*;}M>#*` z0+7$^wq-WM3(L-Ps$q!pc3@47yo;y_Ko{UdDLHX61tE&aW2i*8uGz4-96Q#khPMR!{nbSynuHpKDjq{cGZugFbfb>9ly8GjlWs&hkz9~}C{k{&OsDW?2s)cV%9o^@UgSbq-y?1X}F9<+N zTtS3!fIJ?iHTqpCxh;lFWCwX#{HfBcFxKWpbp1%l#kylb7_D!yY#Pw-Uvk z!x%UBVlfXlcC#I+Jd$qSF1Sj6R*Yd>Ba7?a7>#@=rp&`=HI2VZp%p6+Y4Nk@ngNwa zKw=`Xt{-<2y076e;?9Gs7youTwznLf?~GG(08t}8E%v7|hk5-XU6V1jwFB#bdF#lG=v;j%glwWId z!(L|qm>N`cLQdM?VQ&=5@7Nswb)~M0HRpe#m+jmOwdn6-TlfX_g>HztB{WLmVPEB( zqbCPHM$!h2;1bMADSfxnUb7N@n7(os-1zbi4$j+@jH%L67;MXCnAC(_*n%s#xsT#@ z?->g@hKK#~zTVi`c=c-DnG*uKd-sX}MYCC1ThJY?Wr&)rOkNl0DHg zj%B|!{iKZ_c=}KF{@M%t7mhjLGJ7My>PN{nZbWb3MmNCo>z8BAS3`SuTRbzhCm1FDW=2Jpd*Rsn$9oBP z1|N_&TIb-O9a9la^0_TTmgm@H+WTluJlxZf$C6`T{^OeSCpBx%Y+iHyM%EzpF9!~r z_wnl#BEtU@;}^BA7Qs_=CURYLPnJ;mxL78BdDM}(s0!8S)(^6oFG*!gWNx-d=aoPK z)9YTq&;1woew%;)f9%g)04eM48Y72E94T+Zp;HH3{7o8le_8&h%aS@RRVF1H{Y=ej zw?L;LUNE1Xm|LZxzBGmpx7KNMBfngntSasre~Ky>A(kd}K{fNH6r;u+l4ypNwMxJ6 z5N5H3ch_Hz6B`50WRW1mtagmk03iyemEpXPo};u8;G2fH>A<<3i5m0AX#$i#cnrfj zVhNIJck5SEgP|uu>M<>?jjh6XwH3GgPIFXjF~D6zyr``JLlv%UKq(J{d_>X29J69w zjmjMJ1t2MYOqnGy0D#?}XojP5a4PI<{sKh_bOE5N+%(TkzYLa|13n1LAWFz=66ssg zciDdFy#1+7uwx7?>|H$#;ReL)s26y4lN*2v#XKhK zn@k5T5d}K;K&v8OpO5-99Q+8?GP@y40!X7Uy1h}9HUbwk``<4IvW2{3ShhgkUTAcy z7regdqgAPBfOv{g)f9}Hsvo*D`zW;dR`_g26GdzM6av*mICu)=^Xs-ipAXtuVOZ*j zTJqKj>w}<)Hu8X2U{*ZnD)g3IvH&zpA+7oD!!&T*6`LX zKFFGf3a=K9ORFr9M0KG+ck;rqg19F1nvvIo(qcoYK0d1&29?qZhsy?|C_Zig@{4Sa`;5n178lL!eFNuKfC)GCpO14o)JKwz3`b0%H2+{x{JSg_7l_SK9)6%}PP!SUQs9^1Ol`lQ<@O{k2wUkSU4?qAS zft6o)U}Ig;*J#m0egwC(t=RH+lK!nO4Xg9>`aPG@x|FOEsV#P9ccZ88c_Jm)NkO83 z0*JrbNj&0f`)5e0pKI>h@6~{+SCt=_`7(L&b`X_*==>YN{mU zE5rW0Cm)}24@XsAJeA5Z>4PGx2RMbEr~$0Pu~QcjxrBI2eiY2eW_2 zQI;yj)TRr(i;mOkwp`izj^N1yF^#CQ6MNcDA!jGG=(Ds=9w3B73u4^iRf=Ky_mbESZetV>ulo$v*XKddAXLp_9hZ zZkvHWIcrfnC*TWtJZp3#nqDdB-YE0CV(et|W&BD&d-PfW=$RW!$A=UiON}9(g3bw- zt;;}wHOy{sZQJaCH^J8ovkr}sS%(H6{p;$z8*_9cdiC56>z3$+`M{I0PPw1#AEPK4 zFhV?zA{=s!h1Cf zF7v29BgW)$v$LmGk$V>S4H{~lEsp8W*4--U-wLOnT%T=C5Gg{2%jKq=|Gy%J+RcuX zT7?`CTp*Ou%`H9+Bs7!G&p~Mc-M*y7N+%lc^Bx-5Q`@>Xl~o+k&X$i6WNE7VmmXao zvRuZa{m+pAGm<9r$Cu+~gEUhAT(bVLNmDok|-`Dgv-8ZH5kXHh4n{$6=IVHmUWd7UE zZGNJGZwuIOf4RpMoYdz{M9GZn%b(OWdGq=SqS}nnoY&woLE`9}E#Y%c1{HeYm=VL- z*9MbA>2v89+`{NIS?Ur*|JrOA&&g60S|}E_s^dc%@y^AgA_kAig)&+9^H`_Lx)ogAJj;|fESsT% zC0wa7Cea~o5Q*6Y=@Gp7ku*ye9~4i}kq9wG=vK#g6dhgNxo%~&h){*H-rT90?2DLO zVgfIxb6JnW-)p5lDZsdgrG*U~J6*&^SrC~!HF~lMu92)PY0l;-EyHoO4vRV5xd}3_ zuidq%RgLJIo|;q9p)3+IPG=e&EOL3 zofn9R#qLDKq{|HB|8kh51_C_SJQv4Z5w849f(Il< z<*#;M<}M}+ciNQ31zr_|ZBx|sdKTdn|C5Hx+s9TC*JHDOIk^KHG*TRf3=n`{7WyY>h^ z3wTq}5>8aby#$0{v(3z1*&=pL2mBG|Or#Hb<70y;Ff~ouPGocgd)elUZca?NEk%mm1W9-1Uf_UYhI|I2n-e9?ilP zKemNJ8u1ULxqna4{c74a6|?F+EXGumkhei^W<1%s^CJc zY(_PQ-NQdgp0U7G`$4SIsxJd&cW+?B`eQ60A6U2^=?`6tIOYd)^s!lqbC<=wHhR8QZ2ez!!Tt#@x; zvVl=lRnJQYozD!lOKls2gKQb)#b)=9;UlZ3gKl3ofb*tcxHaB~4vlIUU1tF0{j1C+ zRc`CuAg!yT=MN9G0;D@70r+Q4^7#wsFW)5Y^DffI#CS#Kf+RZ?6E9X3K-!MkHX)~1 z2rggLe@g)!vwX259t6U}1}_@&VB0$RAZFp)9EfmSha(Fx7nh{dpt9`xw`iLAu7T37 zht8_0>bH4Ru^hwwv)V~{AshXkZicH{CKrZ>29I~SNfDGsR?4JO(%9rnA;di~H5Q89&xh_@67My6BuGrKqsi-B zkc*f)$3yQ+IL{agak)}xB0fT+{gW@mNEH1Cg23#%jY7iHSuF;;LbgL_BsA)HhHbBy}8*PQ|Qlo*<}kR z<<|J|F3H|=XX%Y*K{LHg)?$fk-HO2PcF22__krZ9_b3OJn1vCg(ec-Nz1XT^8WC*U zD)ZMZRZvZb9FUPqke?mcI3e;aC0ssdFW5Ax09c0q{gbW#vu_|iE+Y1#9)))R)ixkyCWaY!TgFbn~)qjc-W|^EwOB zHMV94wjy^4dU&0FE4=&rdcT@fP%I9JxbFpl)K`WwSarEjU>~pqk56;G1;S;WOAa30 zX~DM%_2H!gm-hb?DWopG+w_S%q3=_=F92eTq3^$-YAPBiJeSY6p;ql~eP-tc9mrsH zk$C~ceK)rU&F0it>X#^bfVb62<^ib%^mkvFUazQ*r`bi5-gMc~S{Mq@G*l=HSI^7= zK+>G};8h1rPRUm^s2{cGz6icCzZqT%?gw=NH=#NkoJ1=NKyn}os86SZ!047W(?H_; z?$#Y8>;jmtAORYas`$<%P>&OtB>H?dVertHEQ)ICjAC2`rGAmt3;#NM9|+WH8MgqJ zz4}HkY*2k81KtIEOYSgAJp8-@Y{+4e0rpoguQWCKcFv;~8p}uWgP6eQCJ4t|z^_SX zpr{Z34~OW^aO={VF`#G(aq4-E85_x=9zHmi1-co?QZ`W3DW&5>no}HCk z$vT`%dX%7v7mSRt-Lx315;S{XgAF5sMr1gp4Ja^{=wrj^S0{rdp4i5BhyKP5QWnM>Ug03BOz*f)edWa>r)CN z*IL4T{0NK>8-RrDqBV2@{kiF~c1Q1(dnwt$0l`3@_Ebvv$shl@YQ_I-RpbAH=dJNZ zkR{YxgFWW9Zfd*DgyZgf;je%~J)cNSy9cd#M{-J6J_>d()0o>>Gpf;3gAbu1Nr!`u zVvf*z2I>JNSk<#6oa~S=1u7dzqmIDTWlt$x(^|@b^%%tV$u5n&{W#4#hIYk;oez54 zQzYJ8S<@Fnwf@%vHAiOrvfG9Eu*u2w$afezvvBCnnr|+Mjr|jit zkCtU|)TAyb@FvYL(A2VX$}!X-( zHzas=us-$e1|yl5UGf$>o{9gCe??k1JA_&u+9stO7YMxnbQFl{AptbhjzKYaK}$7d zB6bETBQ|q57uef1OH4m&SZ4;_Aq=;mw>DB2(4`w}m$^&?68{t>f|@OVjauBH1~l)c zxF}K+%53oie>p-!;z7c$Jnv)X_B-#17>#dyAr7|-r?Sahcc8^T-JUpU27rl-_yC~s zA}A4@*PWNKqWD71|PR2s~YYVQ6n@c3B^b@k#r+z&&Zij=f$8 z6||X?^86QGl)rA9mrE&<`@ds-h1hYZz>hdXqD{WCE|hyEqbu;MEm#=^*LVAJE+#m; zA%$J+U`vF63nC)09ed)$6vdRvN})u>Y>LpLxvPmR;%~ysRqeOEzb-{nuM8I`0B8T-}3n{vV`KgGw95 zAz`SMpVEn)?8v!un%ZB~tankg=a~)CffZPWQ7$szR{*!z<&fG|({F1>jwO0;rZJN- z)YNoP)LQ(ti)ACOf4$un;&yMD(uu{_e)xfmsiKAGQd9|^Xone2(Zg;37jg5yFO2v)oe4^!$=*flwzL8 zt(h=MTEK6@-oI|ng+PyoZ?239M}M4;L)lOxM>3FEep4g2o6}78w^sR0D%&O{wGI zKPY+Oi#WTFcU(m6)Z@%eL(fkf;H928@=jv!MxG=!K5D~6>Reg=HzKK5HvU$1LHYXy zS1nCr0afE-Au*>|z*kIe9S&^ToV%?M@NLdWaGcCh6}TsDwDTX$=sQmS4+u_=+_+!4 zJ!Bq_91i`lGK2a7YSQ7&TL3?WI#b}Gqc!je5>?n>FF zNh(E*$RV>2_%%-wT&kLUgH9>@FT`S`5=2Q$X)=Dx4vy3ft; z{Qb_8{RqJ#kfqVip%_~_JbJ9SY23KE3KBCoY_P0FHP0R-Jwe&I?c8Hkt^EbxO<>q?RzLR-&P1J z5><&BDRLSlY)LZ>v#=6gtWUu_0JpP({iFol(y0E@I~YqONtVa`#Z$b4sF-iGNmBR zMdtkowQ4%ft+j}!U+x)uqV>QR(Y`~`o&HR+(jtaOsDm6kS?Gq9ka1Hj7b&M&>ijz- z(ocQ7H1X24siq^5NjIRR!vYZLONJ45T|f5%Q>v$Gi+Q5D*iq_T9{Ko(^a8w3-N0c= z2gTZ?+oGJJ&0^dsDZ!~3rQ|rJ+iie7w5V-qvch3Hg{5uG3#MqiIl=6#f!AwSZua?8 zwslJb?}|(18PC2_z|PW8B=FMe7dJbAb%i^6wrPotmlug%M(oB9>ZFOMWX+@o4n>wH zSs*Cr)DuJ`)5kQ1VT<#6&CRoAS;ge`%$K{l<&4ygrCcPXeMA22+-C4JW)bEDFRV=u zm#I8Fd0N4LjJ%D@XzcU9LXKbEKfiYh`%(&)TqiNYZiZnv(JS)KK&z!3k5|BW<5j7A z=n~$GO$NXijQ)Y*E@IT3|#==YR;T)b)|HO{?^lhEO?ER&cUT*-$Yt+u`UM z&vnbS{EmNaTT7*PDc_Lk;C77lvdI&>ZnNcP{ED@AsqqY#)*N(SST9z65j#FV5H|_= z8%4&EBO}cAOLu)Nu#ninEcsMR5yjRVZPvLR_I^%Qc2B4JPxV}}V&XZo z6++q)nL=Svwa5>sp33IEw>;tzyYGH*cp?0AHh-bsPa8$MdBbmvNA%CAQxfhp@ISrZ zRi2rfFfJ8uWeAg?%qxa@XzY{YH zr8XXOUy4^__yQFBRGd>50Mrfq1`7>;_ObsZWOlT;cHBA1oJnm&iflaZo(Gdde&(Xi zFJ}FHD)o=7n&{0AZvR@o&Hv>RVNT%Gdu7KN3&&Y%4V+KoQO*5ln^VuAZinrc7_#Eg zG-)}t_SB#TzBAV_8)x6+lU1*K3jg*Ra;SV1Yw`GVWZnglJ&kmxSj^!&`$c{$eHHfX z&aBE$`@{jLQNSb-utB+8WNT^Ge3T8`{jq!UF%<53?&Kjqj@o(NW$plqPW_~?1G6zR zFe`Lyo}=P%kZ0uyZojfiHJfA%j1Q)JCmuVpW?gl{sFMv+r zmv^lh0M>AE+#3LXBl=b@Ci>As(#SmI=O_VTUPBbI5u82nG*ULoWEXB#8_1ts=J^1^ zxc?&=G;37FEG#7;Mp%zGKrtO46hnrwh`+CPZ7Ct5Dq$4}v{E2T?DV4XpP2!zut+nj z4?2KXkfxH?j}fkmbY43 zW99SfOYk1Q1W>kXM#Gb8NZ0`&Cp)ZKtl<*NzMBeVp%*%U35V`oISUCam^q67JH3`GD*sZ8ld2oW^Q2eqXsu>~g4k}lK zEnH1tB_)(1pJW48B)B?%RzZ(dd0Sf$^pqrTGH^K2?hn~1Ur990 z&yjuT_DL5ldixo`B5`{7ACX(8*JL|>gK$|1Cm9^MDeIK2JzOWQUsf-S$9W0>w|e2P?Oa(VpW+BkiJF)*gkY^ca#@q7a1eY zT{o)V+DlokEIBQrXo#vttZBPcKeE_8!?y%XR@7kjvz?o&({kedEZ&BFV*lKjAqY*9 zRAmKud|3pojP(jy3YBw2@Vs1x*4AnjVS$FwKM>Icvfc%FBekAYY9&3StFFH`_e^=#S@MEK9&;5V!mOE{he0YDZ3QscqIgZX+^2B-@A zJ2BQ1uh*D%o4@?DiD!>P$vb@(hJV(1fQwP<7WtjP6CmOCa$)diG6n=A&K>WV)Pmujn?nG=H(p2)2ClZ_FCgeEgu>J3 zhEa1$pvu6@dUScO!5Yeoyvv+ppic)k9@0=l2C(n<0GOJpgbDaWmIT)x(69kc?|LmW z9jVtU@J|47vG-Gj;3!^w*_T<`X!bLieIv`&DDQ|ZaLG8id4@d73<4a>>C-l-@wa2k zds>$$oPVIRb>45_J_Mnkp(LzV`499 z1GFpCd|C1?11O0~z1hhhehK-9B)$VxWfhGDSJL)__9=W5E_$4h;OJ1w+M9l`>RRxp z{9)Z^L3wLKUF{!l`1k7-hp?}5pB0B@Td-vBc;gv!Q&2-GJOs><(7*Xhtr3OYS5M8xX;q}8IT016StCeJ_KFKR zwFp$8Jr!#rR9x zPv8bp&%Pw`qIg+J+ol|J7KCH3uc3ZuzwDP*4SPW9b9Qb!ejqr`&|5{`?e9C2(#sKT zv~e3DmvosA#I+|JqoyHJKV6sLN0NIpxJz}SryWyne01@3fp7G;_Fg$o#*H*NM)Kkj zfNEwG$@oa1<4=4YxK+wq4v*N0xA$xi^c*|_Sa)@nw~6A~8X*{FPz5K}XRP$&iw>2& z!4#htAw(4y1v7c)W4gEz9e)SB0qfiAH{4{)9(7-%rne)fIY!n1R3-;a9f1m?-27ey-u_CWrTTTPeVAIVB=jir_mY@8`~ii0}H5={{ke;p~UUqJ}= zA~|UtH`i&5!O34ja(!rmfluMgsl?~W;|uC@r+%jS`DrBk`n-F_0+V3%X}-x=BQ zQzbJ&{`;TXySwU7uP+`0(+{U}u776dH{Zu)13BwjaY$qRi5`-Tw?`?8HHSPcjv!CcnDBrbuz5YR-ojcTgI3?~cX$bj5IIpG3zpwa}F&Vu=b@u~Ke_&h+#H=4$Im~L| z!7>Vk6D3@g#4ZC5@xmOM74CXZg}EuI)1=2K)@5}m1zIx5y9LRz?9*mwh$7sX zVy{X^P2Ph@wS_#3s)~p&m&T{8K7{>n5Kz9<5x)+e*=v|>Xm-gl=^rbmNmVr3ha0z{ty|oBPCbM=7;voPttXm0P_vK6B$jk@{E18= zh>&*vKu8OsF{}B&V|L@|wdP0Ev9&xR2`Dzz2sPZCiL}BJ$xpxUtvObe>9zdB*^A>C z^!clP@R!N;5dCrVDA*D~FA9TDqTu1pcM;qfb7SfKo0YE9|0V|T|Lb7!Ru`$<0P>&2@W_n}dr7v9v;l)$q1aMz7HhVK^I>66l9~R|hMXncbxoft6`W zk$bHHf~3@kf8N{`{eqC*YTC~$&(^;ytd3sHo=*_WdNvquslSBBYK+=0`JUYcMg3+D_A&3u86QMXQCP zMJ5vrjW`(RuQTEIUzYKOQ zMS`7wq*anx*3Yv)ALaD1YgM4>NgBrL7djz7K(BoTwCChV-ADmwEr}`#MLPH5LOuGm zJ4yf}+07X(pPL}(MN|8lW@lDayEH)7FU*NJGz~Ebd=QiPfYyazM5`u_s|;+dIs`1j zb>w2#UW29vS1~ZrXmI61*I4(_4AG*!hy^)Iq)&POEhe62XA-zR%;U{{<)fJK^7fzb zf68{91u3r-Wpp6&5#2HHO0LQBBHM^ZUYzH}J_4N4ern&0pY%Uy?4BHwfm1GuwZ!=Y zvwF=4)em(vf8R<_~K+)sIZFoc}{HD;0yG~r+n)pxz;MoFl}3fSGg58y$Nz>>!0*e9>TFxj)&2dK?>nO zhPOEAx^9U%(+kAokp)B3_`|&GXaQI?^{ZfMZFjv&tDKZu0y=8Nr4myr-!RWh*1l@* zUY*L5qh?d)N6SU!^cX1(>xAmHw~HEAYs+^#<2O2B$1pOA2!st6_ZqlJ@ZC7~(2L|C zoy5uY?4L=`rw~B%r_8f$vD8c&ccXV~WmUe+@z(7N7^OiTKOAC_#V$&g88)4Y%HMc+ z)7Q{aZ9f&8^U24ZjXKgVzzp*9C#l#duhDopJK+|^qsUNjo+_K zPQl%ETo-qr+|M6u>Z#+EAZBNbDxI@ShK2G%#+`d<_f|WRtrCQq-Z#BObY^ z{W?f`B)v6mJO40(Z)?oUfEevaJ}5DbzWuq~_H|-qGwF+R5%BRXn{|gqpy+vb6 z9V2+KTB5-05hV_zUD6eovnrUoUhS+;Ta!zH&#U#f8ZqGy;Axd|#f+z219?us#ClCT zPBuq^gC4FBN3nX}xSt%E&Wbky(hl3l0BrkzCeHrbKFf?_DzHWO5j)o+(%9=$H2=qi`?X~>E|LcR?RaV@hvYw|r4i>>uCnvZSz{lK<6 zi5Y$`xV|6S^u^oCnO})@DseA-9--E!$aGhEYzRwkOuHPQb2+mJ8NL)2bV3QQd@8e6 zdl}xhcy%2Q5-*qQ>)an-oSvg9lpD=+Yt8r}+0!A94>}Lb^`sx0I=1TR`?skCe_NDi z+u9%a)x=kgnPE>t0tB?2EeHP;k1s=QRLCpJOzwgE5F+ZM>9A6$|GkH3u>jAbIOyO)|4|bzS#h`}DC}MB|3c`hX$+`+^J0OcjRwP4AsLSN7mWZlZ-_`^(~4 z$9Z0SnO#%L51YuNwgfBE<3li~V?SW!ptjlSNJzWvqoy}CZ6y1m9@L!`cN>y%oQ$@= zp~g|1K0X`ywoN8#kNBQf~-F)pz~7oNKr4? zC2K0?n*nA(ObX8NpWdPMD3Q#n&0+UU}r!c=`F*TS*s&qBDuvIXQ+}CZAu(r$t zlWQ&)$cfe{lHn_$zrBF&dI9pH#5DO%u}s?V^C)KY96}EUF`IF|)xw#vE##p7c->CU z6aXr8uZ)FDtU7Mq=~{l4m|TsA=IF>qq{?VO7g*OU8$Py0lZ>T2sEnedUG5tLrac%A zc~D_|4&K!XCs>1yU)TAE4mB81)Kf59>cAiGLgfSP^bR zplX^NJmEA7-0NXjX1p6egxPixYMVNofm!lONm>`>HeHe!wi(N(@a_AI&S6$tTvn_LE!Q#=T6&RHWoOsH#fw@zqh@d z4!nds?`U%EKfzZ1x9u1H2%_Pwjew`A34Wrs$vnNf`OT)Rui?K@6T3}<`g>4Hwg@>K uJ*qJw*;0Ek@V7U%zoi=sPXYQ{=6}+H{f`{zJ+Q8sVDbL9GyH{r4gMExtp Date: Mon, 30 Jun 2025 18:17:07 +0800 Subject: [PATCH 051/112] =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 +---- pyproject.toml | 2 +- src/recv_handler/__init__.py | 3 +++ src/recv_handler/message_handler.py | 36 +++++++++++++++++++++++--- src/recv_handler/meta_event_handler.py | 2 +- src/recv_handler/notice_handler.py | 10 ++++--- src/utils.py | 36 +++++++++++++++++++++++++- 7 files changed, 80 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index a928191..07b3002 100644 --- a/main.py +++ b/main.py @@ -20,11 +20,7 @@ async def message_recv(server_connection: Server.ServerConnection): asyncio.create_task(notice_handler.set_server_connection(server_connection)) await send_handler.set_server_connection(server_connection) async for raw_message in server_connection: - logger.debug( - f"{raw_message[:100]}..." - if (len(raw_message) > 100 and global_config.debug.level != "DEBUG") - else raw_message - ) + logger.debug(f"{raw_message[:1500]}..." if (len(raw_message) > 1500) else raw_message) decoded_raw_message: dict = json.loads(raw_message) post_type = decoded_raw_message.get("post_type") if post_type in ["meta_event", "message", "notice"]: diff --git a/pyproject.toml b/pyproject.toml index f3256a8..aa72630 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.1" +version = "0.4.2" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 068af29..0deaede 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -82,3 +82,6 @@ class CommandType(Enum): def __str__(self) -> str: return self.value + + +ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command"] diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index c84483f..aa327e7 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -4,12 +4,13 @@ get_group_info, get_member_info, get_image_base64, + get_record_detail, get_self_info, get_message_detail, ) from .qq_emoji_list import qq_face from .message_sending import message_send_instance -from . import RealMessageType, MessageType +from . import RealMessageType, MessageType, ACCEPT_FORMAT import time import json @@ -108,8 +109,8 @@ async def handle_raw_message(self, raw_message: dict) -> None: template_info: TemplateInfo = None # 模板信息,暂时为空,等待启用 format_info: FormatInfo = FormatInfo( - content_format=["text", "image", "emoji"], - accept_format=["text", "image", "emoji", "reply", "voice", "command"], + content_format=["text", "image", "emoji", "voice"], + accept_format=ACCEPT_FORMAT, ) # 格式化信息 if message_type == MessageType.private: sub_type = raw_message.get("sub_type") @@ -285,7 +286,13 @@ async def handle_real_message(self, raw_message: dict, in_reply: bool = False) - else: logger.warning("image处理失败") case RealMessageType.record: - logger.warning("不支持语音解析") + ret_seg = await self.handle_record_message(sub_message) + if ret_seg: + seg_message.clear() + seg_message.append(ret_seg) + break # 使得消息只有record消息 + else: + logger.warning("record处理失败或不支持") case RealMessageType.video: logger.warning("不支持视频解析") case RealMessageType.at: @@ -405,6 +412,27 @@ async def handle_at_message(self, raw_message: dict, self_id: int, group_id: int else: return None + async def handle_record_message(self, raw_message: dict) -> Seg | None: + """ + 处理语音消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + file: str = message_data.get("file") + try: + record_detail = await get_record_detail(self.server_connection, file) + audio_base64: str = record_detail.get("base64") + except Exception as e: + logger.error(f"语音消息处理失败: {str(e)}") + return None + if not audio_base64: + logger.error("语音消息处理失败,未获取到音频数据") + return None + return Seg(type="voice", data=audio_base64) + async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: # sourcery skip: move-assign-in-block, use-named-expression """ diff --git a/src/recv_handler/meta_event_handler.py b/src/recv_handler/meta_event_handler.py index bb9efe6..289e551 100644 --- a/src/recv_handler/meta_event_handler.py +++ b/src/recv_handler/meta_event_handler.py @@ -22,7 +22,7 @@ async def handle_meta_event(self, message: dict) -> None: if sub_type == MetaEventType.Lifecycle.connect: self_id = message.get("self_id") self.last_heart_beat = time.time() - logger.info(f"Bot {self_id} 连接成功") + logger.success(f"Bot {self_id} 连接成功") asyncio.create_task(self.check_heartbeat(self_id)) elif event_type == MetaEventType.heartbeat: if message["status"].get("online") and message["status"].get("good"): diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 73656ad..df46083 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -7,10 +7,10 @@ from src.logger import logger from src.config import global_config from src.database import BanUser, db_manager, is_identical -from . import NoticeType +from . import NoticeType, ACCEPT_FORMAT from .message_sending import message_send_instance from .message_handler import message_handler -from maim_message import UserInfo, GroupInfo, Seg, BaseMessageInfo, MessageBase +from maim_message import FormatInfo, UserInfo, GroupInfo, Seg, BaseMessageInfo, MessageBase from src.utils import ( get_group_info, @@ -151,7 +151,10 @@ async def handle_notice(self, raw_message: dict) -> None: user_info=user_info, group_info=group_info, template_info=None, - format_info=None, + format_info=FormatInfo( + content_format=["text", "notify"], + accept_format=ACCEPT_FORMAT, + ), additional_config={"target_id": target_id}, # 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁 ) @@ -170,6 +173,7 @@ async def handle_notice(self, raw_message: dict) -> None: async def handle_poke_notify( self, raw_message: dict, group_id: int, user_id: int ) -> Tuple[Seg | None, UserInfo | None]: + # sourcery skip: merge-comparisons, merge-duplicate-blocks, remove-redundant-if, remove-unnecessary-else, swap-if-else-branches self_info: dict = await get_self_info(self.server_connection) if not self_info: diff --git a/src/utils.py b/src/utils.py index caa0b56..6e07da4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -11,7 +11,7 @@ from .response_pool import get_response from PIL import Image -from typing import Union, List, Tuple +from typing import Union, List, Tuple, Optional class SSLAdapter(urllib3.PoolManager): @@ -219,6 +219,40 @@ async def get_message_detail(websocket: Server.ServerConnection, message_id: Uni return response.get("data") +async def get_record_detail( + websocket: Server.ServerConnection, file: str, file_id: Optional[str] = None +) -> dict | None: + """ + 获取语音消息内容 + Parameters: + websocket: WebSocket连接对象 + file: 文件名 + file_id: 文件ID + Returns: + dict: 返回的语音消息详情 + """ + logger.debug("获取语音消息详情中") + request_uuid = str(uuid.uuid4()) + payload = json.dumps( + { + "action": "get_record", + "params": {"file": file, "file_id": file_id, "out_format": "wav"}, + "echo": request_uuid, + } + ) + try: + await websocket.send(payload) + response: dict = await get_response(request_uuid) + except TimeoutError: + logger.error(f"获取语音消息详情超时,文件: {file}, 文件ID: {file_id}") + return None + except Exception as e: + logger.error(f"获取语音消息详情失败: {e}") + return None + logger.debug(f"{str(response)[:200]}...") # 防止语音的超长base64编码导致日志过长 + return response.get("data") + + async def read_ban_list( websocket: Server.ServerConnection, ) -> Tuple[List[BanUser], List[BanUser]]: From c0416a517ef7fa3a540cf9dd7e40e5c2ddafb816 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 1 Jul 2025 16:25:54 +0800 Subject: [PATCH 052/112] logger change (hope it dont crash) --- src/logger.py | 13 ++++++++++++- src/mmc_com_layer.py | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/logger.py b/src/logger.py index 8071ff7..4100964 100644 --- a/src/logger.py +++ b/src/logger.py @@ -2,9 +2,20 @@ from .config import global_config import sys +# 默认 logger logger.remove() logger.add( sys.stderr, level=global_config.debug.level, - format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", + format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", + filter=lambda record: "name" not in record["extra"] or record["extra"].get("name") != "maim_message", ) +logger.add( + sys.stderr, + level="INFO", + format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", + filter=lambda record: record["extra"].get("name") == "maim_message", +) +# 创建样式不同的 logger +custom_logger = logger.bind(name="maim_message") +logger = logger.bind(name="MaiBot-Napcat-Adapter") diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index f7fd1ad..0c5a525 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -1,6 +1,6 @@ from maim_message import Router, RouteConfig, TargetConfig from .config import global_config -from .logger import logger +from .logger import logger, custom_logger from .send_handler import send_handler route_config = RouteConfig( @@ -11,7 +11,7 @@ ) } ) -router = Router(route_config, logger) +router = Router(route_config, custom_logger) async def mmc_start_com(): From 59af134904a88e062f5d89b119630665fbee0460 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 1 Jul 2025 16:31:56 +0800 Subject: [PATCH 053/112] issue #27, thanks pr #45 --- src/__init__.py | 1 + src/send_handler.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/__init__.py b/src/__init__.py index e5c2d6a..bc1f63b 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -12,6 +12,7 @@ class CommandType(Enum): GROUP_KICK = "set_group_kick" # 踢出群聊 SEND_POKE = "send_poke" # 戳一戳 DELETE_MSG = "delete_msg" # 撤回消息 + AI_VOICE_SEND = "send_group_ai_record" # 发送群AI语音 def __str__(self) -> str: return self.value diff --git a/src/send_handler.py b/src/send_handler.py index e02f1b1..e727619 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -108,6 +108,8 @@ async def send_command(self, raw_message_base: MessageBase) -> None: command, args_dict = self.handle_poke_command(seg_data.get("args"), group_info) case CommandType.DELETE_MSG.name: command, args_dict = self.delete_msg_command(seg_data.get("args")) + case CommandType.AI_VOICE_SEND.name: + command, args_dict = self.handle_ai_voice_send_command(seg_data.get("args"), group_info) case _: logger.error(f"未知命令: {command_name}") return @@ -378,6 +380,32 @@ def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]] }, ) + def handle_ai_voice_send_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """ + 处理AI语音发送命令的逻辑。 + 并返回 NapCat 兼容的 (action, params) 元组。 + """ + if not group_info or not group_info.group_id: + raise ValueError("AI语音发送命令必须在群聊上下文中使用") + if not args: + raise ValueError("AI语音发送命令缺少参数") + + group_id: int = int(group_info.group_id) + character_id = args.get("character") + text_content = args.get("text") + + if not character_id or not text_content: + raise ValueError(f"AI语音发送命令参数不完整: character='{character_id}', text='{text_content}'") + + return ( + CommandType.AI_VOICE_SEND.value, + { + "group_id": group_id, + "text": text_content, + "character": character_id, + }, + ) + async def send_message_to_napcat(self, action: str, params: dict) -> dict: request_uuid = str(uuid.uuid4()) payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) From 163ddefffd2996a99aca3d0e02b366e0d073e531 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 1 Jul 2025 17:20:59 +0800 Subject: [PATCH 054/112] minor fix --- src/recv_handler/message_sending.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler/message_sending.py b/src/recv_handler/message_sending.py index 2e43bbd..0c6a732 100644 --- a/src/recv_handler/message_sending.py +++ b/src/recv_handler/message_sending.py @@ -22,10 +22,10 @@ async def message_send(self, message_base: MessageBase) -> bool: send_status = await self.maibot_router.send_message(message_base) if not send_status: raise RuntimeError("可能是路由未正确配置或连接异常") + return send_status except Exception as e: logger.error(f"发送消息失败: {str(e)}") logger.error("请检查与MaiBot之间的连接") - return send_status message_send_instance = MessageSending() From 8923ebf534d28cb777605c547786db28c9fdb8f6 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 2 Jul 2025 16:25:18 +0800 Subject: [PATCH 055/112] =?UTF-8?q?=E6=B1=82star?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__init__.py b/src/__init__.py index bc1f63b..b8e354e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -21,4 +21,4 @@ def __str__(self) -> str: pyproject_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pyproject.toml") toml_data = tomlkit.parse(open(pyproject_path, "r", encoding="utf-8").read()) version = toml_data["project"]["version"] -logger.info(f"版本\n\nMaiBot-Napcat-Adapter 版本: {version}\n") +logger.info(f"版本\n\nMaiBot-Napcat-Adapter 版本: {version}\n喜欢的话点个star喵~\n") \ No newline at end of file From b4ab8ffa78c79f22f9b6bc9d4d9d7c0530ca9738 Mon Sep 17 00:00:00 2001 From: infinitycat Date: Thu, 3 Jul 2025 14:14:10 +0800 Subject: [PATCH 056/112] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=BA=95?= =?UTF-8?q?=E5=B1=82=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7dee666..d50a5f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.13.2-slim +FROM python:3.13.5-slim LABEL authors="infinitycat233" # Copy uv and maim_message From f93572c1f755036250f6dd73d9fd61d555f754ec Mon Sep 17 00:00:00 2001 From: infinitycat Date: Thu, 3 Jul 2025 14:16:42 +0800 Subject: [PATCH 057/112] =?UTF-8?q?ci(docker):=20=E6=9B=B4=E6=96=B0dev?= =?UTF-8?q?=E5=88=86=E6=94=AF=E9=95=9C=E5=83=8F=E8=87=AA=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-image.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index b73a361..fc40c97 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,7 +2,7 @@ name: Docker Image CI on: push: - branches: [ "main" ] + branches: [ "main", "dev" ] jobs: @@ -32,7 +32,11 @@ jobs: - name: Determine Image Tags id: tags run: | - echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:latest,${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:main-$(date -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + if [ "${{ github.ref_name }}" == "main" ]; then + echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:latest,${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:main-$(date -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + elif [ "${{ github.ref_name }}" == "dev" ]; then + echo "tags=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:dev,${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:dev-$(date -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + fi - name: Build and Push Docker Image uses: docker/build-push-action@v5 @@ -42,8 +46,8 @@ jobs: platforms: linux/amd64,linux/arm64 tags: ${{ steps.tags.outputs.tags }} push: true - cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:buildcache - cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:buildcache,mode=max + cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:buildcache-${{ github.ref_name }} + cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/maimbot-adapter:buildcache-${{ github.ref_name }},mode=max labels: | org.opencontainers.image.created=${{ steps.tags.outputs.date_tag }} org.opencontainers.image.revision=${{ github.sha }} \ No newline at end of file From fc3391947dff0032b501134b0fcd286dcf98e747 Mon Sep 17 00:00:00 2001 From: infinitycat Date: Thu, 3 Jul 2025 15:08:32 +0800 Subject: [PATCH 058/112] =?UTF-8?q?feat:=20=E5=A4=87=E4=BB=BD=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=94=BE=E5=85=A5=E5=88=B0=E6=96=87=E4=BB=B6=E5=A4=B9?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=94=B9=E4=B8=BA=E8=A6=86=E5=86=99=E7=9A=84?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/config/config.py b/src/config/config.py index a219078..5143f39 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -1,5 +1,6 @@ import os from dataclasses import dataclass +from datetime import datetime import tomlkit import shutil @@ -56,11 +57,16 @@ def update_config(): else: logger.info("已有配置文件未检测到版本号,可能是旧版本。将进行更新") + # 创建备份文件夹 + backup_dir = "config_backup" + os.makedirs(backup_dir, exist_ok=True) + # 备份文件名 - old_backup_path = "config.toml.back" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + old_backup_path = os.path.join(backup_dir, f"config.toml.bak.{timestamp}") # 备份旧配置文件 - shutil.move(old_config_path, old_backup_path) + shutil.copy2(old_config_path, old_backup_path) logger.info(f"已备份旧配置文件到: {old_backup_path}") # 复制模板文件到配置目录 From 5dd2dda06782ba32199801eafd0ced938e571123 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 6 Jul 2025 12:14:04 +0800 Subject: [PATCH 059/112] =?UTF-8?q?=E4=BF=9D=E8=AF=81=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=E5=AD=98=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database.py b/src/database.py index 45100cb..612c4c6 100644 --- a/src/database.py +++ b/src/database.py @@ -49,6 +49,7 @@ class DatabaseManager: """ def __init__(self): + os.mkdir(os.path.join(os.path.dirname(__file__), "..", "data"), exist_ok=True) # 确保数据目录存在 DATABASE_FILE = os.path.join(os.path.dirname(__file__), "..", "data", "NapcatAdapter.db") self.sqlite_url = f"sqlite:///{DATABASE_FILE}" # SQLite 数据库 URL self.engine = create_engine(self.sqlite_url, echo=False) # 创建数据库引擎 From ad844ffc434eb97ae14af67b6f827aeb9bb4632b Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 6 Jul 2025 12:29:28 +0800 Subject: [PATCH 060/112] =?UTF-8?q?=E5=86=99=E9=94=99=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database.py b/src/database.py index 612c4c6..f9a94d1 100644 --- a/src/database.py +++ b/src/database.py @@ -49,7 +49,7 @@ class DatabaseManager: """ def __init__(self): - os.mkdir(os.path.join(os.path.dirname(__file__), "..", "data"), exist_ok=True) # 确保数据目录存在 + os.makedirs(os.path.join(os.path.dirname(__file__), "..", "data"), exist_ok=True) # 确保数据目录存在 DATABASE_FILE = os.path.join(os.path.dirname(__file__), "..", "data", "NapcatAdapter.db") self.sqlite_url = f"sqlite:///{DATABASE_FILE}" # SQLite 数据库 URL self.engine = create_engine(self.sqlite_url, echo=False) # 创建数据库引擎 From e2980305a50e05bcef602bf471adc1d27ee18c06 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 6 Jul 2025 12:30:05 +0800 Subject: [PATCH 061/112] =?UTF-8?q?=E5=86=99=E9=94=99=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database.py b/src/database.py index 612c4c6..f9a94d1 100644 --- a/src/database.py +++ b/src/database.py @@ -49,7 +49,7 @@ class DatabaseManager: """ def __init__(self): - os.mkdir(os.path.join(os.path.dirname(__file__), "..", "data"), exist_ok=True) # 确保数据目录存在 + os.makedirs(os.path.join(os.path.dirname(__file__), "..", "data"), exist_ok=True) # 确保数据目录存在 DATABASE_FILE = os.path.join(os.path.dirname(__file__), "..", "data", "NapcatAdapter.db") self.sqlite_url = f"sqlite:///{DATABASE_FILE}" # SQLite 数据库 URL self.engine = create_engine(self.sqlite_url, echo=False) # 创建数据库引擎 From 36e8f4a99a091b8c65cfabc91e34050bfa9a2830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sun, 6 Jul 2025 23:03:16 +0800 Subject: [PATCH 062/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0config=5Fback?= =?UTF-8?q?up=E5=88=B0.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f0977e5..ec98f59 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ elua.confirmed # C extensions *.so /results - +config_backup/ # Distribution / packaging .Python build/ From 93a8eec15eaca2917c412678501f495546f77665 Mon Sep 17 00:00:00 2001 From: 1334431750 <1334431750@qq.com> Date: Mon, 7 Jul 2025 05:44:29 +0000 Subject: [PATCH 063/112] =?UTF-8?q?feat:=E5=8F=91=E9=80=81=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/send_handler.py b/src/send_handler.py index e727619..70cfa79 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -175,6 +175,9 @@ def process_message_by_type(self, seg: Seg, payload: list) -> list: elif seg.type == "music": song_id = seg.data new_payload = self.build_payload(payload, self.handle_music_message(song_id), False) + elif seg.type == "videourl": + video_url = seg.data + new_payload = self.build_payload(payload, self.handle_videourl_message(video_url), False) return new_payload def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: @@ -251,6 +254,12 @@ def handle_music_message(self, song_id: str) -> dict: "type": "music", "data": {"type": "163", "id": song_id}, } + def handle_videourl_message(self, video_url: str) -> dict: + """处理视频链接消息""" + return { + "type": "video", + "data": {"file": video_url}, + } def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: """处理封禁命令 From ab9bd1c67586c8e0b9457471994abee48cbb45e3 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 7 Jul 2025 16:49:07 +0800 Subject: [PATCH 064/112] fix #49 --- command_args.md | 2 +- notify_args.md | 6 +++++- pyproject.toml | 2 +- src/__init__.py | 2 +- src/config/config.py | 2 +- src/recv_handler/notice_handler.py | 6 +++--- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/command_args.md b/command_args.md index afd5f37..3c8947d 100644 --- a/command_args.md +++ b/command_args.md @@ -57,4 +57,4 @@ Seg.data: Dict[str, Any] = { } } ``` -message_id怎么搞到手全凭你本事,也请在自己的插件里写好确定是否能撤回对应的消息的功能,毕竟这玩意真的单纯根据message_id撤消息 \ No newline at end of file +其中message_id是消息的实际qq_id,于新版的mmc中可以从数据库获取(如果工作正常的话) \ No newline at end of file diff --git a/notify_args.md b/notify_args.md index 31e1151..8a94fef 100644 --- a/notify_args.md +++ b/notify_args.md @@ -7,10 +7,12 @@ Seg.type = "notify" Seg.data: Dict[str, Any] = { "sub_type": "ban", "duration": "对应的禁言时间,单位为秒", - "banned_user_info": "被禁言的用户的信息,为标准UserInfo对象" + "banned_user_info": "被禁言的用户的信息,为标准UserInfo转换成的字典" } ``` 此时`MessageBase.UserInfo`,即消息的`UserInfo`为操作者(operator)的信息 + +**注意: `banned_user_info`需要自行调用`UserInfo.from_dict()`函数转换为标准UserInfo对象** ## 群聊开启全体禁言 ```python Seg.data: Dict[str, Any] = { @@ -30,6 +32,8 @@ Seg.data: Dict[str, Any] = { **对于自然禁言解除的情况,此时`MessageBase.UserInfo`为`None`** 对于手动解除禁言的情况,此时`MessageBase.UserInfo`,即消息的`UserInfo`为操作者(operator)的信息 + +**注意: `lifted_user_info`需要自行调用`UserInfo.from_dict()`函数转换为标准UserInfo对象** ## 群聊关闭全体禁言 ```python Seg.data: Dict[str, Any] = { diff --git a/pyproject.toml b/pyproject.toml index aa72630..6f18a98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.2" +version = "0.4.3" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/__init__.py b/src/__init__.py index b8e354e..0159c09 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -21,4 +21,4 @@ def __str__(self) -> str: pyproject_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pyproject.toml") toml_data = tomlkit.parse(open(pyproject_path, "r", encoding="utf-8").read()) version = toml_data["project"]["version"] -logger.info(f"版本\n\nMaiBot-Napcat-Adapter 版本: {version}\n喜欢的话点个star喵~\n") \ No newline at end of file +logger.info(f"版本\n\nMaiBot-Napcat-Adapter 版本: {version}\n喜欢的话点个star喵~\n") diff --git a/src/config/config.py b/src/config/config.py index 5143f39..f3b90bb 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -60,7 +60,7 @@ def update_config(): # 创建备份文件夹 backup_dir = "config_backup" os.makedirs(backup_dir, exist_ok=True) - + # 备份文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") old_backup_path = os.path.join(backup_dir, f"config.toml.bak.{timestamp}") diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index df46083..1e51ea4 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -300,7 +300,7 @@ async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg data={ "sub_type": sub_type, "duration": duration, - "banned_user_info": banned_user_info, + "banned_user_info": banned_user_info.to_dict() if banned_user_info else None, }, ) @@ -364,7 +364,7 @@ async def handle_lift_ban_notify( type="notify", data={ "sub_type": sub_type, - "lifted_user_info": lifted_user_info, + "lifted_user_info": lifted_user_info.to_dict() if lifted_user_info else None, }, ) return seg_data, operator_info @@ -463,7 +463,7 @@ async def natural_lift(self, group_id: int, user_id: int) -> Seg | None: type="notify", data={ "sub_type": "lift_ban", - "lifted_user_info": lifted_user_info, + "lifted_user_info": lifted_user_info.to_dict(), }, ) From 59ec3d4425c801a0a54effd8854d521d731d845e Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 7 Jul 2025 16:50:30 +0800 Subject: [PATCH 065/112] version update --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6f18a98..61d7c1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.3" +version = "0.4.4" description = "A MaiBot adapter for Napcat" [tool.ruff] From 9e281d838c8478abc743651fcf913cb2f5db66df Mon Sep 17 00:00:00 2001 From: Donaldzhao <1583100297@qq.com> Date: Tue, 8 Jul 2025 21:26:37 +0800 Subject: [PATCH 066/112] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8F=91=E9=80=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/send_handler.py b/src/send_handler.py index 70cfa79..cf64a44 100644 --- a/src/send_handler.py +++ b/src/send_handler.py @@ -178,6 +178,9 @@ def process_message_by_type(self, seg: Seg, payload: list) -> list: elif seg.type == "videourl": video_url = seg.data new_payload = self.build_payload(payload, self.handle_videourl_message(video_url), False) + elif seg.type == "file": + file_path = seg.data + new_payload = self.build_payload(payload, self.handle_file_message(file_path), False) return new_payload def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: @@ -261,6 +264,13 @@ def handle_videourl_message(self, video_url: str) -> dict: "data": {"file": video_url}, } + def handle_file_message(self, file_path: str) -> dict: + """处理文件消息""" + return { + "type": "file", + "data": {"file": f"file://{file_path}"}, + } + def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: """处理封禁命令 From b0b511ee9dba44cb962fcbe4d826d044684b5636 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 9 Jul 2025 19:53:42 +0800 Subject: [PATCH 067/112] accept format change --- src/recv_handler/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 0deaede..41a8f68 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -84,4 +84,4 @@ def __str__(self) -> str: return self.value -ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command"] +ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl"] From 10fc60e04d2c3ec809293257dff2bc2fcaffeab9 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 9 Jul 2025 19:55:10 +0800 Subject: [PATCH 068/112] accept format change --- src/recv_handler/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 41a8f68..422041b 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -84,4 +84,4 @@ def __str__(self) -> str: return self.value -ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl"] +ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl", "file"] From d6a97e30145422d9b24ac153f2c3f8169e202696 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 18 Jul 2025 15:58:09 +0800 Subject: [PATCH 069/112] =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index aa327e7..1bb59ba 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -422,8 +422,14 @@ async def handle_record_message(self, raw_message: dict) -> Seg | None: """ message_data: dict = raw_message.get("data") file: str = message_data.get("file") + if not file: + logger.warning("语音消息缺少文件信息") + return None try: record_detail = await get_record_detail(self.server_connection, file) + if not record_detail: + logger.warning("获取语音消息详情失败") + return None audio_base64: str = record_detail.get("base64") except Exception as e: logger.error(f"语音消息处理失败: {str(e)}") From 7b255269d176fa98c9d9ae80be02762301a03749 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 18 Jul 2025 22:32:35 +0800 Subject: [PATCH 070/112] =?UTF-8?q?=E5=85=88=E5=88=A4=E6=96=AD=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=85=81=E8=AE=B8=E4=BB=A5=E5=A2=9E=E5=8A=A0=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/recv_handler/message_handler.py | 30 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 61d7c1d..f1acff6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.4" +version = "0.4.5" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 1bb59ba..82aeaf9 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -60,20 +60,6 @@ async def check_allow_to_chat( bool: 是否允许聊天 """ logger.debug(f"群聊id: {group_id}, 用户id: {user_id}") - if global_config.chat.ban_qq_bot and group_id and not ignore_bot: - logger.debug("开始判断是否为机器人") - member_info = await get_member_info(self.server_connection, group_id, user_id) - if member_info: - is_bot = member_info.get("is_robot") - if is_bot is None: - logger.warning("无法获取用户是否为机器人,默认为不是但是不进行更新") - else: - if is_bot: - logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃,新机器人加入拦截名单") - self.bot_id_list[user_id] = True - return False - else: - self.bot_id_list[user_id] = False logger.debug("开始检查聊天白名单/黑名单") if group_id: if global_config.chat.group_list_type == "whitelist" and group_id not in global_config.chat.group_list: @@ -92,6 +78,22 @@ async def check_allow_to_chat( if user_id in global_config.chat.ban_user_id and not ignore_global_list: logger.warning("用户在全局黑名单中,消息被丢弃") return False + + if global_config.chat.ban_qq_bot and group_id and not ignore_bot: + logger.debug("开始判断是否为机器人") + member_info = await get_member_info(self.server_connection, group_id, user_id) + if member_info: + is_bot = member_info.get("is_robot") + if is_bot is None: + logger.warning("无法获取用户是否为机器人,默认为不是但是不进行更新") + else: + if is_bot: + logger.warning("QQ官方机器人消息拦截已启用,消息被丢弃,新机器人加入拦截名单") + self.bot_id_list[user_id] = True + return False + else: + self.bot_id_list[user_id] = False + return True async def handle_raw_message(self, raw_message: dict) -> None: From 81fa3aa4dfc22998f618e9541d57003777aafb7c Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 22 Jul 2025 22:10:00 +0800 Subject: [PATCH 071/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9=E5=A4=9A?= =?UTF-8?q?=E6=AC=A1=E7=A6=81=E8=A8=80=E7=9A=84=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/database.py | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f1acff6..e4abfd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.5" +version = "0.4.6" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/database.py b/src/database.py index f9a94d1..af193da 100644 --- a/src/database.py +++ b/src/database.py @@ -123,14 +123,26 @@ def create_ban_record(self, ban_record: BanUser) -> None: 其同时还是简化版的更新方式。 """ with Session(self.engine) as session: - db_record = DB_BanUser( - user_id=ban_record.user_id, group_id=ban_record.group_id, lift_time=ban_record.lift_time + # 检查记录是否已存在 + statement = select(DB_BanUser).where( + DB_BanUser.user_id == ban_record.user_id, DB_BanUser.group_id == ban_record.group_id ) - session.add(db_record) + existing_record = session.exec(statement).first() + if existing_record: + # 如果记录已存在,更新 lift_time + existing_record.lift_time = ban_record.lift_time + session.add(existing_record) + logger.debug(f"更新禁言记录: {ban_record}") + else: + # 如果记录不存在,创建新记录 + db_record = DB_BanUser( + user_id=ban_record.user_id, group_id=ban_record.group_id, lift_time=ban_record.lift_time + ) + session.add(db_record) + logger.debug(f"创建新禁言记录: {ban_record}") session.commit() - logger.debug(f"创建/更新禁言记录: {ban_record}") - def delete_ban_record(self, ban_record: BanUser) -> bool: + def delete_ban_record(self, ban_record: BanUser): """ 删除特定用户在特定群组中的禁言记录。 一个简化版本的删除方式,防止 update_ban_record 方法的复杂性。 From 991115814b31d7d84414bd0ad1cb271361bc67f5 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Thu, 31 Jul 2025 19:14:23 +0800 Subject: [PATCH 072/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E6=96=87=E4=BB=B6=E8=BF=87=E5=A4=A7=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B6=85=E6=97=B6=E6=97=B6=E9=95=BF=E9=98=B2?= =?UTF-8?q?=E7=82=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- pyproject.toml | 2 +- src/response_pool.py | 18 +++++++++--------- src/utils.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/main.py b/main.py index 07b3002..64d8c32 100644 --- a/main.py +++ b/main.py @@ -52,7 +52,7 @@ async def main(): async def napcat_server(): logger.info("正在启动adapter...") - async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port) as server: + async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port, max_size=2**26) as server: logger.info( f"Adapter已启动,监听地址: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" ) diff --git a/pyproject.toml b/pyproject.toml index e4abfd0..42e56eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.6" +version = "0.4.7" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/response_pool.py b/src/response_pool.py index c41ed7f..41feb9e 100644 --- a/src/response_pool.py +++ b/src/response_pool.py @@ -8,19 +8,19 @@ response_time_dict: Dict = {} -async def get_response(request_id: str) -> dict: - retry_count = 0 - max_retries = 50 # 10秒超时 - while request_id not in response_dict: - retry_count += 1 - if retry_count >= max_retries: - raise TimeoutError(f"请求超时,未收到响应,request_id: {request_id}") - await asyncio.sleep(0.2) - response = response_dict.pop(request_id) +async def get_response(request_id: str, timeout: int = 10) -> dict: + response = await asyncio.wait_for(_get_response(request_id), timeout) _ = response_time_dict.pop(request_id) logger.trace(f"响应信息id: {request_id} 已从响应字典中取出") return response +async def _get_response(request_id: str) -> dict: + """ + 内部使用的获取响应函数,主要用于在需要时获取响应 + """ + while request_id not in response_dict: + await asyncio.sleep(0.2) + return response_dict.pop(request_id) async def put_response(response: dict): echo_id = response.get("echo") diff --git a/src/utils.py b/src/utils.py index 6e07da4..78b0d0c 100644 --- a/src/utils.py +++ b/src/utils.py @@ -208,7 +208,7 @@ async def get_message_detail(websocket: Server.ServerConnection, message_id: Uni payload = json.dumps({"action": "get_msg", "params": {"message_id": message_id}, "echo": request_uuid}) try: await websocket.send(payload) - response: dict = await get_response(request_uuid) + response: dict = await get_response(request_uuid, 30) # 增加超时时间到30秒 except TimeoutError: logger.error(f"获取消息详情超时,消息ID: {message_id}") return None @@ -242,7 +242,7 @@ async def get_record_detail( ) try: await websocket.send(payload) - response: dict = await get_response(request_uuid) + response: dict = await get_response(request_uuid, 30) # 增加超时时间到30秒 except TimeoutError: logger.error(f"获取语音消息详情超时,文件: {file}, 文件ID: {file_id}") return None From 4debb6d7831630182d6cd651ec12d540d1a8e1a0 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Thu, 7 Aug 2025 23:12:22 +0800 Subject: [PATCH 073/112] =?UTF-8?q?=E6=89=8B=E5=8A=A8=E8=A7=A6=E5=8F=91wor?= =?UTF-8?q?kflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index fc40c97..f672672 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -3,6 +3,7 @@ name: Docker Image CI on: push: branches: [ "main", "dev" ] + workflow_dispatch: # 允许手动触发工作流 jobs: From eaa587869c71e14b1c0296dffbcbfe3f68457af8 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Tue, 12 Aug 2025 21:11:17 +0800 Subject: [PATCH 074/112] reply_message_id --- src/recv_handler/message_handler.py | 46 ++++++++++++++++------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 82aeaf9..3ce5315 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -205,10 +205,24 @@ async def handle_raw_message(self, raw_message: dict) -> None: logger.warning(f"群聊消息类型 {sub_type} 不支持") return None - additional_config: dict = {} + # 处理实际信息 + if not raw_message.get("message"): + logger.warning("原始消息内容为空") + return None + + # 获取Seg列表 + seg_message, additional_config = await self.handle_real_message(raw_message) if global_config.voice.use_tts: additional_config["allow_tts"] = True + if not seg_message: + logger.warning("处理后消息内容为空") + return None + submit_seg: Seg = Seg( + type="seglist", + data=seg_message, + ) + # 消息信息 message_info: BaseMessageInfo = BaseMessageInfo( platform=global_config.maibot_server.platform_name, @@ -221,20 +235,6 @@ async def handle_raw_message(self, raw_message: dict) -> None: additional_config=additional_config, ) - # 处理实际信息 - if not raw_message.get("message"): - logger.warning("原始消息内容为空") - return None - - # 获取Seg列表 - seg_message: List[Seg] = await self.handle_real_message(raw_message) - if not seg_message: - logger.warning("处理后消息内容为空") - return None - submit_seg: Seg = Seg( - type="seglist", - data=seg_message, - ) # MessageBase创建 message_base: MessageBase = MessageBase( message_info=message_info, @@ -245,7 +245,9 @@ async def handle_raw_message(self, raw_message: dict) -> None: logger.info("发送到Maibot处理信息") await message_send_instance.message_send(message_base) - async def handle_real_message(self, raw_message: dict, in_reply: bool = False) -> List[Seg] | None: + async def handle_real_message( + self, raw_message: dict, in_reply: bool = False + ) -> Tuple[List[Seg] | None, Dict[str, Any]]: # sourcery skip: low-code-quality """ 处理实际消息 @@ -254,6 +256,7 @@ async def handle_real_message(self, raw_message: dict, in_reply: bool = False) - Returns: seg_message: list[Seg]: 处理后的消息段列表 """ + additional_config: dict = {} real_message: list = raw_message.get("message") if not real_message: return None @@ -276,7 +279,7 @@ async def handle_real_message(self, raw_message: dict, in_reply: bool = False) - logger.warning("face处理失败或不支持") case RealMessageType.reply: if not in_reply: - ret_seg = await self.handle_reply_message(sub_message) + ret_seg, additional_config = await self.handle_reply_message(sub_message, additional_config) if ret_seg: seg_message += ret_seg else: @@ -330,7 +333,7 @@ async def handle_real_message(self, raw_message: dict, in_reply: bool = False) - logger.warning("不支持转发消息节点解析") case _: logger.warning(f"未知消息类型: {sub_message_type}") - return seg_message + return seg_message, additional_config async def handle_text_message(self, raw_message: dict) -> Seg: """ @@ -441,7 +444,7 @@ async def handle_record_message(self, raw_message: dict) -> Seg | None: return None return Seg(type="voice", data=audio_base64) - async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: + async def handle_reply_message(self, raw_message: dict, additional_config: dict) -> Tuple[List[Seg] | None, dict]: # sourcery skip: move-assign-in-block, use-named-expression """ 处理回复消息 @@ -453,11 +456,12 @@ async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: message_id = raw_message_data.get("id") else: return None + additional_config["reply_message_id"] = message_id message_detail: dict = await get_message_detail(self.server_connection, message_id) if not message_detail: logger.warning("获取被引用的消息详情失败") return None - reply_message = await self.handle_real_message(message_detail, in_reply=True) + reply_message, _ = await self.handle_real_message(message_detail, in_reply=True) if reply_message is None: reply_message = "(获取发言内容失败)" sender_info: dict = message_detail.get("sender") @@ -471,7 +475,7 @@ async def handle_reply_message(self, raw_message: dict) -> List[Seg] | None: seg_message.append(Seg(type="text", data=f"[回复<{sender_nickname}:{sender_id}>:")) seg_message += reply_message seg_message.append(Seg(type="text", data="],说:")) - return seg_message + return seg_message, additional_config async def handle_forward_message(self, message_list: list) -> Seg | None: """ From 8bf1bd15178f9b64a20720ab3da18323d1fba4f1 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 10 Sep 2025 22:44:55 +0800 Subject: [PATCH 075/112] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E7=A7=8D=E7=B1=BB=E7=9A=84=E6=B6=88=E6=81=AF=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=EF=BC=8C=E9=87=8D=E6=9E=84send=5Fhandler=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 +- pyproject.toml | 2 +- src/__init__.py | 1 + src/mmc_com_layer.py | 2 +- src/recv_handler/__init__.py | 2 +- src/send_handler.py | 461 ----------------------- src/send_handler/__init__.py | 0 src/send_handler/main_send_handler.py | 104 +++++ src/send_handler/nc_sending.py | 49 +++ src/send_handler/send_command_handler.py | 221 +++++++++++ src/send_handler/send_message_handler.py | 188 +++++++++ 11 files changed, 568 insertions(+), 466 deletions(-) delete mode 100644 src/send_handler.py create mode 100644 src/send_handler/__init__.py create mode 100644 src/send_handler/main_send_handler.py create mode 100644 src/send_handler/nc_sending.py create mode 100644 src/send_handler/send_command_handler.py create mode 100644 src/send_handler/send_message_handler.py diff --git a/main.py b/main.py index 64d8c32..424860d 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ from src.recv_handler.meta_event_handler import meta_event_handler from src.recv_handler.notice_handler import notice_handler from src.recv_handler.message_sending import message_send_instance -from src.send_handler import send_handler +from src.send_handler.nc_sending import nc_message_sender from src.config import global_config from src.mmc_com_layer import mmc_start_com, mmc_stop_com, router from src.response_pool import put_response, check_timeout_response @@ -18,7 +18,7 @@ async def message_recv(server_connection: Server.ServerConnection): await message_handler.set_server_connection(server_connection) asyncio.create_task(notice_handler.set_server_connection(server_connection)) - await send_handler.set_server_connection(server_connection) + await nc_message_sender.set_server_connection(server_connection) async for raw_message in server_connection: logger.debug(f"{raw_message[:1500]}..." if (len(raw_message) > 1500) else raw_message) decoded_raw_message: dict = json.loads(raw_message) diff --git a/pyproject.toml b/pyproject.toml index 42e56eb..50161b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.4.7" +version = "0.5.0" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/__init__.py b/src/__init__.py index 0159c09..646d0a9 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -13,6 +13,7 @@ class CommandType(Enum): SEND_POKE = "send_poke" # 戳一戳 DELETE_MSG = "delete_msg" # 撤回消息 AI_VOICE_SEND = "send_group_ai_record" # 发送群AI语音 + MESSAGE_LIKE = "message_like" # 给消息贴表情 def __str__(self) -> str: return self.value diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index 0c5a525..012d153 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -1,7 +1,7 @@ from maim_message import Router, RouteConfig, TargetConfig from .config import global_config from .logger import logger, custom_logger -from .send_handler import send_handler +from .send_handler.main_send_handler import send_handler route_config = RouteConfig( route_config={ diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 422041b..3f342fd 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -84,4 +84,4 @@ def __str__(self) -> str: return self.value -ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl", "file"] +ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl", "file", "forward"] diff --git a/src/send_handler.py b/src/send_handler.py deleted file mode 100644 index cf64a44..0000000 --- a/src/send_handler.py +++ /dev/null @@ -1,461 +0,0 @@ -import json -import websockets as Server -import uuid -from maim_message import ( - UserInfo, - GroupInfo, - Seg, - BaseMessageInfo, - MessageBase, -) -from typing import Dict, Any, Tuple - -from . import CommandType -from .config import global_config -from .response_pool import get_response -from .logger import logger -from .utils import get_image_format, convert_image_to_gif -from .recv_handler.message_sending import message_send_instance - - -class SendHandler: - def __init__(self): - self.server_connection: Server.ServerConnection = None - - async def set_server_connection(self, server_connection: Server.ServerConnection) -> None: - """设置Napcat连接""" - self.server_connection = server_connection - - async def handle_message(self, raw_message_base_dict: dict) -> None: - raw_message_base: MessageBase = MessageBase.from_dict(raw_message_base_dict) - message_segment: Seg = raw_message_base.message_segment - logger.info("接收到来自MaiBot的消息,处理中") - if message_segment.type == "command": - return await self.send_command(raw_message_base) - else: - return await self.send_normal_message(raw_message_base) - - async def send_normal_message(self, raw_message_base: MessageBase) -> None: - """ - 处理普通消息发送 - """ - logger.info("处理普通信息中") - message_info: BaseMessageInfo = raw_message_base.message_info - message_segment: Seg = raw_message_base.message_segment - group_info: GroupInfo = message_info.group_info - user_info: UserInfo = message_info.user_info - target_id: int = None - action: str = None - id_name: str = None - processed_message: list = [] - try: - processed_message = await self.handle_seg_recursive(message_segment) - except Exception as e: - logger.error(f"处理消息时发生错误: {e}") - return - - if not processed_message: - logger.critical("现在暂时不支持解析此回复!") - return None - - if group_info and user_info: - logger.debug("发送群聊消息") - target_id = group_info.group_id - action = "send_group_msg" - id_name = "group_id" - elif user_info: - logger.debug("发送私聊消息") - target_id = user_info.user_id - action = "send_private_msg" - id_name = "user_id" - else: - logger.error("无法识别的消息类型") - return - logger.info("尝试发送到napcat") - response = await self.send_message_to_napcat( - action, - { - id_name: target_id, - "message": processed_message, - }, - ) - if response.get("status") == "ok": - logger.info("消息发送成功") - qq_message_id = response.get("data", {}).get("message_id") - await self.message_sent_back(raw_message_base, qq_message_id) - else: - logger.warning(f"消息发送失败,napcat返回:{str(response)}") - - async def send_command(self, raw_message_base: MessageBase) -> None: - """ - 处理命令类 - """ - logger.info("处理命令中") - message_info: BaseMessageInfo = raw_message_base.message_info - message_segment: Seg = raw_message_base.message_segment - group_info: GroupInfo = message_info.group_info - seg_data: Dict[str, Any] = message_segment.data - command_name: str = seg_data.get("name") - try: - match command_name: - case CommandType.GROUP_BAN.name: - command, args_dict = self.handle_ban_command(seg_data.get("args"), group_info) - case CommandType.GROUP_WHOLE_BAN.name: - command, args_dict = self.handle_whole_ban_command(seg_data.get("args"), group_info) - case CommandType.GROUP_KICK.name: - command, args_dict = self.handle_kick_command(seg_data.get("args"), group_info) - case CommandType.SEND_POKE.name: - command, args_dict = self.handle_poke_command(seg_data.get("args"), group_info) - case CommandType.DELETE_MSG.name: - command, args_dict = self.delete_msg_command(seg_data.get("args")) - case CommandType.AI_VOICE_SEND.name: - command, args_dict = self.handle_ai_voice_send_command(seg_data.get("args"), group_info) - case _: - logger.error(f"未知命令: {command_name}") - return - except Exception as e: - logger.error(f"处理命令时发生错误: {e}") - return None - - if not command or not args_dict: - logger.error("命令或参数缺失") - return None - - response = await self.send_message_to_napcat(command, args_dict) - if response.get("status") == "ok": - logger.info(f"命令 {command_name} 执行成功") - else: - logger.warning(f"命令 {command_name} 执行失败,napcat返回:{str(response)}") - - def get_level(self, seg_data: Seg) -> int: - if seg_data.type == "seglist": - return 1 + max(self.get_level(seg) for seg in seg_data.data) - else: - return 1 - - async def handle_seg_recursive(self, seg_data: Seg) -> list: - payload: list = [] - if seg_data.type == "seglist": - # level = self.get_level(seg_data) # 给以后可能的多层嵌套做准备,此处不使用 - if not seg_data.data: - return [] - for seg in seg_data.data: - payload = self.process_message_by_type(seg, payload) - else: - payload = self.process_message_by_type(seg_data, payload) - return payload - - def process_message_by_type(self, seg: Seg, payload: list) -> list: - # sourcery skip: reintroduce-else, swap-if-else-branches, use-named-expression - new_payload = payload - if seg.type == "reply": - target_id = seg.data - if target_id == "notice": - return payload - new_payload = self.build_payload(payload, self.handle_reply_message(target_id), True) - elif seg.type == "text": - text = seg.data - if not text: - return payload - new_payload = self.build_payload(payload, self.handle_text_message(text), False) - elif seg.type == "face": - logger.warning("MaiBot 发送了qq原生表情,暂时不支持") - elif seg.type == "image": - image = seg.data - new_payload = self.build_payload(payload, self.handle_image_message(image), False) - elif seg.type == "emoji": - emoji = seg.data - new_payload = self.build_payload(payload, self.handle_emoji_message(emoji), False) - elif seg.type == "voice": - voice = seg.data - new_payload = self.build_payload(payload, self.handle_voice_message(voice), False) - elif seg.type == "voiceurl": - voice_url = seg.data - new_payload = self.build_payload(payload, self.handle_voiceurl_message(voice_url), False) - elif seg.type == "music": - song_id = seg.data - new_payload = self.build_payload(payload, self.handle_music_message(song_id), False) - elif seg.type == "videourl": - video_url = seg.data - new_payload = self.build_payload(payload, self.handle_videourl_message(video_url), False) - elif seg.type == "file": - file_path = seg.data - new_payload = self.build_payload(payload, self.handle_file_message(file_path), False) - return new_payload - - def build_payload(self, payload: list, addon: dict, is_reply: bool = False) -> list: - # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator - """构建发送的消息体""" - if is_reply: - temp_list = [] - temp_list.append(addon) - for i in payload: - if i.get("type") == "reply": - logger.debug("检测到多个回复,使用最新的回复") - continue - temp_list.append(i) - return temp_list - else: - payload.append(addon) - return payload - - def handle_reply_message(self, id: str) -> dict: - """处理回复消息""" - return {"type": "reply", "data": {"id": id}} - - def handle_text_message(self, message: str) -> dict: - """处理文本消息""" - return {"type": "text", "data": {"text": message}} - - def handle_image_message(self, encoded_image: str) -> dict: - """处理图片消息""" - return { - "type": "image", - "data": { - "file": f"base64://{encoded_image}", - "subtype": 0, - }, - } # base64 编码的图片 - - def handle_emoji_message(self, encoded_emoji: str) -> dict: - """处理表情消息""" - encoded_image = encoded_emoji - image_format = get_image_format(encoded_emoji) - if image_format != "gif": - encoded_image = convert_image_to_gif(encoded_emoji) - return { - "type": "image", - "data": { - "file": f"base64://{encoded_image}", - "subtype": 1, - "summary": "[动画表情]", - }, - } - - def handle_voice_message(self, encoded_voice: str) -> dict: - """处理语音消息""" - if not global_config.voice.use_tts: - logger.warning("未启用语音消息处理") - return {} - if not encoded_voice: - return {} - return { - "type": "record", - "data": {"file": f"base64://{encoded_voice}"}, - } - - def handle_voiceurl_message(self, voice_url: str) -> dict: - """处理语音链接消息""" - return { - "type": "record", - "data": {"file": voice_url}, - } - - def handle_music_message(self, song_id: str) -> dict: - """处理音乐消息""" - return { - "type": "music", - "data": {"type": "163", "id": song_id}, - } - def handle_videourl_message(self, video_url: str) -> dict: - """处理视频链接消息""" - return { - "type": "video", - "data": {"file": video_url}, - } - - def handle_file_message(self, file_path: str) -> dict: - """处理文件消息""" - return { - "type": "file", - "data": {"file": f"file://{file_path}"}, - } - - def handle_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理封禁命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - duration: int = int(args["duration"]) - user_id: int = int(args["qq_id"]) - group_id: int = int(group_info.group_id) - if duration < 0: - raise ValueError("封禁时间必须大于等于0") - if not user_id or not group_id: - raise ValueError("封禁命令缺少必要参数") - if duration > 2592000: - raise ValueError("封禁时间不能超过30天") - return ( - CommandType.GROUP_BAN.value, - { - "group_id": group_id, - "user_id": user_id, - "duration": duration, - }, - ) - - def handle_whole_ban_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理全体禁言命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - enable = args["enable"] - assert isinstance(enable, bool), "enable参数必须是布尔值" - group_id: int = int(group_info.group_id) - if group_id <= 0: - raise ValueError("群组ID无效") - return ( - CommandType.GROUP_WHOLE_BAN.value, - { - "group_id": group_id, - "enable": enable, - }, - ) - - def handle_kick_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理群成员踢出命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - user_id: int = int(args["qq_id"]) - group_id: int = int(group_info.group_id) - if group_id <= 0: - raise ValueError("群组ID无效") - if user_id <= 0: - raise ValueError("用户ID无效") - return ( - CommandType.GROUP_KICK.value, - { - "group_id": group_id, - "user_id": user_id, - "reject_add_request": False, # 不拒绝加群请求 - }, - ) - - def handle_poke_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """处理戳一戳命令 - - Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - user_id: int = int(args["qq_id"]) - if group_info is None: - group_id = None - else: - group_id: int = int(group_info.group_id) - if group_id <= 0: - raise ValueError("群组ID无效") - if user_id <= 0: - raise ValueError("用户ID无效") - return ( - CommandType.SEND_POKE.value, - { - "group_id": group_id, - "user_id": user_id, - }, - ) - - def delete_msg_command(self, args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: - """处理撤回消息命令 - - Args: - args (Dict[str, Any]): 参数字典 - - Returns: - Tuple[CommandType, Dict[str, Any]] - """ - try: - message_id = int(args["message_id"]) - if message_id <= 0: - raise ValueError("消息ID无效") - except KeyError: - raise ValueError("缺少必需参数: message_id") from None - except (ValueError, TypeError) as e: - raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") from None - - return ( - CommandType.DELETE_MSG.value, - { - "message_id": message_id, - }, - ) - - def handle_ai_voice_send_command(self, args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """ - 处理AI语音发送命令的逻辑。 - 并返回 NapCat 兼容的 (action, params) 元组。 - """ - if not group_info or not group_info.group_id: - raise ValueError("AI语音发送命令必须在群聊上下文中使用") - if not args: - raise ValueError("AI语音发送命令缺少参数") - - group_id: int = int(group_info.group_id) - character_id = args.get("character") - text_content = args.get("text") - - if not character_id or not text_content: - raise ValueError(f"AI语音发送命令参数不完整: character='{character_id}', text='{text_content}'") - - return ( - CommandType.AI_VOICE_SEND.value, - { - "group_id": group_id, - "text": text_content, - "character": character_id, - }, - ) - - async def send_message_to_napcat(self, action: str, params: dict) -> dict: - request_uuid = str(uuid.uuid4()) - payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) - await self.server_connection.send(payload) - try: - response = await get_response(request_uuid) - except TimeoutError: - logger.error("发送消息超时,未收到响应") - return {"status": "error", "message": "timeout"} - except Exception as e: - logger.error(f"发送消息失败: {e}") - return {"status": "error", "message": str(e)} - return response - - async def message_sent_back(self, message_base: MessageBase, qq_message_id: str) -> None: - # 修改 additional_config,添加 echo 字段 - if message_base.message_info.additional_config is None: - message_base.message_info.additional_config = {} - - message_base.message_info.additional_config["echo"] = True - - # 获取原始的 mmc_message_id - mmc_message_id = message_base.message_info.message_id - - # 修改 message_segment 为 notify 类型 - message_base.message_segment = Seg( - type="notify", data={"sub_type": "echo", "echo": mmc_message_id, "actual_id": qq_message_id} - ) - await message_send_instance.message_send(message_base) - logger.debug("已回送消息ID") - return - - -send_handler = SendHandler() diff --git a/src/send_handler/__init__.py b/src/send_handler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/send_handler/main_send_handler.py b/src/send_handler/main_send_handler.py new file mode 100644 index 0000000..8cce8a9 --- /dev/null +++ b/src/send_handler/main_send_handler.py @@ -0,0 +1,104 @@ +from typing import Any, Dict +from maim_message import ( + UserInfo, + GroupInfo, + Seg, + BaseMessageInfo, + MessageBase, +) +from src.logger import logger +from .send_command_handler import SendCommandHandleClass +from .send_message_handler import SendMessageHandleClass +from .nc_sending import nc_message_sender + + +class SendHandler: + def __init__(self): + pass + + async def handle_message(self, raw_message_base_dict: dict) -> None: + raw_message_base: MessageBase = MessageBase.from_dict(raw_message_base_dict) + message_segment: Seg = raw_message_base.message_segment + logger.info("接收到来自MaiBot的消息,处理中") + if message_segment.type == "command": + return await self.send_command(raw_message_base) + else: + return await self.send_normal_message(raw_message_base) + + async def send_command(self, raw_message_base: MessageBase) -> None: + """ + 处理命令类 + """ + logger.info("处理命令中") + message_info: BaseMessageInfo = raw_message_base.message_info + message_segment: Seg = raw_message_base.message_segment + group_info: GroupInfo = message_info.group_info + seg_data: Dict[str, Any] = message_segment.data + try: + command, args_dict = SendCommandHandleClass.handle_command(seg_data, group_info) + except Exception as e: + logger.error(f"处理命令时出错: {str(e)}") + return + + if not command or not args_dict: + logger.error("命令或参数缺失") + return None + + response = await nc_message_sender.send_message_to_napcat(command, args_dict) + if response.get("status") == "ok": + logger.info(f"命令 {seg_data.get('name')} 执行成功") + else: + logger.warning(f"命令 {seg_data.get('name')} 执行失败,napcat返回:{str(response)}") + + async def send_normal_message(self, raw_message_base: MessageBase) -> None: + """ + 处理普通消息发送 + """ + logger.info("处理普通信息中") + message_info: BaseMessageInfo = raw_message_base.message_info + message_segment: Seg = raw_message_base.message_segment + group_info: GroupInfo = message_info.group_info + user_info: UserInfo = message_info.user_info + target_id: int = None + action: str = None + id_name: str = None + processed_message: list = [] + try: + processed_message = SendMessageHandleClass.process_seg_recursive(message_segment) + except Exception as e: + logger.error(f"处理消息时发生错误: {e}") + return + + if not processed_message: + logger.critical("现在暂时不支持解析此回复!") + return None + + if group_info and user_info: + logger.debug("发送群聊消息") + target_id = group_info.group_id + action = "send_group_msg" + id_name = "group_id" + elif user_info: + logger.debug("发送私聊消息") + target_id = user_info.user_id + action = "send_private_msg" + id_name = "user_id" + else: + logger.error("无法识别的消息类型") + return + logger.info("尝试发送到napcat") + response = await nc_message_sender.send_message_to_napcat( + action, + { + id_name: target_id, + "message": processed_message, + }, + ) + if response.get("status") == "ok": + logger.info("消息发送成功") + qq_message_id = response.get("data", {}).get("message_id") + await nc_message_sender.message_sent_back(raw_message_base, qq_message_id) + else: + logger.warning(f"消息发送失败,napcat返回:{str(response)}") + +send_handler = SendHandler() \ No newline at end of file diff --git a/src/send_handler/nc_sending.py b/src/send_handler/nc_sending.py new file mode 100644 index 0000000..e9f45ca --- /dev/null +++ b/src/send_handler/nc_sending.py @@ -0,0 +1,49 @@ +import json +import uuid +import websockets as Server +from maim_message import MessageBase, Seg + +from src.response_pool import get_response +from src.logger import logger +from src.recv_handler.message_sending import message_send_instance + +class NCMessageSender: + def __init__(self): + self.server_connection: Server.ServerConnection = None + + async def set_server_connection(self, connection: Server.ServerConnection): + self.server_connection = connection + + async def send_message_to_napcat(self, action: str, params: dict) -> dict: + request_uuid = str(uuid.uuid4()) + payload = json.dumps({"action": action, "params": params, "echo": request_uuid}) + await self.server_connection.send(payload) + try: + response = await get_response(request_uuid) + except TimeoutError: + logger.error("发送消息超时,未收到响应") + return {"status": "error", "message": "timeout"} + except Exception as e: + logger.error(f"发送消息失败: {e}") + return {"status": "error", "message": str(e)} + return response + + async def message_sent_back(self, message_base: MessageBase, qq_message_id: str) -> None: + # 修改 additional_config,添加 echo 字段 + if message_base.message_info.additional_config is None: + message_base.message_info.additional_config = {} + + message_base.message_info.additional_config["echo"] = True + + # 获取原始的 mmc_message_id + mmc_message_id = message_base.message_info.message_id + + # 修改 message_segment 为 notify 类型 + message_base.message_segment = Seg( + type="notify", data={"sub_type": "echo", "echo": mmc_message_id, "actual_id": qq_message_id} + ) + await message_send_instance.message_send(message_base) + logger.debug("已回送消息ID") + return + +nc_message_sender = NCMessageSender() \ No newline at end of file diff --git a/src/send_handler/send_command_handler.py b/src/send_handler/send_command_handler.py new file mode 100644 index 0000000..dff9505 --- /dev/null +++ b/src/send_handler/send_command_handler.py @@ -0,0 +1,221 @@ +from maim_message import GroupInfo +from typing import Any, Dict, Tuple + +from src import CommandType + + +class SendCommandHandleClass: + @classmethod + def handle_command(cls, raw_command_data: Dict[str, Any], group_info: GroupInfo): + command_name: str = raw_command_data.get("name") + try: + match command_name: + case CommandType.GROUP_BAN.name: + return cls.handle_ban_command(raw_command_data.get("args", {}), group_info) + case CommandType.GROUP_WHOLE_BAN.name: + return cls.handle_whole_ban_command(raw_command_data.get("args", {}), group_info) + case CommandType.GROUP_KICK.name: + return cls.handle_kick_command(raw_command_data.get("args", {}), group_info) + case CommandType.SEND_POKE.name: + return cls.handle_poke_command(raw_command_data.get("args", {}), group_info) + case CommandType.DELETE_MSG.name: + return cls.delete_msg_command(raw_command_data.get("args", {})) + case CommandType.AI_VOICE_SEND.name: + return cls.handle_ai_voice_send_command(raw_command_data.get("args", {}), group_info) + case CommandType.MESSAGE_LIKE.name: + return cls.handle_message_like_command(raw_command_data.get("args", {})) + case _: + raise RuntimeError(f"未知的命令类型: {command_name}") + except Exception as e: + raise RuntimeError(f"处理命令时出错: {str(e)}") from e + + @staticmethod + def handle_ban_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理封禁命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + duration: int = int(args["duration"]) + user_id: int = int(args["qq_id"]) + group_id: int = int(group_info.group_id) + if duration < 0: + raise ValueError("封禁时间必须大于等于0") + if not user_id or not group_id: + raise ValueError("封禁命令缺少必要参数") + if duration > 2592000: + raise ValueError("封禁时间不能超过30天") + return ( + CommandType.GROUP_BAN.value, + { + "group_id": group_id, + "user_id": user_id, + "duration": duration, + }, + ) + + @staticmethod + def handle_whole_ban_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理全体禁言命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + enable = args["enable"] + assert isinstance(enable, bool), "enable参数必须是布尔值" + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + return ( + CommandType.GROUP_WHOLE_BAN.value, + { + "group_id": group_id, + "enable": enable, + }, + ) + + @staticmethod + def handle_kick_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理群成员踢出命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + user_id: int = int(args["qq_id"]) + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + if user_id <= 0: + raise ValueError("用户ID无效") + return ( + CommandType.GROUP_KICK.value, + { + "group_id": group_id, + "user_id": user_id, + "reject_add_request": False, # 不拒绝加群请求 + }, + ) + + @staticmethod + def handle_poke_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """处理戳一戳命令 + + Args: + args (Dict[str, Any]): 参数字典 + group_info (GroupInfo): 群聊信息(对应目标群聊) + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + user_id: int = int(args["qq_id"]) + if group_info is None: + group_id = None + else: + group_id: int = int(group_info.group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + if user_id <= 0: + raise ValueError("用户ID无效") + return ( + CommandType.SEND_POKE.value, + { + "group_id": group_id, + "user_id": user_id, + }, + ) + + @staticmethod + def delete_msg_command(args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: + """处理撤回消息命令 + + Args: + args (Dict[str, Any]): 参数字典 + + Returns: + Tuple[CommandType, Dict[str, Any]] + """ + try: + message_id = int(args["message_id"]) + if message_id <= 0: + raise ValueError("消息ID无效") + except KeyError: + raise ValueError("缺少必需参数: message_id") from None + except (ValueError, TypeError) as e: + raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") from None + + return ( + CommandType.DELETE_MSG.value, + { + "message_id": message_id, + }, + ) + + @staticmethod + def handle_ai_voice_send_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + """ + 处理AI语音发送命令的逻辑。 + 并返回 NapCat 兼容的 (action, params) 元组。 + """ + if not group_info or not group_info.group_id: + raise ValueError("AI语音发送命令必须在群聊上下文中使用") + if not args: + raise ValueError("AI语音发送命令缺少参数") + + group_id: int = int(group_info.group_id) + character_id = args.get("character") + text_content = args.get("text") + + if not character_id or not text_content: + raise ValueError(f"AI语音发送命令参数不完整: character='{character_id}', text='{text_content}'") + + return ( + CommandType.AI_VOICE_SEND.value, + { + "group_id": group_id, + "text": text_content, + "character": character_id, + }, + ) + + @staticmethod + def handle_message_like_command(args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: + """ + 处理给消息贴表情的逻辑。 + """ + if not args: + raise ValueError("消息贴表情命令缺少参数") + + message_id = args.get("message_id") + emoji_id = args.get("emoji_id") + if not message_id: + raise ValueError("消息贴表情命令缺少必要参数: message_id") + if not emoji_id: + raise ValueError("消息贴表情命令缺少必要参数: emoji_id") + + message_id = int(message_id) + emoji_id = int(emoji_id) + if message_id <= 0: + raise ValueError("消息ID无效") + if emoji_id <= 0: + raise ValueError("表情ID无效") + + return ( + CommandType.MESSAGE_LIKE.value, + { + "message_id": message_id, + "emoji_id": emoji_id, + "set": True, + }, + ) diff --git a/src/send_handler/send_message_handler.py b/src/send_handler/send_message_handler.py new file mode 100644 index 0000000..308b688 --- /dev/null +++ b/src/send_handler/send_message_handler.py @@ -0,0 +1,188 @@ +from maim_message import Seg, MessageBase +from typing import List, Dict + +from src.logger import logger +from src.config import global_config +from src.utils import get_image_format, convert_image_to_gif + + +class SendMessageHandleClass: + @classmethod + def parse_seg_to_nc_format(cls, message_segment: Seg): + parsed_payload: List = cls.process_seg_recursive(message_segment) + return parsed_payload + + @classmethod + def process_seg_recursive(cls, seg_data: Seg, in_forward: bool = False) -> List: + payload: List = [] + if seg_data.type == "seglist": + if not seg_data.data: + return [] + for seg in seg_data.data: + payload = cls.process_message_by_type(seg, payload, in_forward) + else: + payload = cls.process_message_by_type(seg_data, payload, in_forward) + return payload + + @classmethod + def process_message_by_type(cls, seg: Seg, payload: List, in_forward: bool = False) -> List: + # sourcery skip: for-append-to-extend, reintroduce-else, swap-if-else-branches, use-named-expression + new_payload = payload + if seg.type == "reply": + target_id = seg.data + if target_id == "notice": + return payload + new_payload = cls.build_payload(payload, cls.handle_reply_message(target_id), True) + elif seg.type == "text": + text = seg.data + if not text: + return payload + new_payload = cls.build_payload(payload, cls.handle_text_message(text), False) + elif seg.type == "face": + face_id = seg.data + new_payload = cls.build_payload(payload, cls.handle_native_face_message(face_id), False) + elif seg.type == "image": + image = seg.data + new_payload = cls.build_payload(payload, cls.handle_image_message(image), False) + elif seg.type == "emoji": + emoji = seg.data + new_payload = cls.build_payload(payload, cls.handle_emoji_message(emoji), False) + elif seg.type == "voice": + voice = seg.data + new_payload = cls.build_payload(payload, cls.handle_voice_message(voice), False) + elif seg.type == "voiceurl": + voice_url = seg.data + new_payload = cls.build_payload(payload, cls.handle_voiceurl_message(voice_url), False) + elif seg.type == "music": + song_id = seg.data + new_payload = cls.build_payload(payload, cls.handle_music_message(song_id), False) + elif seg.type == "videourl": + video_url = seg.data + new_payload = cls.build_payload(payload, cls.handle_videourl_message(video_url), False) + elif seg.type == "file": + file_path = seg.data + new_payload = cls.build_payload(payload, cls.handle_file_message(file_path), False) + elif seg.type == "forward" and not in_forward: + forward_message_content: List[Dict] = seg.data + new_payload: List[Dict] = [ + cls.handle_forward_message(MessageBase.from_dict(item)) for item in forward_message_content + ] # 转发消息不能和其他消息一起发送 + return new_payload + + @classmethod + def handle_forward_message(cls, item: MessageBase) -> Dict: + # sourcery skip: remove-unnecessary-else + message_segment: Seg = item.message_segment + if message_segment.type == "id": + return {"type": "node", "data": {"id": message_segment.data}} + else: + user_info = item.message_info.user_info + content = cls.process_seg_recursive(message_segment.data, True) + return { + "type": "node", + "data": {"name": user_info.user_nickname or "QQ用户", "uin": user_info.user_id, "content": content}, + } + + @staticmethod + def build_payload(payload: List, addon: dict, is_reply: bool = False) -> List: + # sourcery skip: for-append-to-extend, merge-list-append, simplify-generator + if is_reply: + temp_list = [] + temp_list.append(addon) + for i in payload: + if i.get("type") == "reply": + logger.debug("检测到多个回复,使用最新的回复") + continue + temp_list.append(i) + return temp_list + else: + payload.append(addon) + return payload + + @staticmethod + def handle_reply_message(id: str) -> dict: + """处理回复消息""" + return {"type": "reply", "data": {"id": id}} + + @staticmethod + def handle_text_message(message: str) -> dict: + """处理文本消息""" + return {"type": "text", "data": {"text": message}} + + @staticmethod + def handle_native_face_message(face_id: int) -> dict: + # sourcery skip: remove-unnecessary-cast + """处理原生表情消息""" + return {"type": "face", "data": {"id": int(face_id)}} + + @staticmethod + def handle_image_message(encoded_image: str) -> dict: + """处理图片消息""" + return { + "type": "image", + "data": { + "file": f"base64://{encoded_image}", + "subtype": 0, + }, + } # base64 编码的图片 + + @staticmethod + def handle_emoji_message(encoded_emoji: str) -> dict: + """处理表情消息""" + encoded_image = encoded_emoji + image_format = get_image_format(encoded_emoji) + if image_format != "gif": + encoded_image = convert_image_to_gif(encoded_emoji) + return { + "type": "image", + "data": { + "file": f"base64://{encoded_image}", + "subtype": 1, + "summary": "[动画表情]", + }, + } + + @staticmethod + def handle_voice_message(encoded_voice: str) -> dict: + """处理语音消息""" + if not global_config.voice.use_tts: + logger.warning("未启用语音消息处理") + return {} + if not encoded_voice: + return {} + return { + "type": "record", + "data": {"file": f"base64://{encoded_voice}"}, + } + + @staticmethod + def handle_voiceurl_message(voice_url: str) -> dict: + """处理语音链接消息""" + return { + "type": "record", + "data": {"file": voice_url}, + } + + @staticmethod + def handle_music_message(song_id: str) -> dict: + """处理音乐消息""" + return { + "type": "music", + "data": {"type": "163", "id": song_id}, + } + + @staticmethod + def handle_videourl_message(video_url: str) -> dict: + """处理视频链接消息""" + return { + "type": "video", + "data": {"file": video_url}, + } + + @staticmethod + def handle_file_message(file_path: str) -> dict: + """处理文件消息""" + return { + "type": "file", + "data": {"file": f"file://{file_path}"}, + } From eaee8a45fb93ecbaeb3dfafdd1725cc6de7a4f96 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 10 Sep 2025 22:47:58 +0800 Subject: [PATCH 076/112] manually cherry-pick commit of imageurl to dev --- src/recv_handler/__init__.py | 15 ++++++++++++++- src/send_handler/send_message_handler.py | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 3f342fd..c6accec 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -84,4 +84,17 @@ def __str__(self) -> str: return self.value -ACCEPT_FORMAT = ["text", "image", "emoji", "reply", "voice", "command", "voiceurl", "music", "videourl", "file", "forward"] +ACCEPT_FORMAT = [ + "text", + "image", + "emoji", + "reply", + "voice", + "command", + "voiceurl", + "music", + "videourl", + "file", + "imageurl", + "forward", +] diff --git a/src/send_handler/send_message_handler.py b/src/send_handler/send_message_handler.py index 308b688..db80c29 100644 --- a/src/send_handler/send_message_handler.py +++ b/src/send_handler/send_message_handler.py @@ -62,6 +62,9 @@ def process_message_by_type(cls, seg: Seg, payload: List, in_forward: bool = Fal elif seg.type == "file": file_path = seg.data new_payload = cls.build_payload(payload, cls.handle_file_message(file_path), False) + elif seg.type == "imageurl": + image_url = seg.data + new_payload = cls.build_payload(payload, cls.handle_imageurl_message(image_url), False) elif seg.type == "forward" and not in_forward: forward_message_content: List[Dict] = seg.data new_payload: List[Dict] = [ @@ -186,3 +189,11 @@ def handle_file_message(file_path: str) -> dict: "type": "file", "data": {"file": f"file://{file_path}"}, } + + @staticmethod + def handle_imageurl_message(image_url: str) -> dict: + """处理图片链接消息""" + return { + "type": "image", + "data": {"file": image_url}, + } From f165cf3e2e5c78c9194e9611b4a67d9ab2b8ed70 Mon Sep 17 00:00:00 2001 From: CKylinMC Date: Thu, 11 Sep 2025 18:35:33 +0800 Subject: [PATCH 077/112] feat: add napcat token verify --- main.py | 11 ++++++++++- src/config/official_configs.py | 3 +++ template/template_config.toml | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 424860d..66525af 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import asyncio import sys import json +import http import websockets as Server from src.logger import logger from src.recv_handler.message_handler import message_handler @@ -49,10 +50,18 @@ async def main(): message_send_instance.maibot_router = router _ = await asyncio.gather(napcat_server(), mmc_start_com(), message_process(), check_timeout_response()) +def check_napcat_server_token(conn, request): + token = global_config.napcat_server.token + if not token or token.strip() == "": + return None + auth_header = request.headers.get("Authorization") + if auth_header != f"Bearer {token}": + return Server.Response(http.HTTPStatus.UNAUTHORIZED, "", headers=Server.Headers([("Content-Type", "text/plain")]), body=b"Unauthorized\n") + return None async def napcat_server(): logger.info("正在启动adapter...") - async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port, max_size=2**26) as server: + async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port, max_size=2**26, process_request=check_napcat_server_token) as server: logger.info( f"Adapter已启动,监听地址: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" ) diff --git a/src/config/official_configs.py b/src/config/official_configs.py index d652f35..98b4552 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -28,6 +28,9 @@ class NapcatServerConfig(ConfigBase): port: int = 8095 """Napcat服务端的端口号""" + token: str = "" + """Napcat服务端的访问令牌,若无则留空""" + heartbeat_interval: int = 30 """Napcat心跳间隔时间,单位为秒""" diff --git a/template/template_config.toml b/template/template_config.toml index 06ac657..84a7a12 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -8,6 +8,7 @@ nickname = "" [napcat_server] # Napcat连接的ws服务设置 host = "localhost" # Napcat设定的主机地址 port = 8095 # Napcat设定的端口 +token = "" # Napcat设定的访问令牌,若无则留空 heartbeat_interval = 30 # 与Napcat设置的心跳相同(按秒计) [maibot_server] # 连接麦麦的ws服务设置 From 86d7b67cd3609c28598259a7c69505f3839cd76b Mon Sep 17 00:00:00 2001 From: Zuole <3087033824@qq.com> Date: Sat, 13 Sep 2025 13:15:09 +0800 Subject: [PATCH 078/112] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=8F=91=E9=80=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/send_handler/send_message_handler.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/send_handler/send_message_handler.py b/src/send_handler/send_message_handler.py index db80c29..9e02391 100644 --- a/src/send_handler/send_message_handler.py +++ b/src/send_handler/send_message_handler.py @@ -65,6 +65,9 @@ def process_message_by_type(cls, seg: Seg, payload: List, in_forward: bool = Fal elif seg.type == "imageurl": image_url = seg.data new_payload = cls.build_payload(payload, cls.handle_imageurl_message(image_url), False) + elif seg.type == "video": + video_path = seg.data + new_payload = cls.build_payload(payload, cls.handle_video_message(video_path), False) elif seg.type == "forward" and not in_forward: forward_message_content: List[Dict] = seg.data new_payload: List[Dict] = [ @@ -197,3 +200,17 @@ def handle_imageurl_message(image_url: str) -> dict: "type": "image", "data": {"file": image_url}, } + + @staticmethod + def handle_video_message(encoded_video: str) -> dict: + """处理视频消息(base64格式)""" + if not encoded_video: + logger.error("视频数据为空") + return {} + + logger.info(f"处理视频消息,数据长度: {len(encoded_video)} 字符") + + return { + "type": "video", + "data": {"file": f"base64://{encoded_video}"}, + } From 0584ac70c653c94bb63c5661bae034316cb2591e Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 13 Sep 2025 13:35:15 +0800 Subject: [PATCH 079/112] accept video --- src/recv_handler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index c6accec..767ae77 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -97,4 +97,5 @@ def __str__(self) -> str: "file", "imageurl", "forward", + "video", ] From 8ac7d023620f61bfcd390e31d184c4fffc1cafe5 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer <53261159+UnCLAS-Prommer@users.noreply.github.com> Date: Sat, 13 Sep 2025 13:42:44 +0800 Subject: [PATCH 080/112] Update main.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 66525af..6f824a6 100644 --- a/main.py +++ b/main.py @@ -56,7 +56,11 @@ def check_napcat_server_token(conn, request): return None auth_header = request.headers.get("Authorization") if auth_header != f"Bearer {token}": - return Server.Response(http.HTTPStatus.UNAUTHORIZED, "", headers=Server.Headers([("Content-Type", "text/plain")]), body=b"Unauthorized\n") + return Server.Response( + status=http.HTTPStatus.UNAUTHORIZED, + headers=Server.Headers([("Content-Type", "text/plain")]), + body=b"Unauthorized\n" + ) return None async def napcat_server(): From b4600793b95e6964679c3099a003399f85660857 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 13 Sep 2025 13:44:49 +0800 Subject: [PATCH 081/112] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- template/template_config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/template_config.toml b/template/template_config.toml index 84a7a12..63b55ae 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -1,5 +1,5 @@ [inner] -version = "0.1.1" # 版本号 +version = "0.1.2" # 版本号 # 请勿修改版本号,除非你知道自己在做什么 [nickname] # 现在没用 From ca27153fe6a319275f51a902ab85959503ea593e Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 14 Sep 2025 00:04:13 +0800 Subject: [PATCH 082/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BD=AC=E5=8F=91?= =?UTF-8?q?=E7=9A=84=E5=B0=8F=E9=97=AE=E9=A2=98=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/send_handler/send_message_handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 50161b0..ba0cb66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.5.0" +version = "0.5.4" description = "A MaiBot adapter for Napcat" [tool.ruff] diff --git a/src/send_handler/send_message_handler.py b/src/send_handler/send_message_handler.py index 9e02391..089353b 100644 --- a/src/send_handler/send_message_handler.py +++ b/src/send_handler/send_message_handler.py @@ -83,7 +83,7 @@ def handle_forward_message(cls, item: MessageBase) -> Dict: return {"type": "node", "data": {"id": message_segment.data}} else: user_info = item.message_info.user_info - content = cls.process_seg_recursive(message_segment.data, True) + content = cls.process_seg_recursive(message_segment, True) return { "type": "node", "data": {"name": user_info.user_nickname or "QQ用户", "uin": user_info.user_id, "content": content}, From df5a874a60530d080abcca0ce52bc43e2a0a4950 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 14 Sep 2025 16:19:06 +0800 Subject: [PATCH 083/112] =?UTF-8?q?echo=E6=B6=88=E6=81=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_sending.py | 13 ++++++++++ src/send_handler/nc_sending.py | 40 +++++++++++++++++++---------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/recv_handler/message_sending.py b/src/recv_handler/message_sending.py index 0c6a732..e40ed99 100644 --- a/src/recv_handler/message_sending.py +++ b/src/recv_handler/message_sending.py @@ -1,3 +1,4 @@ +from typing import Dict from src.logger import logger from maim_message import MessageBase, Router @@ -26,6 +27,18 @@ async def message_send(self, message_base: MessageBase) -> bool: except Exception as e: logger.error(f"发送消息失败: {str(e)}") logger.error("请检查与MaiBot之间的连接") + + async def send_custom_message(self, custom_message: Dict, platform: str, message_type: str) -> bool: + """ + 发送自定义消息 + """ + try: + await self.maibot_router.send_custom_message(platform=platform, message_type_name=message_type, message=custom_message) + return True + except Exception as e: + logger.error(f"发送自定义消息失败: {str(e)}") + logger.error("请检查与MaiBot之间的连接") + return False message_send_instance = MessageSending() diff --git a/src/send_handler/nc_sending.py b/src/send_handler/nc_sending.py index e9f45ca..bb3b65e 100644 --- a/src/send_handler/nc_sending.py +++ b/src/send_handler/nc_sending.py @@ -1,7 +1,7 @@ import json import uuid import websockets as Server -from maim_message import MessageBase, Seg +from maim_message import MessageBase from src.response_pool import get_response from src.logger import logger @@ -29,21 +29,33 @@ async def send_message_to_napcat(self, action: str, params: dict) -> dict: return response async def message_sent_back(self, message_base: MessageBase, qq_message_id: str) -> None: - # 修改 additional_config,添加 echo 字段 - if message_base.message_info.additional_config is None: - message_base.message_info.additional_config = {} + # # 修改 additional_config,添加 echo 字段 + # if message_base.message_info.additional_config is None: + # message_base.message_info.additional_config = {} - message_base.message_info.additional_config["echo"] = True + # message_base.message_info.additional_config["echo"] = True - # 获取原始的 mmc_message_id - mmc_message_id = message_base.message_info.message_id + # # 获取原始的 mmc_message_id + # mmc_message_id = message_base.message_info.message_id - # 修改 message_segment 为 notify 类型 - message_base.message_segment = Seg( - type="notify", data={"sub_type": "echo", "echo": mmc_message_id, "actual_id": qq_message_id} - ) - await message_send_instance.message_send(message_base) - logger.debug("已回送消息ID") - return + # # 修改 message_segment 为 notify 类型 + # message_base.message_segment = Seg( + # type="notify", data={"sub_type": "echo", "echo": mmc_message_id, "actual_id": qq_message_id} + # ) + # await message_send_instance.message_send(message_base) + # logger.debug("已回送消息ID") + # return + platform = message_base.message_info.platform + mmc_message_id = message_base.message_info.message_id + echo_data = { + "type": "echo", + "echo": mmc_message_id, + "actual_id": qq_message_id, + } + success = await message_send_instance.send_custom_message(echo_data, platform, "message_id_echo") + if success: + logger.debug("已回送消息ID") + else: + logger.error("回送消息ID失败") nc_message_sender = NCMessageSender() \ No newline at end of file From 178912375d82af27da08727387868ec252dbe652 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sun, 14 Sep 2025 16:20:28 +0800 Subject: [PATCH 084/112] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ba0cb66..2ac69fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.5.4" +version = "0.5.5" description = "A MaiBot adapter for Napcat" [tool.ruff] From 4d4e82d74207b1465adedca9c243d2250563f661 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 22 Sep 2025 21:11:09 +0800 Subject: [PATCH 085/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dunpack=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 3ce5315..8b4815c 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -259,7 +259,8 @@ async def handle_real_message( additional_config: dict = {} real_message: list = raw_message.get("message") if not real_message: - return None + logger.warning("实际消息内容为空") + return None, {} seg_message: List[Seg] = [] for sub_message in real_message: sub_message: dict From 424ca5b473706eb451546d50102dd696c7240afd Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 24 Sep 2025 13:38:20 +0800 Subject: [PATCH 086/112] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E4=BF=AE=E5=A4=8Dunp?= =?UTF-8?q?ack=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 8b4815c..4b96c03 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -324,7 +324,7 @@ async def handle_real_message( messages = await self._get_forward_message(sub_message) if not messages: logger.warning("转发消息内容为空或获取失败") - return None + return None, {} ret_seg = await self.handle_forward_message(messages) if ret_seg: seg_message.append(ret_seg) @@ -456,12 +456,12 @@ async def handle_reply_message(self, raw_message: dict, additional_config: dict) if raw_message_data: message_id = raw_message_data.get("id") else: - return None + return None, {} additional_config["reply_message_id"] = message_id message_detail: dict = await get_message_detail(self.server_connection, message_id) if not message_detail: logger.warning("获取被引用的消息详情失败") - return None + return None, {} reply_message, _ = await self.handle_real_message(message_detail, in_reply=True) if reply_message is None: reply_message = "(获取发言内容失败)" From 0d7733734c422fc54c7d7cead2a12068c79ddf06 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sun, 5 Oct 2025 17:31:31 +0800 Subject: [PATCH 087/112] =?UTF-8?q?=E7=BB=99=E6=89=80=E6=9C=89=E8=BD=AC?= =?UTF-8?q?=E5=8F=91=E7=BB=9F=E4=B8=80=E5=8A=A0=E4=B8=8A=E4=BA=86=E6=A0=87?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 32 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 4b96c03..126c7e9 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -560,6 +560,8 @@ async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple image_count = 0 if message_list is None: return None, 0 + # 统一在最前加入【转发消息】标识(带层级缩进) + seg_list.append(Seg(type="text", data=("--" * layer) + "\n【转发消息】\n")) for sub_message in message_list: sub_message: dict sender_info: dict = sub_message.get("sender") @@ -572,23 +574,17 @@ async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple continue message_of_sub_message = message_of_sub_message_list[0] if message_of_sub_message.get("type") == RealMessageType.forward: - if layer >= 3: - full_seg_data = Seg( - type="text", - data=("--" * layer) + f"【{user_nickname}】:【转发消息】\n", - ) - else: - sub_message_data = message_of_sub_message.get("data") - if not sub_message_data: - continue - contents = sub_message_data.get("content") - seg_data, count = await self._handle_forward_message(contents, layer + 1) - image_count += count - head_tip = Seg( - type="text", - data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n", - ) - full_seg_data = Seg(type="seglist", data=[head_tip, seg_data]) + sub_message_data = message_of_sub_message.get("data") + if not sub_message_data: + continue + contents = sub_message_data.get("content") + seg_data, count = await self._handle_forward_message(contents, layer + 1) + image_count += count + head_tip = Seg( + type="text", + data=("--" * layer) + f"【{user_nickname}】: 合并转发消息内容:\n", + ) + full_seg_data = Seg(type="seglist", data=[head_tip, seg_data]) seg_list.append(full_seg_data) elif message_of_sub_message.get("type") == RealMessageType.text: sub_message_data = message_of_sub_message.get("data") @@ -634,6 +630,8 @@ async def _handle_forward_message(self, message_list: list, layer: int) -> Tuple ] full_seg_data = Seg(type="seglist", data=data_list) seg_list.append(full_seg_data) + # 在结尾追加标识 + seg_list.append(Seg(type="text", data=("--" * layer) + "【转发消息结束】")) return Seg(type="seglist", data=seg_list), image_count async def _get_forward_message(self, raw_message: dict) -> Dict[str, Any] | None: From 3de2444b0e3778fd1d7b24c3a0f440b0f8509073 Mon Sep 17 00:00:00 2001 From: ShiroRikka Date: Wed, 3 Dec 2025 23:44:27 +0800 Subject: [PATCH 088/112] =?UTF-8?q?=E2=9C=A8=20feat(deps):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20pyproject.toml=20=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 向 pyproject.toml 添加了 aiohttp, asyncio, loguru, maim-message, pillow, requests, rich, sqlmodel, tomlkit, websockets 依赖 --- pyproject.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2ac69fc..5db9a29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,18 @@ name = "MaiBotNapcatAdapter" version = "0.5.5" description = "A MaiBot adapter for Napcat" +dependencies = [ + "aiohttp>=3.13.2", + "asyncio>=4.0.0", + "loguru>=0.7.3", + "maim-message>=0.5.7", + "pillow>=12.0.0", + "requests>=2.32.5", + "rich>=14.2.0", + "sqlmodel>=0.0.27", + "tomlkit>=0.13.3", + "websockets>=15.0.1", +] [tool.ruff] From 96b6487ccc36008fb0a8c289bc00a7addca5fbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Thu, 11 Dec 2025 15:10:21 +0800 Subject: [PATCH 089/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9=E7=BE=A4?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E8=A1=A8=E6=83=85=E5=9B=9E=E5=BA=94=E3=80=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E3=80=81=E7=BE=A4=E6=88=90?= =?UTF-8?q?=E5=91=98=E5=A2=9E=E5=87=8F=E5=8F=8A=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E5=8F=98=E5=8A=A8=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=EF=BC=8C=E6=B8=85?= =?UTF-8?q?=E7=90=86=E8=BF=87=E6=9C=9F=E6=97=A5=E5=BF=97=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=80=82=E9=85=8D=E5=99=A8=E5=90=AF=E5=8A=A8=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + main.py | 125 +++++- src/logger.py | 101 ++++- src/recv_handler/__init__.py | 25 ++ src/recv_handler/message_handler.py | 102 ++++- src/recv_handler/notice_handler.py | 607 +++++++++++++++++++++++++++- uv.lock | 8 + 7 files changed, 937 insertions(+), 32 deletions(-) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index ec98f59..3dd0281 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +dev/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/main.py b/main.py index 6f824a6..b0986a6 100644 --- a/main.py +++ b/main.py @@ -64,26 +64,73 @@ def check_napcat_server_token(conn, request): return None async def napcat_server(): - logger.info("正在启动adapter...") - async with Server.serve(message_recv, global_config.napcat_server.host, global_config.napcat_server.port, max_size=2**26, process_request=check_napcat_server_token) as server: - logger.info( - f"Adapter已启动,监听地址: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" - ) - await server.serve_forever() + logger.info("正在启动 MaiBot-Napcat-Adapter...") + logger.debug(f"日志等级: {global_config.debug.level}") + logger.debug("日志文件: logs/adapter_*.log") + try: + async with Server.serve( + message_recv, + global_config.napcat_server.host, + global_config.napcat_server.port, + max_size=2**26, + process_request=check_napcat_server_token + ) as server: + logger.success( + f"✅ Adapter 启动成功! 监听: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" + ) + await server.serve_forever() + except OSError: + # 端口绑定失败时抛出异常让外层处理 + raise -async def graceful_shutdown(): +async def graceful_shutdown(silent: bool = False): + """ + 优雅关闭adapter + Args: + silent: 静默模式,控制台不输出日志,但仍记录到文件 + """ try: - logger.info("正在关闭adapter...") + if not silent: + logger.info("正在关闭adapter...") + else: + logger.debug("正在清理资源...") + + # 先关闭MMC连接 + try: + await asyncio.wait_for(mmc_stop_com(), timeout=3) + except asyncio.TimeoutError: + logger.debug("关闭MMC连接超时") + except Exception as e: + logger.debug(f"关闭MMC连接时出现错误: {e}") + + # 取消所有任务 tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] + if tasks: + logger.debug(f"正在取消 {len(tasks)} 个任务") for task in tasks: if not task.done(): task.cancel() - await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), 15) - await mmc_stop_com() # 后置避免神秘exception - logger.info("Adapter已成功关闭") + + # 等待任务完成,记录异常到日志文件 + if tasks: + try: + results = await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=3) + # 记录任务取消的详细信息到日志文件 + for i, result in enumerate(results): + if isinstance(result, Exception): + logger.debug(f"任务 {i+1} 清理时产生异常: {type(result).__name__}: {result}") + except asyncio.TimeoutError: + logger.debug("任务清理超时") + except Exception as e: + logger.debug(f"任务清理时出现错误: {e}") + + if not silent: + logger.info("Adapter已成功关闭") + else: + logger.debug("资源清理完成") except Exception as e: - logger.error(f"Adapter关闭中出现错误: {e}") + logger.debug(f"graceful_shutdown异常: {e}", exc_info=True) if __name__ == "__main__": @@ -93,11 +140,57 @@ async def graceful_shutdown(): loop.run_until_complete(main()) except KeyboardInterrupt: logger.warning("收到中断信号,正在优雅关闭...") - loop.run_until_complete(graceful_shutdown()) + try: + loop.run_until_complete(graceful_shutdown(silent=False)) + except Exception: + pass + except OSError as e: + # 处理端口占用等网络错误 + if e.errno == 10048 or "address already in use" in str(e).lower(): + logger.error(f"❌ 端口 {global_config.napcat_server.port} 已被占用,请检查:") + logger.error(" 1. 是否有其他 MaiBot-Napcat-Adapter 实例正在运行") + logger.error(" 2. 修改 config.toml 中的 port 配置") + logger.error(f" 3. 使用命令查看占用进程: netstat -ano | findstr {global_config.napcat_server.port}") + logger.debug("完整错误信息:", exc_info=True) + else: + logger.error(f"❌ 网络错误: {str(e)}") + logger.debug("完整错误信息:", exc_info=True) + # 端口占用时静默清理(控制台不输出,但记录到日志文件) + try: + loop.run_until_complete(graceful_shutdown(silent=True)) + except Exception as e: + logger.debug(f"清理资源时出现错误: {e}", exc_info=True) + sys.exit(1) except Exception as e: - logger.exception(f"主程序异常: {str(e)}") + logger.error(f"❌ 主程序异常: {str(e)}") + logger.debug("详细错误信息:", exc_info=True) + try: + loop.run_until_complete(graceful_shutdown(silent=True)) + except Exception as e: + logger.debug(f"清理资源时出现错误: {e}", exc_info=True) sys.exit(1) finally: - if loop and not loop.is_closed(): - loop.close() + # 清理事件循环 + try: + # 取消所有剩余任务 + pending = asyncio.all_tasks(loop) + if pending: + logger.debug(f"finally块清理 {len(pending)} 个剩余任务") + for task in pending: + task.cancel() + # 给任务一点时间完成取消 + try: + results = loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + # 记录清理结果到日志文件 + for i, result in enumerate(results): + if isinstance(result, Exception) and not isinstance(result, asyncio.CancelledError): + logger.debug(f"剩余任务 {i+1} 清理异常: {type(result).__name__}: {result}") + except Exception as e: + logger.debug(f"清理剩余任务时出现错误: {e}") + except Exception as e: + logger.debug(f"finally块清理出现错误: {e}") + finally: + if loop and not loop.is_closed(): + logger.debug("关闭事件循环") + loop.close() sys.exit(0) diff --git a/src/logger.py b/src/logger.py index 4100964..ab509e9 100644 --- a/src/logger.py +++ b/src/logger.py @@ -1,21 +1,106 @@ from loguru import logger from .config import global_config import sys +from pathlib import Path +from datetime import datetime, timedelta -# 默认 logger +# 日志目录配置 +LOG_DIR = Path(__file__).parent.parent / "logs" +LOG_DIR.mkdir(exist_ok=True) + +# 日志等级映射(用于显示单字母) +LEVEL_ABBR = { + "TRACE": "T", + "DEBUG": "D", + "INFO": "I", + "SUCCESS": "S", + "WARNING": "W", + "ERROR": "E", + "CRITICAL": "C" +} + +def get_level_abbr(record): + """获取日志等级的缩写""" + return LEVEL_ABBR.get(record["level"].name, record["level"].name[0]) + +def clean_old_logs(days: int = 30): + """清理超过指定天数的日志文件""" + try: + cutoff_date = datetime.now() - timedelta(days=days) + for log_file in LOG_DIR.glob("*.log"): + try: + file_time = datetime.fromtimestamp(log_file.stat().st_mtime) + if file_time < cutoff_date: + log_file.unlink() + print(f"已清理过期日志: {log_file.name}") + except Exception as e: + print(f"清理日志文件 {log_file.name} 失败: {e}") + except Exception as e: + print(f"清理日志目录失败: {e}") + +# 清理过期日志 +clean_old_logs(30) + +# 移除默认处理器 logger.remove() + +# 自定义格式化函数 +def format_log(record): + """格式化日志记录""" + record["extra"]["level_abbr"] = get_level_abbr(record) + if "module_name" not in record["extra"]: + record["extra"]["module_name"] = "Adapter" + return True + +# 控制台输出处理器 - 简洁格式 logger.add( sys.stderr, level=global_config.debug.level, - format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", - filter=lambda record: "name" not in record["extra"] or record["extra"].get("name") != "maim_message", + format="{time:MM-DD HH:mm:ss} | [{extra[level_abbr]}] | {extra[module_name]} | {message}", + filter=lambda record: format_log(record) and record["extra"].get("module_name") != "maim_message", ) + +# maim_message 单独处理 logger.add( sys.stderr, level="INFO", - format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", - filter=lambda record: record["extra"].get("name") == "maim_message", + format="{time:MM-DD HH:mm:ss} | [{extra[level_abbr]}] | {extra[module_name]} | {message}", + filter=lambda record: format_log(record) and record["extra"].get("module_name") == "maim_message", +) + +# 文件输出处理器 - 详细格式,记录所有TRACE级别 +log_file = LOG_DIR / f"adapter_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" +logger.add( + log_file, + level="TRACE", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | [{level}] | {extra[module_name]} | {name}:{function}:{line} - {message}", + rotation="100 MB", # 单个日志文件最大100MB + retention="30 days", # 保留30天 + encoding="utf-8", + enqueue=True, # 异步写入,避免阻塞 + filter=format_log, # 确保extra字段存在 ) -# 创建样式不同的 logger -custom_logger = logger.bind(name="maim_message") -logger = logger.bind(name="MaiBot-Napcat-Adapter") + +def get_logger(module_name: str = "Adapter"): + """ + 获取自定义模块名的logger + + Args: + module_name: 模块名称,用于日志输出中标识来源 + + Returns: + 配置好的logger实例 + + Example: + >>> from src.logger import get_logger + >>> logger = get_logger("MyModule") + >>> logger.info("这是一条日志") + MM-DD HH:mm:ss | [I] | MyModule | 这是一条日志 + """ + return logger.bind(module_name=module_name) + +# 默认logger实例(用于向后兼容) +logger = logger.bind(module_name="Adapter") + +# maim_message的logger +custom_logger = logger.bind(module_name="maim_message") diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index 767ae77..f8904ee 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -32,6 +32,12 @@ class NoticeType: # 通知事件 group_recall = "group_recall" # 群聊消息撤回 notify = "notify" group_ban = "group_ban" # 群禁言 + group_msg_emoji_like = "group_msg_emoji_like" # 群消息表情回应 + group_upload = "group_upload" # 群文件上传 + group_increase = "group_increase" # 群成员增加 + group_decrease = "group_decrease" # 群成员减少 + group_admin = "group_admin" # 群管理员变动 + essence = "essence" # 精华消息 class Notify: poke = "poke" # 戳一戳 @@ -40,6 +46,23 @@ class GroupBan: ban = "ban" # 禁言 lift_ban = "lift_ban" # 解除禁言 + class GroupIncrease: + approve = "approve" # 管理员同意入群 + invite = "invite" # 被邀请入群 + + class GroupDecrease: + leave = "leave" # 主动退群 + kick = "kick" # 被踢出群 + kick_me = "kick_me" # 机器人被踢 + + class GroupAdmin: + set = "set" # 设置管理员 + unset = "unset" # 取消管理员 + + class Essence: + add = "add" # 添加精华消息 + delete = "delete" # 移除精华消息 + class RealMessageType: # 实际消息分类 text = "text" # 纯文本 @@ -56,6 +79,8 @@ class RealMessageType: # 实际消息分类 reply = "reply" # 回复消息 forward = "forward" # 转发消息 node = "node" # 转发消息节点 + json = "json" # JSON卡片消息 + file = "file" # 文件消息 class MessageSentType: diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 126c7e9..afa7d20 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -300,7 +300,23 @@ async def handle_real_message( else: logger.warning("record处理失败或不支持") case RealMessageType.video: - logger.warning("不支持视频解析") + ret_seg = await self.handle_video_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("video处理失败") + case RealMessageType.json: + ret_seg = await self.handle_json_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("json处理失败") + case RealMessageType.file: + ret_seg = await self.handle_file_message(sub_message) + if ret_seg: + seg_message.append(ret_seg) + else: + logger.warning("file处理失败") case RealMessageType.at: ret_seg = await self.handle_at_message( sub_message, @@ -445,6 +461,77 @@ async def handle_record_message(self, raw_message: dict) -> Seg | None: return None return Seg(type="voice", data=audio_base64) + async def handle_video_message(self, raw_message: dict) -> Seg | None: + """ + 处理视频消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + file: str = message_data.get("file") + url: str = message_data.get("url") + file_size: str = message_data.get("file_size", "未知大小") + + if not file: + logger.warning("视频消息缺少文件信息") + return None + + # 视频消息返回文本描述,包含文件名和大小 + video_text = f"[视频: {file}, 大小: {file_size}字节]" + if url: + video_text += f"\n视频链接: {url}" + + return Seg(type="text", data=video_text) + + async def handle_json_message(self, raw_message: dict) -> Seg | None: + """ + 处理JSON卡片消息(小程序、分享等) + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + json_data: str = message_data.get("data") + + if not json_data: + logger.warning("JSON消息缺少数据") + return None + + try: + # 尝试解析JSON获取prompt提示信息 + parsed_json = json.loads(json_data) + prompt = parsed_json.get("prompt", "[卡片消息]") + return Seg(type="text", data=prompt) + except json.JSONDecodeError: + logger.warning("JSON消息解析失败") + return Seg(type="text", data="[卡片消息]") + + async def handle_file_message(self, raw_message: dict) -> Seg | None: + """ + 处理文件消息 + Parameters: + raw_message: dict: 原始消息 + Returns: + seg_data: Seg: 处理后的消息段 + """ + message_data: dict = raw_message.get("data") + file_name: str = message_data.get("file") + file_size: str = message_data.get("file_size", "未知大小") + file_url: str = message_data.get("url") + + if not file_name: + logger.warning("文件消息缺少文件名") + return None + + file_text = f"[文件: {file_name}, 大小: {file_size}字节]" + if file_url: + file_text += f"\n文件链接: {file_url}" + + return Seg(type="text", data=file_text) + async def handle_reply_message(self, raw_message: dict, additional_config: dict) -> Tuple[List[Seg] | None, dict]: # sourcery skip: move-assign-in-block, use-named-expression """ @@ -489,18 +576,25 @@ async def handle_forward_message(self, message_list: list) -> Seg | None: image_count: int if not handled_message: return None + + # 添加转发消息的标题和结束标识 + forward_header = Seg(type="text", data="========== 转发消息开始 ==========\n") + forward_footer = Seg(type="text", data="========== 转发消息结束 ==========") + if image_count < 5 and image_count > 0: # 处理图片数量小于5的情况,此时解析图片为base64 logger.trace("图片数量小于5,开始解析图片为base64") - return await self._recursive_parse_image_seg(handled_message, True) + parsed_message = await self._recursive_parse_image_seg(handled_message, True) + return Seg(type="seglist", data=[forward_header, parsed_message, forward_footer]) elif image_count > 0: logger.trace("图片数量大于等于5,开始解析图片为占位符") # 处理图片数量大于等于5的情况,此时解析图片为占位符 - return await self._recursive_parse_image_seg(handled_message, False) + parsed_message = await self._recursive_parse_image_seg(handled_message, False) + return Seg(type="seglist", data=[forward_header, parsed_message, forward_footer]) else: # 处理没有图片的情况,此时直接返回 logger.trace("没有图片,直接返回") - return handled_message + return Seg(type="seglist", data=[forward_header, handled_message, forward_footer]) async def _recursive_parse_image_seg(self, seg_data: Seg, to_image: bool) -> Seg: # sourcery skip: merge-else-if-into-elif diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 1e51ea4..2915241 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -87,12 +87,13 @@ async def handle_notice(self, raw_message: dict) -> None: match notice_type: case NoticeType.friend_recall: logger.info("好友撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") + handled_message, user_info = await self.handle_friend_recall_notify(raw_message) case NoticeType.group_recall: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None logger.info("群内用户撤回一条消息") - logger.info(f"撤回消息ID:{raw_message.get('message_id')}, 撤回时间:{raw_message.get('time')}") - logger.warning("暂时不支持撤回消息处理") + handled_message, user_info = await self.handle_group_recall_notify(raw_message, group_id, user_id) + system_notice = True case NoticeType.notify: sub_type = raw_message.get("sub_type") match sub_type: @@ -123,6 +124,37 @@ async def handle_notice(self, raw_message: dict) -> None: system_notice = True case _: logger.warning(f"不支持的group_ban类型: {notice_type}.{sub_type}") + case NoticeType.group_msg_emoji_like: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None + logger.info("处理群消息表情回应") + handled_message, user_info = await self.handle_emoji_like_notify(raw_message, group_id, user_id) + case NoticeType.group_upload: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None + logger.info("处理群文件上传") + handled_message, user_info = await self.handle_group_upload_notify(raw_message, group_id, user_id) + system_notice = True + case NoticeType.group_increase: + sub_type = raw_message.get("sub_type") + logger.info(f"处理群成员增加: {sub_type}") + handled_message, user_info = await self.handle_group_increase_notify(raw_message, group_id, user_id) + system_notice = True + case NoticeType.group_decrease: + sub_type = raw_message.get("sub_type") + logger.info(f"处理群成员减少: {sub_type}") + handled_message, user_info = await self.handle_group_decrease_notify(raw_message, group_id, user_id) + system_notice = True + case NoticeType.group_admin: + sub_type = raw_message.get("sub_type") + logger.info(f"处理群管理员变动: {sub_type}") + handled_message, user_info = await self.handle_group_admin_notify(raw_message, group_id, user_id) + system_notice = True + case NoticeType.essence: + sub_type = raw_message.get("sub_type") + logger.info(f"处理精华消息: {sub_type}") + handled_message, user_info = await self.handle_essence_notify(raw_message, group_id) + system_notice = True case _: logger.warning(f"不支持的notice类型: {notice_type}") return None @@ -240,6 +272,322 @@ async def handle_poke_notify( ) return seg_data, user_info + async def handle_friend_recall_notify(self, raw_message: dict) -> Tuple[Seg | None, UserInfo | None]: + """处理好友消息撤回""" + user_id = raw_message.get("user_id") + message_id = raw_message.get("message_id") + + if not user_id: + logger.error("用户ID不能为空,无法处理好友撤回通知") + return None, None + + # 获取好友信息 + user_qq_info: dict = await get_stranger_info(self.server_connection, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + else: + user_name = "QQ用户" + logger.warning("无法获取撤回消息好友的昵称") + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=None, + ) + + seg_data = Seg( + type="notify", + data={ + "sub_type": "friend_recall", + "message_id": message_id, + }, + ) + + return seg_data, user_info + + async def handle_group_recall_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """处理群消息撤回""" + if not group_id: + logger.error("群ID不能为空,无法处理群撤回通知") + return None, None + + message_id = raw_message.get("message_id") + operator_id = raw_message.get("operator_id") + + # 获取撤回操作者信息 + operator_nickname: str = None + operator_cardname: str = None + + member_info: dict = await get_member_info(self.server_connection, group_id, operator_id) + if member_info: + operator_nickname = member_info.get("nickname") + operator_cardname = member_info.get("card") + else: + logger.warning("无法获取撤回操作者的昵称") + operator_nickname = "QQ用户" + + operator_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=operator_id, + user_nickname=operator_nickname, + user_cardname=operator_cardname, + ) + + # 获取被撤回消息发送者信息(如果不是自己撤回的话) + recalled_user_info: UserInfo | None = None + if user_id != operator_id: + user_member_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_member_info: + user_nickname = user_member_info.get("nickname") + user_cardname = user_member_info.get("card") + else: + user_nickname = "QQ用户" + user_cardname = None + logger.warning("无法获取被撤回消息发送者的昵称") + + recalled_user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_nickname, + user_cardname=user_cardname, + ) + + seg_data = Seg( + type="notify", + data={ + "sub_type": "group_recall", + "message_id": message_id, + "recalled_user_info": recalled_user_info.to_dict() if recalled_user_info else None, + }, + ) + + return seg_data, operator_info + + async def handle_emoji_like_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """处理群消息表情回应""" + if not group_id: + logger.error("群ID不能为空,无法处理表情回应通知") + return None, None + + # 获取用户信息 + user_qq_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + user_name = "QQ用户" + user_cardname = "QQ用户" + logger.warning("无法获取表情回应用户的昵称") + + # 解析表情列表 + likes = raw_message.get("likes", []) + message_id = raw_message.get("message_id") + + # 构建表情文本 + emoji_texts = [] + # QQ 官方表情映射表 (EmojiType=1 为 QQ 系统表情,EmojiType=2 为 Emoji Unicode) + emoji_map = { + # QQ 系统表情 (Type 1) + "4": "得意", + "5": "流泪", + "8": "睡", + "9": "大哭", + "10": "尴尬", + "12": "调皮", + "14": "微笑", + "16": "酷", + "21": "可爱", + "23": "傲慢", + "24": "饥饿", + "25": "困", + "26": "惊恐", + "27": "流汗", + "28": "憨笑", + "29": "悠闲", + "30": "奋斗", + "32": "疑问", + "33": "嘘", + "34": "晕", + "38": "敲打", + "39": "再见", + "41": "发抖", + "42": "爱情", + "43": "跳跳", + "49": "拥抱", + "53": "蛋糕", + "60": "咖啡", + "63": "玫瑰", + "66": "爱心", + "74": "太阳", + "75": "月亮", + "76": "赞", + "78": "握手", + "79": "胜利", + "85": "飞吻", + "89": "西瓜", + "96": "冷汗", + "97": "擦汗", + "98": "抠鼻", + "99": "鼓掌", + "100": "糗大了", + "101": "坏笑", + "102": "左哼哼", + "103": "右哼哼", + "104": "哈欠", + "106": "委屈", + "109": "左亲亲", + "111": "可怜", + "116": "示爱", + "118": "抱拳", + "120": "拳头", + "122": "爱你", + "123": "NO", + "124": "OK", + "125": "转圈", + "129": "挥手", + "144": "喝彩", + "147": "棒棒糖", + "171": "茶", + "173": "泪奔", + "174": "无奈", + "175": "卖萌", + "176": "小纠结", + "179": "doge", + "180": "惊喜", + "181": "骚扰", + "182": "笑哭", + "183": "我最美", + "201": "点赞", + "203": "托脸", + "212": "托腮", + "214": "啵啵", + "219": "蹭一蹭", + "222": "抱抱", + "227": "拍手", + "232": "佛系", + "240": "喷脸", + "243": "甩头", + "246": "加油抱抱", + "262": "脑阔疼", + "264": "捂脸", + "265": "辣眼睛", + "266": "哦哟", + "267": "头秃", + "268": "问号脸", + "269": "暗中观察", + "270": "emm", + "271": "吃瓜", + "272": "呵呵哒", + "273": "我酸了", + "277": "汪汪", + "278": "汗", + "281": "无眼笑", + "282": "敬礼", + "284": "面无表情", + "285": "摸鱼", + "287": "哦", + "289": "睁眼", + "290": "敲开心", + "293": "摸锦鲤", + "294": "期待", + "297": "拜谢", + "298": "元宝", + "299": "牛啊", + "305": "右亲亲", + "306": "牛气冲天", + "307": "喵喵", + "314": "仔细分析", + "315": "加油", + "318": "崇拜", + "319": "比心", + "320": "庆祝", + "322": "拒绝", + "324": "吃糖", + "326": "生气", + # Unicode Emoji (Type 2) + "9728": "☀", + "9749": "☕", + "9786": "☺", + "10024": "✨", + "10060": "❌", + "10068": "❔", + "127801": "🌹", + "127817": "🍉", + "127822": "🍎", + "127827": "🍓", + "127836": "🍜", + "127838": "🍞", + "127847": "🍧", + "127866": "🍺", + "127867": "🍻", + "127881": "🎉", + "128027": "🐛", + "128046": "🐮", + "128051": "🐳", + "128053": "🐵", + "128074": "👊", + "128076": "👌", + "128077": "👍", + "128079": "👏", + "128089": "👙", + "128102": "👦", + "128104": "👨", + "128147": "💓", + "128157": "💝", + "128164": "💤", + "128166": "💦", + "128168": "💨", + "128170": "💪", + "128235": "📫", + "128293": "🔥", + "128513": "😁", + "128514": "😂", + "128516": "😄", + "128522": "😊", + "128524": "😌", + "128527": "😏", + "128530": "😒", + "128531": "😓", + "128532": "😔", + "128536": "😘", + "128538": "😚", + "128540": "😜", + "128541": "😝", + "128557": "😭", + "128560": "😰", + "128563": "😳", + } + + for like in likes: + emoji_id = like.get("emoji_id", "") + count = like.get("count", 1) + emoji = emoji_map.get(emoji_id, f"表情{emoji_id}") + if count > 1: + emoji_texts.append(f"{emoji}x{count}") + else: + emoji_texts.append(emoji) + + emoji_str = "、".join(emoji_texts) if emoji_texts else "未知表情" + display_name = user_cardname if user_cardname and user_cardname != "QQ用户" else user_name + + # 构建消息文本 + message_text = f"{display_name} 对消息(ID:{message_id})表达了 {emoji_str}" + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + + seg_data = Seg(type="text", data=message_text) + return seg_data, user_info + async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg, UserInfo] | Tuple[None, None]: if not group_id: logger.error("群ID不能为空,无法处理禁言通知") @@ -512,5 +860,256 @@ async def send_notice(self) -> None: await unsuccessful_notice_queue.put(to_be_send) await asyncio.sleep(1) + async def handle_group_upload_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """ + 处理群文件上传通知 + """ + file_info: dict = raw_message.get("file", {}) + file_name = file_info.get("name", "未知文件") + file_size = file_info.get("size", 0) + file_id = file_info.get("id", "") + + user_qq_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + logger.warning("无法获取上传者信息") + user_name = "QQ用户" + user_cardname = None + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + + # 格式化文件大小 + if file_size < 1024: + size_str = f"{file_size}B" + elif file_size < 1024 * 1024: + size_str = f"{file_size / 1024:.2f}KB" + else: + size_str = f"{file_size / (1024 * 1024):.2f}MB" + + notify_seg = Seg( + type="notify", + data={ + "sub_type": "group_upload", + "file_name": file_name, + "file_size": size_str, + "file_id": file_id, + }, + ) + + return notify_seg, user_info + + async def handle_group_increase_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """ + 处理群成员增加通知 + """ + sub_type = raw_message.get("sub_type") + operator_id = raw_message.get("operator_id") + + # 获取新成员信息 + user_qq_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + logger.warning("无法获取新成员信息") + user_name = "QQ用户" + user_cardname = None + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + + # 获取操作者信息 + operator_name = "未知" + if operator_id: + operator_info: dict = await get_member_info(self.server_connection, group_id, operator_id) + if operator_info: + operator_name = operator_info.get("card") or operator_info.get("nickname", "未知") + + if sub_type == NoticeType.GroupIncrease.invite: + action_text = f"被 {operator_name} 邀请" + elif sub_type == NoticeType.GroupIncrease.approve: + action_text = f"经 {operator_name} 同意" + else: + action_text = "加入" + + notify_seg = Seg( + type="notify", + data={ + "sub_type": "group_increase", + "action": action_text, + "increase_type": sub_type, + "operator_id": operator_id, + }, + ) + + return notify_seg, user_info + + async def handle_group_decrease_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """ + 处理群成员减少通知 + """ + sub_type = raw_message.get("sub_type") + operator_id = raw_message.get("operator_id") + + # 获取离开成员信息 + user_qq_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + logger.warning("无法获取离开成员信息") + user_name = "QQ用户" + user_cardname = None + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + + # 获取操作者信息 + operator_name = "未知" + if operator_id and operator_id != 0: + operator_info: dict = await get_member_info(self.server_connection, group_id, operator_id) + if operator_info: + operator_name = operator_info.get("card") or operator_info.get("nickname", "未知") + + if sub_type == NoticeType.GroupDecrease.leave: + action_text = "主动退群" + elif sub_type == NoticeType.GroupDecrease.kick: + action_text = f"被 {operator_name} 踢出" + elif sub_type == NoticeType.GroupDecrease.kick_me: + action_text = "机器人被踢出" + else: + action_text = "离开群聊" + + notify_seg = Seg( + type="notify", + data={ + "sub_type": "group_decrease", + "action": action_text, + "decrease_type": sub_type, + "operator_id": operator_id, + }, + ) + + return notify_seg, user_info + + async def handle_group_admin_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """ + 处理群管理员变动通知 + """ + sub_type = raw_message.get("sub_type") + + # 获取目标用户信息 + user_qq_info: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_qq_info: + user_name = user_qq_info.get("nickname") + user_cardname = user_qq_info.get("card") + else: + logger.warning("无法获取目标用户信息") + user_name = "QQ用户" + user_cardname = None + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + + if sub_type == NoticeType.GroupAdmin.set: + action_text = "被设置为管理员" + elif sub_type == NoticeType.GroupAdmin.unset: + action_text = "被取消管理员" + else: + action_text = "管理员变动" + + notify_seg = Seg( + type="notify", + data={ + "sub_type": "group_admin", + "action": action_text, + "admin_type": sub_type, + }, + ) + + return notify_seg, user_info + + async def handle_essence_notify( + self, raw_message: dict, group_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """ + 处理精华消息通知 + """ + sub_type = raw_message.get("sub_type") + sender_id = raw_message.get("sender_id") + operator_id = raw_message.get("operator_id") + message_id = raw_message.get("message_id") + + # 获取操作者信息(设置精华的人) + operator_info: dict = await get_member_info(self.server_connection, group_id, operator_id) + if operator_info: + operator_name = operator_info.get("nickname") + operator_cardname = operator_info.get("card") + else: + logger.warning("无法获取操作者信息") + operator_name = "QQ用户" + operator_cardname = None + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=operator_id, + user_nickname=operator_name, + user_cardname=operator_cardname, + ) + + # 获取消息发送者信息 + sender_name = "未知用户" + if sender_id: + sender_info: dict = await get_member_info(self.server_connection, group_id, sender_id) + if sender_info: + sender_name = sender_info.get("card") or sender_info.get("nickname", "未知用户") + + if sub_type == NoticeType.Essence.add: + action_text = f"将 {sender_name} 的消息设为精华" + elif sub_type == NoticeType.Essence.delete: + action_text = f"移除了 {sender_name} 的精华消息" + else: + action_text = "精华消息变动" + + notify_seg = Seg( + type="notify", + data={ + "sub_type": "essence", + "action": action_text, + "essence_type": sub_type, + "sender_id": sender_id, + "message_id": message_id, + }, + ) + + return notify_seg, user_info + notice_handler = NoticeHandler() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..8c87f71 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "maibotnapcatadapter" +version = "0.5.5" +source = { virtual = "." } From 12f6d205e1c4212c2a12b2e12424e0629a88d2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Thu, 11 Dec 2025 15:26:25 +0800 Subject: [PATCH 090/112] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=AF=B9JSON?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=B6=88=E6=81=AF=E7=9A=84=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E7=BE=A4=E5=85=AC=E5=91=8A=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=B9=B6=E6=9E=84=E5=BB=BA=E7=9B=B8=E5=BA=94=E6=96=87?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index afa7d20..14c2c6e 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -487,7 +487,7 @@ async def handle_video_message(self, raw_message: dict) -> Seg | None: async def handle_json_message(self, raw_message: dict) -> Seg | None: """ - 处理JSON卡片消息(小程序、分享等) + 处理JSON卡片消息(小程序、分享、群公告等) Parameters: raw_message: dict: 原始消息 Returns: @@ -501,8 +501,27 @@ async def handle_json_message(self, raw_message: dict) -> Seg | None: return None try: - # 尝试解析JSON获取prompt提示信息 + # 尝试解析JSON获取详细信息 parsed_json = json.loads(json_data) + app = parsed_json.get("app", "") + + # 检查是否为群公告 + if app == "com.tencent.mannounce": + meta = parsed_json.get("meta", {}) + mannounce = meta.get("mannounce", {}) + title = mannounce.get("title", "") + text = mannounce.get("text", "") + + # 构建群公告文本 + announce_text = "[群公告]" + if title: + announce_text += f"\n标题: {title}" + if text: + announce_text += f"\n内容: {text}" + + return Seg(type="text", data=announce_text) + + # 其他卡片消息使用prompt字段 prompt = parsed_json.get("prompt", "[卡片消息]") return Seg(type="text", data=prompt) except json.JSONDecodeError: From 0beb3dfdfac4d46cfdd21b777950f6fedb64d3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Thu, 11 Dec 2025 15:29:48 +0800 Subject: [PATCH 091/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9=E9=9F=B3?= =?UTF-8?q?=E4=B9=90=E5=8D=A1=E7=89=87=E6=B6=88=E6=81=AF=E7=9A=84=E5=A4=84?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E6=8F=90=E5=8F=96=E6=AD=8C=E6=9B=B2=E5=92=8C?= =?UTF-8?q?=E6=AD=8C=E6=89=8B=E4=BF=A1=E6=81=AF=E5=B9=B6=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E7=9B=B8=E5=BA=94=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 14c2c6e..10d01d8 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -521,6 +521,22 @@ async def handle_json_message(self, raw_message: dict) -> Seg | None: return Seg(type="text", data=announce_text) + # 检查是否为音乐卡片 + if app == "com.tencent.music.lua" or app == "com.tencent.structmsg": + meta = parsed_json.get("meta", {}) + music = meta.get("music", {}) + + # 尝试从music字段提取信息 + if music: + title = music.get("title", "") + singer = music.get("singer", "") + music_text = "[音乐卡片]" + if title: + music_text += f"\n歌曲: {title}" + if singer: + music_text += f"\n歌手: {singer}" + return Seg(type="text", data=music_text) + # 其他卡片消息使用prompt字段 prompt = parsed_json.get("prompt", "[卡片消息]") return Seg(type="text", data=prompt) From 250be48ea894a79ff0bf8447c40f565ac73f0428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Thu, 11 Dec 2025 15:43:39 +0800 Subject: [PATCH 092/112] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9=E7=BE=A4?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=8F=98=E6=9B=B4=E9=80=9A=E7=9F=A5=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E6=94=AF=E6=8C=81Base64=E8=A7=A3?= =?UTF-8?q?=E7=A0=81=E7=BE=A4=E5=85=AC=E5=91=8A=E6=A0=87=E9=A2=98=E5=92=8C?= =?UTF-8?q?=E5=86=85=E5=AE=B9=EF=BC=8C=E4=BC=98=E5=8C=96=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E7=A7=8D=E6=95=B0=E6=8D=AE=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/__init__.py | 1 + src/recv_handler/message_handler.py | 19 +++++++++- src/recv_handler/notice_handler.py | 48 ++++++++++++++++++++++++ src/send_handler/send_message_handler.py | 46 +++++++++++++++++++---- 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/recv_handler/__init__.py b/src/recv_handler/__init__.py index f8904ee..e4c9744 100644 --- a/src/recv_handler/__init__.py +++ b/src/recv_handler/__init__.py @@ -41,6 +41,7 @@ class NoticeType: # 通知事件 class Notify: poke = "poke" # 戳一戳 + group_name = "group_name" # 群名称变更 class GroupBan: ban = "ban" # 禁言 diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 10d01d8..d52d21a 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -8,6 +8,7 @@ get_self_info, get_message_detail, ) +import base64 from .qq_emoji_list import qq_face from .message_sending import message_send_instance from . import RealMessageType, MessageType, ACCEPT_FORMAT @@ -509,8 +510,22 @@ async def handle_json_message(self, raw_message: dict) -> Seg | None: if app == "com.tencent.mannounce": meta = parsed_json.get("meta", {}) mannounce = meta.get("mannounce", {}) - title = mannounce.get("title", "") - text = mannounce.get("text", "") + title_encoded = mannounce.get("title", "") + text_encoded = mannounce.get("text", "") + + # 解码Base64编码的标题和内容 + title = "" + text = "" + try: + if title_encoded: + title = base64.b64decode(title_encoded).decode("utf-8") + if text_encoded: + text = base64.b64decode(text_encoded).decode("utf-8") + except Exception as e: + logger.warning(f"群公告Base64解码失败: {e}") + # 降级使用原始值 + title = title_encoded + text = text_encoded # 构建群公告文本 announce_text = "[群公告]" diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 2915241..1c9ea5d 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -105,6 +105,12 @@ async def handle_notice(self, raw_message: dict) -> None: handled_message, user_info = await self.handle_poke_notify(raw_message, group_id, user_id) else: logger.warning("戳一戳消息被禁用,取消戳一戳处理") + case NoticeType.Notify.group_name: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None + logger.info("处理群名称变更") + handled_message, user_info = await self.handle_group_name_notify(raw_message, group_id, user_id) + system_notice = True case _: logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}") case NoticeType.group_ban: @@ -1111,5 +1117,47 @@ async def handle_essence_notify( return notify_seg, user_info + async def handle_group_name_notify( + self, raw_message: dict, group_id: int, user_id: int + ) -> Tuple[Seg | None, UserInfo | None]: + """ + 处理群名称变更通知 + """ + new_name = raw_message.get("name_new") + + if not new_name: + logger.warning("群名称变更通知缺少新名称") + return None, None + + # 获取操作者信息 + user_info_dict: dict = await get_member_info(self.server_connection, group_id, user_id) + if user_info_dict: + user_name = user_info_dict.get("nickname") + user_cardname = user_info_dict.get("card") + else: + logger.warning("无法获取修改群名称的用户信息") + user_name = "QQ用户" + user_cardname = None + + user_info = UserInfo( + platform=global_config.maibot_server.platform_name, + user_id=user_id, + user_nickname=user_name, + user_cardname=user_cardname, + ) + + action_text = f"修改群名称为: {new_name}" + + notify_seg = Seg( + type="notify", + data={ + "sub_type": "group_name", + "action": action_text, + "new_name": new_name, + }, + ) + + return notify_seg, user_info + notice_handler = NoticeHandler() diff --git a/src/send_handler/send_message_handler.py b/src/send_handler/send_message_handler.py index 089353b..b5c70f5 100644 --- a/src/send_handler/send_message_handler.py +++ b/src/send_handler/send_message_handler.py @@ -54,8 +54,8 @@ def process_message_by_type(cls, seg: Seg, payload: List, in_forward: bool = Fal voice_url = seg.data new_payload = cls.build_payload(payload, cls.handle_voiceurl_message(voice_url), False) elif seg.type == "music": - song_id = seg.data - new_payload = cls.build_payload(payload, cls.handle_music_message(song_id), False) + music_data = seg.data + new_payload = cls.build_payload(payload, cls.handle_music_message(music_data), False) elif seg.type == "videourl": video_url = seg.data new_payload = cls.build_payload(payload, cls.handle_videourl_message(video_url), False) @@ -170,12 +170,42 @@ def handle_voiceurl_message(voice_url: str) -> dict: } @staticmethod - def handle_music_message(song_id: str) -> dict: - """处理音乐消息""" - return { - "type": "music", - "data": {"type": "163", "id": song_id}, - } + def handle_music_message(music_data) -> dict: + """ + 处理音乐消息 + music_data 可以是: + 1. 字符串:默认为网易云音乐ID + 2. 字典:{"type": "163"/"qq", "id": "歌曲ID"} + """ + # 兼容旧格式:直接传入歌曲ID字符串 + if isinstance(music_data, str): + return { + "type": "music", + "data": {"type": "163", "id": music_data}, + } + + # 新格式:字典包含平台和ID + if isinstance(music_data, dict): + platform = music_data.get("type", "163") # 默认网易云 + song_id = music_data.get("id", "") + + # 验证平台类型 + if platform not in ["163", "qq"]: + logger.warning(f"不支持的音乐平台: {platform},使用默认平台163") + platform = "163" + + # 确保ID是字符串 + if not isinstance(song_id, str): + song_id = str(song_id) + + return { + "type": "music", + "data": {"type": platform, "id": song_id}, + } + + # 其他情况返回空 + logger.error(f"不支持的音乐数据格式: {type(music_data)}") + return {} @staticmethod def handle_videourl_message(video_url: str) -> dict: From 417e30daca66051dea2a590c0ffebd061e3ae46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Thu, 11 Dec 2025 15:45:57 +0800 Subject: [PATCH 093/112] revert CI --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ec98f59..6438cf5 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ coverage.xml .hypothesis/ .pytest_cache/ cover/ +dev/ # Translations *.mo From af5b7f1a927e3ab11665086a4b1fda536490bbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 13 Dec 2025 15:12:01 +0800 Subject: [PATCH 094/112] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=92=8C=E9=9F=B3=E4=B9=90=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E8=BF=94=E5=9B=9E=E7=BB=93=E6=9E=84=E5=8C=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A0=BC=E5=BC=8F=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=88=86=E4=BA=AB=E6=B6=88=E6=81=AF=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_handler.py | 65 +++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index d52d21a..56a8c69 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -468,23 +468,23 @@ async def handle_video_message(self, raw_message: dict) -> Seg | None: Parameters: raw_message: dict: 原始消息 Returns: - seg_data: Seg: 处理后的消息段 + seg_data: Seg: 处理后的消息段(video_card类型) """ message_data: dict = raw_message.get("data") - file: str = message_data.get("file") - url: str = message_data.get("url") - file_size: str = message_data.get("file_size", "未知大小") + file: str = message_data.get("file", "") + url: str = message_data.get("url", "") + file_size: str = message_data.get("file_size", "") if not file: logger.warning("视频消息缺少文件信息") return None - # 视频消息返回文本描述,包含文件名和大小 - video_text = f"[视频: {file}, 大小: {file_size}字节]" - if url: - video_text += f"\n视频链接: {url}" - - return Seg(type="text", data=video_text) + # 返回结构化的视频卡片数据 + return Seg(type="video_card", data={ + "file": file, + "file_size": file_size, + "url": url + }) async def handle_json_message(self, raw_message: dict) -> Seg | None: """ @@ -544,13 +544,44 @@ async def handle_json_message(self, raw_message: dict) -> Seg | None: # 尝试从music字段提取信息 if music: title = music.get("title", "") - singer = music.get("singer", "") - music_text = "[音乐卡片]" - if title: - music_text += f"\n歌曲: {title}" - if singer: - music_text += f"\n歌手: {singer}" - return Seg(type="text", data=music_text) + singer = music.get("desc", "") or music.get("singer", "") + jump_url = music.get("jumpUrl", "") or music.get("jump_url", "") + music_url = music.get("musicUrl", "") or music.get("music_url", "") + tag = music.get("tag", "") # 音乐来源标签,如"网易云音乐" + preview = music.get("preview", "") # 封面图URL + + # 返回结构化的音乐卡片数据 + return Seg(type="music_card", data={ + "title": title, + "singer": singer, + "jump_url": jump_url, + "music_url": music_url, + "tag": tag, + "preview": preview + }) + + # 检查是否为小程序分享(如B站视频分享) + if app == "com.tencent.miniapp_01": + meta = parsed_json.get("meta", {}) + detail = meta.get("detail_1", {}) + + if detail: + title = detail.get("title", "") # 小程序名称,如"哔哩哔哩" + desc = detail.get("desc", "") # 分享内容描述 + url = detail.get("url", "") # 小程序链接 + qqdocurl = detail.get("qqdocurl", "") # 原始链接(如B站链接) + preview = detail.get("preview", "") # 预览图 + icon = detail.get("icon", "") # 小程序图标 + + # 返回结构化的小程序卡片数据 + return Seg(type="miniapp_card", data={ + "title": title, + "desc": desc, + "url": url, + "source_url": qqdocurl, + "preview": preview, + "icon": icon + }) # 其他卡片消息使用prompt字段 prompt = parsed_json.get("prompt", "[卡片消息]") From 76b02a0d81072e89d29c178bb71cff2b5930565b Mon Sep 17 00:00:00 2001 From: A0000Xz <122650088+A0000Xz@users.noreply.github.com> Date: Wed, 24 Dec 2025 04:08:02 +0800 Subject: [PATCH 095/112] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=B6=88=E6=81=AF=EF=BC=88=E5=A6=82=E7=BA=A2?= =?UTF-8?q?=E5=8C=85=EF=BC=89=E6=97=B6=E7=9A=84=E5=9B=9E=E5=A4=8D=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正reply_message在意外情况时的错误类型,使其从str变为list[Seg],保证正常的引用消息发出 --- src/recv_handler/message_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 56a8c69..b1cad75 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -632,7 +632,7 @@ async def handle_reply_message(self, raw_message: dict, additional_config: dict) return None, {} reply_message, _ = await self.handle_real_message(message_detail, in_reply=True) if reply_message is None: - reply_message = "(获取发言内容失败)" + reply_message = [Seg(type="text", data="(获取发言内容失败)")] sender_info: dict = message_detail.get("sender") sender_nickname: str = sender_info.get("nickname") sender_id: str = sender_info.get("user_id") From b0bfa1a42dd0afca8a107c6f965606b49e0eab50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 02:00:58 +0800 Subject: [PATCH 096/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=AE=A1=E7=90=86=E5=99=A8=E6=94=AF=E6=8C=81=E7=83=AD?= =?UTF-8?q?=E9=87=8D=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 ConfigManager 类,支持加载和热重载配置文件 - 使用 watchdog 监控配置文件变化,自动重载配置 - 支持为特定配置项注册回调函数,便于处理配置变更 - 提供多种配置属性访问接口,如 nickname、chat、voice 等 - 增加防抖机制,避免频繁重载导致的性能问题 --- main.py | 120 ++- pyproject.toml | 1 + requirements.txt | 3 +- src/config/__init__.py | 3 +- src/config/config.py | 12 +- src/config/config_manager.py | 276 ++++++ src/recv_handler/message_handler.py | 2 +- src/send_handler/send_command_handler.py | 149 ++- uv.lock | 1081 ++++++++++++++++++++++ 9 files changed, 1581 insertions(+), 66 deletions(-) create mode 100644 src/config/config_manager.py diff --git a/main.py b/main.py index b0986a6..5019eb6 100644 --- a/main.py +++ b/main.py @@ -14,20 +14,26 @@ from src.response_pool import put_response, check_timeout_response message_queue = asyncio.Queue() +websocket_server = None # 保存WebSocket服务器实例以便关闭 async def message_recv(server_connection: Server.ServerConnection): - await message_handler.set_server_connection(server_connection) - asyncio.create_task(notice_handler.set_server_connection(server_connection)) - await nc_message_sender.set_server_connection(server_connection) - async for raw_message in server_connection: - logger.debug(f"{raw_message[:1500]}..." if (len(raw_message) > 1500) else raw_message) - decoded_raw_message: dict = json.loads(raw_message) - post_type = decoded_raw_message.get("post_type") - if post_type in ["meta_event", "message", "notice"]: - await message_queue.put(decoded_raw_message) - elif post_type is None: - await put_response(decoded_raw_message) + try: + await message_handler.set_server_connection(server_connection) + asyncio.create_task(notice_handler.set_server_connection(server_connection)) + await nc_message_sender.set_server_connection(server_connection) + async for raw_message in server_connection: + logger.debug(f"{raw_message[:1500]}..." if (len(raw_message) > 1500) else raw_message) + decoded_raw_message: dict = json.loads(raw_message) + post_type = decoded_raw_message.get("post_type") + if post_type in ["meta_event", "message", "notice"]: + await message_queue.put(decoded_raw_message) + elif post_type is None: + await put_response(decoded_raw_message) + except asyncio.CancelledError: + logger.debug("message_recv 收到取消信号,正在关闭连接") + await server_connection.close() + raise async def message_process(): @@ -47,8 +53,72 @@ async def message_process(): async def main(): + # 启动配置文件监控并注册napcat_server配置变更回调 + from src.config import config_manager + + # 保存napcat_server任务的引用,用于重启 + napcat_task = None + restart_event = asyncio.Event() + + async def on_napcat_config_change(old_value, new_value): + """当napcat_server配置变更时,重启WebSocket服务器""" + nonlocal napcat_task + + logger.warning( + f"NapCat配置已变更:\n" + f" 旧配置: {old_value.host}:{old_value.port}\n" + f" 新配置: {new_value.host}:{new_value.port}" + ) + + # 关闭当前WebSocket服务器 + global websocket_server + if websocket_server: + try: + logger.info("正在关闭旧的WebSocket服务器...") + websocket_server.close() + await websocket_server.wait_closed() + logger.info("旧的WebSocket服务器已关闭") + except Exception as e: + logger.error(f"关闭旧WebSocket服务器失败: {e}") + + # 取消旧任务 + if napcat_task and not napcat_task.done(): + napcat_task.cancel() + try: + await napcat_task + except asyncio.CancelledError: + pass + + # 触发重启 + restart_event.set() + + config_manager.on_config_change("napcat_server", on_napcat_config_change) + + # 启动文件监控 + asyncio.create_task(config_manager.start_watch()) + + # WebSocket服务器重启循环 + async def napcat_with_restart(): + nonlocal napcat_task + while True: + restart_event.clear() + try: + await napcat_server() + except asyncio.CancelledError: + break + except Exception as e: + logger.error(f"NapCat服务器异常: {e}") + break + + # 等待重启信号 + if not restart_event.is_set(): + break + + logger.info("正在重启WebSocket服务器...") + await asyncio.sleep(1) # 等待1秒后重启 + message_send_instance.maibot_router = router - _ = await asyncio.gather(napcat_server(), mmc_start_com(), message_process(), check_timeout_response()) + _ = await asyncio.gather(napcat_with_restart(), mmc_start_com(), message_process(), check_timeout_response()) def check_napcat_server_token(conn, request): token = global_config.napcat_server.token @@ -64,6 +134,7 @@ def check_napcat_server_token(conn, request): return None async def napcat_server(): + global websocket_server logger.info("正在启动 MaiBot-Napcat-Adapter...") logger.debug(f"日志等级: {global_config.debug.level}") logger.debug("日志文件: logs/adapter_*.log") @@ -75,10 +146,15 @@ async def napcat_server(): max_size=2**26, process_request=check_napcat_server_token ) as server: + websocket_server = server logger.success( f"✅ Adapter 启动成功! 监听: ws://{global_config.napcat_server.host}:{global_config.napcat_server.port}" ) - await server.serve_forever() + try: + await server.serve_forever() + except asyncio.CancelledError: + logger.debug("napcat_server 收到取消信号") + raise except OSError: # 端口绑定失败时抛出异常让外层处理 raise @@ -90,13 +166,24 @@ async def graceful_shutdown(silent: bool = False): Args: silent: 静默模式,控制台不输出日志,但仍记录到文件 """ + global websocket_server try: if not silent: logger.info("正在关闭adapter...") else: logger.debug("正在清理资源...") - # 先关闭MMC连接 + # 先关闭WebSocket服务器 + if websocket_server: + try: + logger.debug("正在关闭WebSocket服务器") + websocket_server.close() + await websocket_server.wait_closed() + logger.debug("WebSocket服务器已关闭") + except Exception as e: + logger.debug(f"关闭WebSocket服务器时出现错误: {e}") + + # 关闭MMC连接 try: await asyncio.wait_for(mmc_stop_com(), timeout=3) except asyncio.TimeoutError: @@ -151,10 +238,11 @@ async def graceful_shutdown(silent: bool = False): logger.error(" 1. 是否有其他 MaiBot-Napcat-Adapter 实例正在运行") logger.error(" 2. 修改 config.toml 中的 port 配置") logger.error(f" 3. 使用命令查看占用进程: netstat -ano | findstr {global_config.napcat_server.port}") - logger.debug("完整错误信息:", exc_info=True) else: logger.error(f"❌ 网络错误: {str(e)}") - logger.debug("完整错误信息:", exc_info=True) + + logger.debug("完整错误信息:", exc_info=True) + # 端口占用时静默清理(控制台不输出,但记录到日志文件) try: loop.run_until_complete(graceful_shutdown(silent=True)) diff --git a/pyproject.toml b/pyproject.toml index 5db9a29..749e59a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "sqlmodel>=0.0.27", "tomlkit>=0.13.3", "websockets>=15.0.1", + "watchdog>=3.0.0", ] [tool.ruff] diff --git a/requirements.txt b/requirements.txt index 5757fb5..817dc53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ loguru pillow tomlkit rich -sqlmodel \ No newline at end of file +sqlmodel +watchdog \ No newline at end of file diff --git a/src/config/__init__.py b/src/config/__init__.py index 40ba89a..e6d30db 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -1,5 +1,6 @@ -from .config import global_config +from .config import global_config, _config_manager as config_manager __all__ = [ "global_config", + "config_manager", ] diff --git a/src/config/config.py b/src/config/config.py index f3b90bb..bdf7837 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -142,5 +142,15 @@ def load_config(config_path: str) -> Config: update_config() logger.info("正在品鉴配置文件...") -global_config = load_config(config_path="config.toml") + +# 创建配置管理器 +from .config_manager import ConfigManager + +_config_manager = ConfigManager() +_config_manager.load(config_path="config.toml") + +# 向后兼容:global_config 指向配置管理器 +# 所有现有代码可以继续使用 global_config.chat.xxx 访问配置 +global_config = _config_manager + logger.info("非常的新鲜,非常的美味!") diff --git a/src/config/config_manager.py b/src/config/config_manager.py new file mode 100644 index 0000000..046f15d --- /dev/null +++ b/src/config/config_manager.py @@ -0,0 +1,276 @@ +"""配置管理器 - 支持热重载""" +import asyncio +import os +from typing import Callable, Dict, List, Any, Optional +from datetime import datetime +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler, FileModifiedEvent + +from ..logger import logger +from .config import Config, load_config +from .official_configs import ( + ChatConfig, + DebugConfig, + MaiBotServerConfig, + NapcatServerConfig, + NicknameConfig, + VoiceConfig, +) + + +class ConfigManager: + """配置管理器 - 混合模式(属性代理 + 选择性回调) + + 支持热重载配置文件,使用watchdog实时监控文件变化。 + 需要特殊处理的配置项可以注册回调函数。 + """ + + def __init__(self) -> None: + self._config: Optional[Config] = None + self._config_path: str = "config.toml" + self._lock: asyncio.Lock = asyncio.Lock() + self._callbacks: Dict[str, List[Callable]] = {} + + # Watchdog相关 + self._observer: Optional[Observer] = None + self._event_handler: Optional[FileSystemEventHandler] = None + self._reload_debounce_task: Optional[asyncio.Task] = None + self._debounce_delay: float = 0.5 # 防抖延迟(秒) + self._loop: Optional[asyncio.AbstractEventLoop] = None # 事件循环引用 + + def load(self, config_path: str = "config.toml") -> None: + """加载配置文件 + + Args: + config_path: 配置文件路径 + """ + self._config_path = os.path.abspath(config_path) + self._config = load_config(config_path) + + logger.info(f"配置已加载: {config_path}") + + async def reload(self, config_path: Optional[str] = None) -> bool: + """重载配置文件(热重载) + + Args: + config_path: 配置文件路径,如果为None则使用初始路径 + + Returns: + bool: 是否重载成功 + """ + if config_path is None: + config_path = self._config_path + + async with self._lock: + old_config = self._config + try: + new_config = load_config(config_path) + + if old_config is not None: + await self._notify_changes(old_config, new_config) + + self._config = new_config + logger.info(f"配置重载成功: {config_path}") + return True + + except Exception as e: + logger.error(f"配置重载失败: {e}", exc_info=True) + return False + + def on_config_change( + self, + config_path: str, + callback: Callable[[Any, Any], Any] + ) -> None: + """为特定配置路径注册回调函数 + + Args: + config_path: 配置路径,如 'napcat_server', 'chat.ban_user_id', 'debug.level' + callback: 回调函数,签名为 async def callback(old_value, new_value) + """ + if config_path not in self._callbacks: + self._callbacks[config_path] = [] + self._callbacks[config_path].append(callback) + logger.debug(f"已注册配置变更回调: {config_path}") + + async def _notify_changes(self, old_config: Config, new_config: Config) -> None: + """通知配置变更 + + Args: + old_config: 旧配置对象 + new_config: 新配置对象 + """ + for config_path, callbacks in self._callbacks.items(): + try: + old_value = self._get_value(old_config, config_path) + new_value = self._get_value(new_config, config_path) + + if old_value != new_value: + logger.info(f"检测到配置变更: {config_path}") + for callback in callbacks: + try: + if asyncio.iscoroutinefunction(callback): + await callback(old_value, new_value) + else: + callback(old_value, new_value) + except Exception as e: + logger.error( + f"配置变更回调执行失败 [{config_path}]: {e}", + exc_info=True + ) + except Exception as e: + logger.error(f"获取配置值失败 [{config_path}]: {e}") + + def _get_value(self, config: Config, path: str) -> Any: + """获取嵌套配置值 + + Args: + config: 配置对象 + path: 配置路径,支持点分隔的嵌套路径 + + Returns: + Any: 配置值 + + Raises: + AttributeError: 配置路径不存在 + """ + parts = path.split('.') + value = config + for part in parts: + value = getattr(value, part) + return value + + @property + def nickname(self) -> NicknameConfig: + """昵称配置""" + if self._config is None: + raise RuntimeError("配置尚未加载,请先调用 load() 方法") + return self._config.nickname + + @property + def chat(self) -> ChatConfig: + """聊天配置""" + if self._config is None: + raise RuntimeError("配置尚未加载,请先调用 load() 方法") + return self._config.chat + + @property + def voice(self) -> VoiceConfig: + """语音配置""" + if self._config is None: + raise RuntimeError("配置尚未加载,请先调用 load() 方法") + return self._config.voice + + @property + def napcat_server(self) -> NapcatServerConfig: + """NapCat服务器配置""" + if self._config is None: + raise RuntimeError("配置尚未加载,请先调用 load() 方法") + return self._config.napcat_server + + @property + def maibot_server(self) -> MaiBotServerConfig: + """MaiBot服务器配置""" + if self._config is None: + raise RuntimeError("配置尚未加载,请先调用 load() 方法") + return self._config.maibot_server + + @property + def debug(self) -> DebugConfig: + """调试配置""" + if self._config is None: + raise RuntimeError("配置尚未加载,请先调用 load() 方法") + return self._config.debug + + async def start_watch(self) -> None: + """启动配置文件监控(需要在事件循环中调用)""" + if self._observer is not None: + logger.warning("配置文件监控已在运行") + return + + # 保存当前事件循环引用 + self._loop = asyncio.get_running_loop() + + # 创建文件监控事件处理器 + config_file_path = self._config_path + + class ConfigFileHandler(FileSystemEventHandler): + def __init__(handler_self, manager: "ConfigManager"): + handler_self.manager = manager + handler_self.config_path = config_file_path + + def on_modified(handler_self, event): + # 检查是否是目标配置文件修改事件 + if isinstance(event, FileModifiedEvent) and os.path.abspath(event.src_path) == handler_self.config_path: + logger.debug(f"检测到配置文件变更: {event.src_path}") + # 使用防抖机制避免重复重载 + # watchdog运行在独立线程,需要使用run_coroutine_threadsafe + if handler_self.manager._loop: + asyncio.run_coroutine_threadsafe( + handler_self.manager._debounced_reload(), + handler_self.manager._loop + ) + + self._event_handler = ConfigFileHandler(self) + + # 创建Observer并监控配置文件所在目录 + self._observer = Observer() + watch_dir = os.path.dirname(self._config_path) or "." + + self._observer.schedule(self._event_handler, watch_dir, recursive=False) + self._observer.start() + + logger.info(f"已启动配置文件实时监控: {self._config_path}") + + async def stop_watch(self) -> None: + """停止配置文件监控""" + if self._observer is None: + return + + logger.debug("正在停止配置文件监控") + + # 取消防抖任务 + if self._reload_debounce_task: + self._reload_debounce_task.cancel() + try: + await self._reload_debounce_task + except asyncio.CancelledError: + pass + + # 停止observer + self._observer.stop() + self._observer.join(timeout=2) + self._observer = None + self._event_handler = None + + logger.info("配置文件监控已停止") + + async def _debounced_reload(self) -> None: + """防抖重载:避免短时间内多次文件修改事件导致重复重载""" + # 取消之前的防抖任务 + if self._reload_debounce_task and not self._reload_debounce_task.done(): + self._reload_debounce_task.cancel() + + # 等待防抖延迟 + await asyncio.sleep(self._debounce_delay) + + # 执行重载 + modified_time = datetime.fromtimestamp( + os.path.getmtime(self._config_path) + ).strftime("%Y-%m-%d %H:%M:%S") + + logger.info( + f"配置文件已更新 (修改时间: {modified_time}),正在重载..." + ) + + success = await self.reload() + + if not success: + logger.error( + "配置文件重载失败!请检查配置文件格式是否正确。\n" + "当前仍使用旧配置运行,修复配置文件后将自动重试。" + ) + + def __repr__(self) -> str: + watching = self._observer is not None and self._observer.is_alive() + return f"" diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index b1cad75..41354cb 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -537,7 +537,7 @@ async def handle_json_message(self, raw_message: dict) -> Seg | None: return Seg(type="text", data=announce_text) # 检查是否为音乐卡片 - if app == "com.tencent.music.lua" or app == "com.tencent.structmsg": + if app in ("com.tencent.music.lua", "com.tencent.structmsg"): meta = parsed_json.get("meta", {}) music = meta.get("music", {}) diff --git a/src/send_handler/send_command_handler.py b/src/send_handler/send_command_handler.py index dff9505..d7eeec1 100644 --- a/src/send_handler/send_command_handler.py +++ b/src/send_handler/send_command_handler.py @@ -1,44 +1,83 @@ from maim_message import GroupInfo -from typing import Any, Dict, Tuple +from typing import Any, Dict, Tuple, Callable, Optional from src import CommandType +# 全局命令处理器注册表(在类外部定义以避免循环引用) +_command_handlers: Dict[str, Dict[str, Any]] = {} + + +def register_command(command_type: CommandType, require_group: bool = True): + """装饰器:注册命令处理器 + + Args: + command_type: 命令类型 + require_group: 是否需要群聊信息,默认为True + + Returns: + 装饰器函数 + """ + + def decorator(func: Callable) -> Callable: + _command_handlers[command_type.name] = { + "handler": func, + "require_group": require_group, + } + return func + + return decorator + + class SendCommandHandleClass: @classmethod - def handle_command(cls, raw_command_data: Dict[str, Any], group_info: GroupInfo): + def handle_command(cls, raw_command_data: Dict[str, Any], group_info: Optional[GroupInfo]): + """统一命令处理入口 + + Args: + raw_command_data: 原始命令数据 + group_info: 群聊信息(可选) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) 用于发送给NapCat + + Raises: + RuntimeError: 命令类型未知或处理失败 + """ command_name: str = raw_command_data.get("name") + + if command_name not in _command_handlers: + raise RuntimeError(f"未知的命令类型: {command_name}") + try: - match command_name: - case CommandType.GROUP_BAN.name: - return cls.handle_ban_command(raw_command_data.get("args", {}), group_info) - case CommandType.GROUP_WHOLE_BAN.name: - return cls.handle_whole_ban_command(raw_command_data.get("args", {}), group_info) - case CommandType.GROUP_KICK.name: - return cls.handle_kick_command(raw_command_data.get("args", {}), group_info) - case CommandType.SEND_POKE.name: - return cls.handle_poke_command(raw_command_data.get("args", {}), group_info) - case CommandType.DELETE_MSG.name: - return cls.delete_msg_command(raw_command_data.get("args", {})) - case CommandType.AI_VOICE_SEND.name: - return cls.handle_ai_voice_send_command(raw_command_data.get("args", {}), group_info) - case CommandType.MESSAGE_LIKE.name: - return cls.handle_message_like_command(raw_command_data.get("args", {})) - case _: - raise RuntimeError(f"未知的命令类型: {command_name}") + handler_info = _command_handlers[command_name] + handler = handler_info["handler"] + require_group = handler_info["require_group"] + + # 检查群聊信息要求 + if require_group and not group_info: + raise ValueError(f"命令 {command_name} 需要在群聊上下文中使用") + + # 调用处理器 + args = raw_command_data.get("args", {}) + return handler(args, group_info) + except Exception as e: - raise RuntimeError(f"处理命令时出错: {str(e)}") from e + raise RuntimeError(f"处理命令 {command_name} 时出错: {str(e)}") from e + + # ============ 命令处理器(使用装饰器注册)============ @staticmethod - def handle_ban_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + @register_command(CommandType.GROUP_BAN, require_group=True) + def handle_ban_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理封禁命令 Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) + args: 参数字典 {"qq_id": int, "duration": int} + group_info: 群聊信息(对应目标群聊) Returns: - Tuple[CommandType, Dict[str, Any]] + Tuple[str, Dict[str, Any]]: (action, params) """ duration: int = int(args["duration"]) user_id: int = int(args["qq_id"]) @@ -59,15 +98,16 @@ def handle_ban_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str ) @staticmethod - def handle_whole_ban_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + @register_command(CommandType.GROUP_WHOLE_BAN, require_group=True) + def handle_whole_ban_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理全体禁言命令 Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) + args: 参数字典 {"enable": bool} + group_info: 群聊信息(对应目标群聊) Returns: - Tuple[CommandType, Dict[str, Any]] + Tuple[str, Dict[str, Any]]: (action, params) """ enable = args["enable"] assert isinstance(enable, bool), "enable参数必须是布尔值" @@ -83,15 +123,16 @@ def handle_whole_ban_command(args: Dict[str, Any], group_info: GroupInfo) -> Tup ) @staticmethod - def handle_kick_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + @register_command(CommandType.GROUP_KICK, require_group=True) + def handle_kick_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理群成员踢出命令 Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) + args: 参数字典 {"qq_id": int} + group_info: 群聊信息(对应目标群聊) Returns: - Tuple[CommandType, Dict[str, Any]] + Tuple[str, Dict[str, Any]]: (action, params) """ user_id: int = int(args["qq_id"]) group_id: int = int(group_info.group_id) @@ -109,15 +150,16 @@ def handle_kick_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[st ) @staticmethod - def handle_poke_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: + @register_command(CommandType.SEND_POKE, require_group=False) + def handle_poke_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理戳一戳命令 Args: - args (Dict[str, Any]): 参数字典 - group_info (GroupInfo): 群聊信息(对应目标群聊) + args: 参数字典 {"qq_id": int} + group_info: 群聊信息(可选,私聊时为None) Returns: - Tuple[CommandType, Dict[str, Any]] + Tuple[str, Dict[str, Any]]: (action, params) """ user_id: int = int(args["qq_id"]) if group_info is None: @@ -137,14 +179,16 @@ def handle_poke_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[st ) @staticmethod - def delete_msg_command(args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: + @register_command(CommandType.DELETE_MSG, require_group=False) + def delete_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理撤回消息命令 Args: - args (Dict[str, Any]): 参数字典 + args: 参数字典 {"message_id": int} + group_info: 群聊信息(不使用) Returns: - Tuple[CommandType, Dict[str, Any]] + Tuple[str, Dict[str, Any]]: (action, params) """ try: message_id = int(args["message_id"]) @@ -163,10 +207,16 @@ def delete_msg_command(args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: ) @staticmethod - def handle_ai_voice_send_command(args: Dict[str, Any], group_info: GroupInfo) -> Tuple[str, Dict[str, Any]]: - """ - 处理AI语音发送命令的逻辑。 - 并返回 NapCat 兼容的 (action, params) 元组。 + @register_command(CommandType.AI_VOICE_SEND, require_group=True) + def handle_ai_voice_send_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """处理AI语音发送命令 + + Args: + args: 参数字典 {"character": str, "text": str} + group_info: 群聊信息 + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) """ if not group_info or not group_info.group_id: raise ValueError("AI语音发送命令必须在群聊上下文中使用") @@ -190,9 +240,16 @@ def handle_ai_voice_send_command(args: Dict[str, Any], group_info: GroupInfo) -> ) @staticmethod - def handle_message_like_command(args: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: - """ - 处理给消息贴表情的逻辑。 + @register_command(CommandType.MESSAGE_LIKE, require_group=False) + def handle_message_like_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """处理给消息贴表情命令 + + Args: + args: 参数字典 {"message_id": int, "emoji_id": int} + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) """ if not args: raise ValueError("消息贴表情命令缺少参数") diff --git a/uv.lock b/uv.lock index 8c87f71..9ffe115 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,1088 @@ version = 1 revision = 2 requires-python = ">=3.13" +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload_time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload_time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload_time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload_time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload_time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload_time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload_time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload_time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload_time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload_time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload_time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload_time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload_time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload_time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload_time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload_time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload_time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload_time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload_time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload_time = "2025-10-28T20:57:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload_time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload_time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload_time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload_time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload_time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload_time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload_time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload_time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload_time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload_time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload_time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload_time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload_time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload_time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload_time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload_time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload_time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload_time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload_time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload_time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload_time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload_time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload_time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload_time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload_time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload_time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload_time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload_time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload_time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload_time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload_time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload_time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload_time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload_time = "2025-10-28T20:58:52.782Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload_time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload_time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload_time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload_time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload_time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload_time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload_time = "2025-11-28T23:37:38.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload_time = "2025-11-28T23:36:57.897Z" }, +] + +[[package]] +name = "asyncio" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/ea/26c489a11f7ca862d5705db67683a7361ce11c23a7b98fc6c2deaeccede2/asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b", size = 5371, upload_time = "2025-08-05T02:51:46.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/64/eff2564783bd650ca25e15938d1c5b459cda997574a510f7de69688cb0b4/asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b", size = 5555, upload_time = "2025-08-05T02:51:45.767Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload_time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload_time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload_time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload_time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload_time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload_time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload_time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload_time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload_time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload_time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload_time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload_time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload_time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload_time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload_time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload_time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload_time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload_time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload_time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload_time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload_time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload_time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload_time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload_time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload_time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload_time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload_time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload_time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload_time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload_time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload_time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload_time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload_time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload_time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload_time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload_time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload_time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload_time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload_time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload_time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload_time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload_time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload_time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload_time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload_time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload_time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload_time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload_time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload_time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload_time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload_time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload_time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload_time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload_time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload_time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload_time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload_time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload_time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload_time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload_time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload_time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload_time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload_time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload_time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload_time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload_time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload_time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload_time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload_time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload_time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload_time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload_time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload_time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload_time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload_time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload_time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload_time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload_time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload_time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload_time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload_time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload_time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload_time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload_time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload_time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload_time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload_time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload_time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload_time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload_time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload_time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload_time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload_time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload_time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload_time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload_time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload_time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload_time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload_time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload_time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload_time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload_time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload_time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload_time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload_time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload_time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload_time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload_time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload_time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload_time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload_time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload_time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload_time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload_time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload_time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload_time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload_time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload_time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload_time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload_time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload_time = "2025-10-15T23:18:12.277Z" }, +] + +[[package]] +name = "fastapi" +version = "0.128.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload_time = "2025-12-27T15:21:13.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload_time = "2025-12-27T15:21:12.154Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload_time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload_time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload_time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload_time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload_time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload_time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload_time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload_time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload_time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload_time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload_time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload_time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload_time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload_time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload_time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload_time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload_time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload_time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload_time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload_time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload_time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload_time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload_time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload_time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload_time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload_time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload_time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload_time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload_time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload_time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload_time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload_time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload_time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload_time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload_time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload_time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload_time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload_time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload_time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload_time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload_time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload_time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload_time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload_time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload_time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload_time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload_time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload_time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload_time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload_time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload_time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload_time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload_time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload_time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload_time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload_time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload_time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload_time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload_time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload_time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload_time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload_time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload_time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload_time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload_time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload_time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "greenlet" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload_time = "2025-12-04T14:49:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload_time = "2025-12-04T14:23:01.282Z" }, + { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload_time = "2025-12-04T14:50:08.309Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload_time = "2025-12-04T14:57:43.968Z" }, + { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload_time = "2025-12-04T15:07:14.697Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload_time = "2025-12-04T14:26:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload_time = "2025-12-04T15:04:25.276Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload_time = "2025-12-04T14:27:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload_time = "2025-12-04T14:32:23.929Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload_time = "2025-12-04T14:23:05.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload_time = "2025-12-04T14:50:10.026Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload_time = "2025-12-04T14:57:45.41Z" }, + { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload_time = "2025-12-04T15:07:15.789Z" }, + { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload_time = "2025-12-04T14:26:05.099Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload_time = "2025-12-04T15:04:27.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload_time = "2025-12-04T14:27:32.366Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload_time = "2025-12-04T14:26:51.063Z" }, + { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload_time = "2025-12-04T14:25:20.941Z" }, + { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload_time = "2025-12-04T14:50:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload_time = "2025-12-04T14:57:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload_time = "2025-12-04T15:07:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload_time = "2025-12-04T14:26:06.225Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload_time = "2025-12-04T15:04:28.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload_time = "2025-12-04T14:27:33.531Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload_time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload_time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload_time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload_time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload_time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload_time = "2024-12-06T11:20:54.538Z" }, +] + [[package]] name = "maibotnapcatadapter" version = "0.5.5" source = { virtual = "." } +dependencies = [ + { name = "aiohttp" }, + { name = "asyncio" }, + { name = "loguru" }, + { name = "maim-message" }, + { name = "pillow" }, + { name = "requests" }, + { name = "rich" }, + { name = "sqlmodel" }, + { name = "tomlkit" }, + { name = "watchdog" }, + { name = "websockets" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", specifier = ">=3.13.2" }, + { name = "asyncio", specifier = ">=4.0.0" }, + { name = "loguru", specifier = ">=0.7.3" }, + { name = "maim-message", specifier = ">=0.5.7" }, + { name = "pillow", specifier = ">=12.0.0" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "rich", specifier = ">=14.2.0" }, + { name = "sqlmodel", specifier = ">=0.0.27" }, + { name = "tomlkit", specifier = ">=0.13.3" }, + { name = "watchdog", specifier = ">=3.0.0" }, + { name = "websockets", specifier = ">=15.0.1" }, +] + +[[package]] +name = "maim-message" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "cryptography" }, + { name = "fastapi" }, + { name = "pydantic" }, + { name = "uvicorn" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/42/adea9b875803fe8a2d35e171a036acf57c7dc7829ad246289e25fd4ebc0b/maim_message-0.6.1.tar.gz", hash = "sha256:96be12e2c487856b825ee7ca2a4dd7056664357975be47ccdccab66855b87ae0", size = 731984, upload_time = "2025-12-26T11:29:40.69Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/21/f6f3bfbe6594edaf95693e521d59afbccd7d2fc624c6b9a177c0042e28e1/maim_message-0.6.1-py3-none-any.whl", hash = "sha256:e6c28c76adbdf90cb15123594506087cb02a6f6e8c9f34751828a62c4c64ec86", size = 98190, upload_time = "2025-12-26T11:29:39.073Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload_time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload_time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload_time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload_time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload_time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload_time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload_time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload_time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload_time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload_time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload_time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload_time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload_time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload_time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload_time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload_time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload_time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload_time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload_time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload_time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload_time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload_time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload_time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload_time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload_time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload_time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload_time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload_time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload_time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload_time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload_time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload_time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload_time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload_time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload_time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload_time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload_time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload_time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload_time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload_time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload_time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload_time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload_time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload_time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload_time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload_time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload_time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload_time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload_time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload_time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload_time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload_time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload_time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload_time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload_time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload_time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload_time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload_time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload_time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload_time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload_time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload_time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload_time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload_time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload_time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload_time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload_time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload_time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload_time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload_time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload_time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload_time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload_time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload_time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload_time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload_time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload_time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload_time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload_time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload_time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload_time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload_time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload_time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload_time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload_time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload_time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload_time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload_time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload_time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload_time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload_time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload_time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload_time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload_time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload_time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload_time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload_time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload_time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload_time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload_time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload_time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload_time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload_time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload_time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload_time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload_time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload_time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload_time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload_time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload_time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload_time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload_time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload_time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload_time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload_time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload_time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload_time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload_time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload_time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload_time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload_time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload_time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload_time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload_time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload_time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload_time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload_time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload_time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload_time = "2026-01-02T09:13:12.068Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload_time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload_time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload_time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload_time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload_time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload_time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload_time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload_time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload_time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload_time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload_time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload_time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload_time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload_time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload_time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload_time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload_time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload_time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload_time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload_time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload_time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload_time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload_time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload_time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload_time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload_time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload_time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload_time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload_time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload_time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload_time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload_time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload_time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload_time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload_time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload_time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload_time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload_time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload_time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload_time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload_time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload_time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload_time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload_time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload_time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload_time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload_time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload_time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload_time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload_time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload_time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload_time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload_time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload_time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload_time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload_time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload_time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload_time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload_time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload_time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload_time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload_time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload_time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload_time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload_time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload_time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload_time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload_time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload_time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload_time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload_time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload_time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload_time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload_time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload_time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload_time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload_time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload_time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload_time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload_time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload_time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload_time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload_time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload_time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload_time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload_time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload_time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload_time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload_time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload_time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload_time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload_time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload_time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload_time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload_time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload_time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload_time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload_time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload_time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload_time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload_time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload_time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload_time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload_time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload_time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload_time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload_time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload_time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload_time = "2025-11-04T13:42:01.186Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload_time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload_time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload_time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload_time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload_time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload_time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload_time = "2025-12-09T21:05:16.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload_time = "2025-12-09T22:11:06.167Z" }, + { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload_time = "2025-12-09T22:13:52.626Z" }, + { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload_time = "2025-12-09T22:11:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload_time = "2025-12-09T22:13:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload_time = "2025-12-09T21:39:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload_time = "2025-12-09T21:39:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload_time = "2025-12-09T22:13:28.622Z" }, + { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload_time = "2025-12-09T22:13:30.188Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload_time = "2025-12-09T22:11:09.662Z" }, + { url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload_time = "2025-12-09T22:13:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload_time = "2025-12-09T22:11:11.1Z" }, + { url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload_time = "2025-12-09T22:13:57.932Z" }, + { url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload_time = "2025-12-09T21:39:38.365Z" }, + { url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload_time = "2025-12-09T21:39:39.503Z" }, + { url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload_time = "2025-12-09T22:13:32.09Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload_time = "2025-12-09T22:13:33.739Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload_time = "2025-12-09T21:54:52.608Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.31" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload_time = "2025-12-28T12:35:01.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload_time = "2025-12-28T12:35:00.108Z" }, +] + +[[package]] +name = "starlette" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload_time = "2025-11-01T15:25:27.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload_time = "2025-11-01T15:25:25.461Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload_time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload_time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload_time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload_time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload_time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload_time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload_time = "2025-12-11T15:56:40.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload_time = "2025-12-11T15:56:38.584Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload_time = "2025-12-21T14:16:22.45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload_time = "2025-12-21T14:16:21.041Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload_time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload_time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload_time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload_time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload_time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload_time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload_time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload_time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload_time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload_time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload_time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload_time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload_time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload_time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload_time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload_time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload_time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload_time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload_time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload_time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload_time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload_time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload_time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload_time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload_time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload_time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload_time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload_time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload_time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload_time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload_time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload_time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload_time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload_time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload_time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload_time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload_time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload_time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload_time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload_time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload_time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload_time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload_time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload_time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload_time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload_time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload_time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload_time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload_time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload_time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload_time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload_time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload_time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload_time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload_time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload_time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload_time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload_time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload_time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload_time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload_time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload_time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload_time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload_time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload_time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload_time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload_time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload_time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload_time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload_time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload_time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload_time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload_time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload_time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload_time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload_time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload_time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload_time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload_time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload_time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload_time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload_time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload_time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload_time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload_time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload_time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload_time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload_time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload_time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload_time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload_time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload_time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload_time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload_time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload_time = "2025-10-06T14:12:53.872Z" }, +] From d40663709cb7bd9949b5b79eb37fe596be583a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 02:08:35 +0800 Subject: [PATCH 097/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0uv.lock?= =?UTF-8?q?=E5=88=B0.gitignore=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3dd0281..116b41d 100644 --- a/.gitignore +++ b/.gitignore @@ -149,6 +149,7 @@ venv/ ENV/ env.bak/ venv.bak/ +uv.lock # Spyder project settings .spyderproject From 66a1c0840566064cc20eb5e7ae68ba9fab90cf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 02:31:22 +0800 Subject: [PATCH 098/112] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=87=8D=E8=BD=BD=E9=80=BB=E8=BE=91=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=87=8D=E8=BD=BD=E7=8A=B6=E6=80=81=E6=A0=87=E8=AE=B0?= =?UTF-8?q?=E5=92=8C=E6=97=B6=E9=97=B4=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config_manager.py | 51 +++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/config/config_manager.py b/src/config/config_manager.py index 046f15d..3e302df 100644 --- a/src/config/config_manager.py +++ b/src/config/config_manager.py @@ -37,6 +37,8 @@ def __init__(self) -> None: self._reload_debounce_task: Optional[asyncio.Task] = None self._debounce_delay: float = 0.5 # 防抖延迟(秒) self._loop: Optional[asyncio.AbstractEventLoop] = None # 事件循环引用 + self._is_reloading: bool = False # 标记是否正在重载 + self._last_reload_trigger: float = 0.0 # 最后一次触发重载的时间 def load(self, config_path: str = "config.toml") -> None: """加载配置文件 @@ -247,29 +249,46 @@ async def stop_watch(self) -> None: async def _debounced_reload(self) -> None: """防抖重载:避免短时间内多次文件修改事件导致重复重载""" - # 取消之前的防抖任务 - if self._reload_debounce_task and not self._reload_debounce_task.done(): - self._reload_debounce_task.cancel() + import time + + # 记录当前触发时间 + trigger_time = time.time() + self._last_reload_trigger = trigger_time # 等待防抖延迟 await asyncio.sleep(self._debounce_delay) - # 执行重载 - modified_time = datetime.fromtimestamp( - os.path.getmtime(self._config_path) - ).strftime("%Y-%m-%d %H:%M:%S") - - logger.info( - f"配置文件已更新 (修改时间: {modified_time}),正在重载..." - ) + # 检查是否有更新的触发 + if self._last_reload_trigger > trigger_time: + # 有更新的触发,放弃本次重载 + logger.debug("放弃过时的重载请求") + return - success = await self.reload() + # 检查是否已有重载在进行 + if self._is_reloading: + logger.debug("重载已在进行中,跳过") + return - if not success: - logger.error( - "配置文件重载失败!请检查配置文件格式是否正确。\n" - "当前仍使用旧配置运行,修复配置文件后将自动重试。" + # 执行重载 + self._is_reloading = True + try: + modified_time = datetime.fromtimestamp( + os.path.getmtime(self._config_path) + ).strftime("%Y-%m-%d %H:%M:%S") + + logger.info( + f"配置文件已更新 (修改时间: {modified_time}),正在重载..." ) + + success = await self.reload() + + if not success: + logger.error( + "配置文件重载失败!请检查配置文件格式是否正确。\n" + "当前仍使用旧配置运行,修复配置文件后将自动重试。" + ) + finally: + self._is_reloading = False def __repr__(self) -> str: watching = self._observer is not None and self._observer.is_alive() From efd98b022f74b4c8bfd6ba259ef7aab60ed7cd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 02:34:15 +0800 Subject: [PATCH 099/112] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E7=9A=84=E9=85=8D=E7=BD=AE=E5=B1=9E=E6=80=A7=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=B9=E4=B8=BA=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E4=BB=A3=E7=90=86=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config_manager.py | 80 +++++++++++++++--------------------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/src/config/config_manager.py b/src/config/config_manager.py index 3e302df..b888ab7 100644 --- a/src/config/config_manager.py +++ b/src/config/config_manager.py @@ -8,14 +8,6 @@ from ..logger import logger from .config import Config, load_config -from .official_configs import ( - ChatConfig, - DebugConfig, - MaiBotServerConfig, - NapcatServerConfig, - NicknameConfig, - VoiceConfig, -) class ConfigManager: @@ -142,47 +134,41 @@ def _get_value(self, config: Config, path: str) -> Any: value = getattr(value, part) return value - @property - def nickname(self) -> NicknameConfig: - """昵称配置""" - if self._config is None: - raise RuntimeError("配置尚未加载,请先调用 load() 方法") - return self._config.nickname - - @property - def chat(self) -> ChatConfig: - """聊天配置""" - if self._config is None: - raise RuntimeError("配置尚未加载,请先调用 load() 方法") - return self._config.chat - - @property - def voice(self) -> VoiceConfig: - """语音配置""" - if self._config is None: - raise RuntimeError("配置尚未加载,请先调用 load() 方法") - return self._config.voice - - @property - def napcat_server(self) -> NapcatServerConfig: - """NapCat服务器配置""" - if self._config is None: - raise RuntimeError("配置尚未加载,请先调用 load() 方法") - return self._config.napcat_server - - @property - def maibot_server(self) -> MaiBotServerConfig: - """MaiBot服务器配置""" - if self._config is None: - raise RuntimeError("配置尚未加载,请先调用 load() 方法") - return self._config.maibot_server - - @property - def debug(self) -> DebugConfig: - """调试配置""" + def __getattr__(self, name: str) -> Any: + """动态代理配置属性访问 + + 支持直接访问配置对象的属性,如: + - config_manager.napcat_server + - config_manager.chat + - config_manager.debug + + Args: + name: 属性名 + + Returns: + Any: 配置对象的对应属性值 + + Raises: + RuntimeError: 配置尚未加载 + AttributeError: 属性不存在 + """ + # 私有属性不代理 + if name.startswith('_'): + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) + + # 检查配置是否已加载 if self._config is None: raise RuntimeError("配置尚未加载,请先调用 load() 方法") - return self._config.debug + + # 尝试从 _config 获取属性 + try: + return getattr(self._config, name) + except AttributeError as e: + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) from e async def start_watch(self) -> None: """启动配置文件监控(需要在事件循环中调用)""" From 3ec14993242ca1614fb45aaf4244ad4274461cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 14:03:14 +0800 Subject: [PATCH 100/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=BD=AC?= =?UTF-8?q?=E5=8F=91=E6=B6=88=E6=81=AF=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9B=BE=E7=89=87=E6=95=B0=E9=87=8F=E9=98=88=E5=80=BC?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.py | 2 ++ src/config/official_configs.py | 8 ++++++++ src/recv_handler/message_handler.py | 13 ++++++++----- template/template_config.toml | 3 +++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/config/config.py b/src/config/config.py index bdf7837..1bf531d 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -14,6 +14,7 @@ from src.config.official_configs import ( ChatConfig, DebugConfig, + ForwardConfig, MaiBotServerConfig, NapcatServerConfig, NicknameConfig, @@ -117,6 +118,7 @@ class Config(ConfigBase): maibot_server: MaiBotServerConfig chat: ChatConfig voice: VoiceConfig + forward: ForwardConfig debug: DebugConfig diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 98b4552..d744c49 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -77,6 +77,14 @@ class VoiceConfig(ConfigBase): """是否启用TTS功能""" +@dataclass +class ForwardConfig(ConfigBase): + """转发消息相关配置""" + + image_threshold: int = 3 + """图片数量阈值:转发消息中图片数量超过此值时,使用占位符代替base64发送,避免麦麦VLM处理卡死""" + + @dataclass class DebugConfig(ConfigBase): level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO" diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index 41354cb..c54186c 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -662,14 +662,17 @@ async def handle_forward_message(self, message_list: list) -> Seg | None: forward_header = Seg(type="text", data="========== 转发消息开始 ==========\n") forward_footer = Seg(type="text", data="========== 转发消息结束 ==========") - if image_count < 5 and image_count > 0: - # 处理图片数量小于5的情况,此时解析图片为base64 - logger.trace("图片数量小于5,开始解析图片为base64") + # 图片阈值:超过此数量使用占位符避免麦麦VLM处理卡死 + image_threshold = global_config.forward.image_threshold + + if image_count < image_threshold and image_count > 0: + # 处理图片数量小于阈值的情况,此时解析图片为base64 + logger.trace(f"图片数量({image_count})小于{image_threshold},开始解析图片为base64") parsed_message = await self._recursive_parse_image_seg(handled_message, True) return Seg(type="seglist", data=[forward_header, parsed_message, forward_footer]) elif image_count > 0: - logger.trace("图片数量大于等于5,开始解析图片为占位符") - # 处理图片数量大于等于5的情况,此时解析图片为占位符 + logger.trace(f"图片数量({image_count})大于等于{image_threshold},开始解析图片为占位符") + # 处理图片数量大于等于阈值的情况,此时解析图片为占位符 parsed_message = await self._recursive_parse_image_seg(handled_message, False) return Seg(type="seglist", data=[forward_header, parsed_message, forward_footer]) else: diff --git a/template/template_config.toml b/template/template_config.toml index 63b55ae..610e1fb 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -31,5 +31,8 @@ enable_poke = true # 是否启用戳一戳功能 [voice] # 发送语音设置 use_tts = false # 是否使用tts语音(请确保你配置了tts并有对应的adapter) +[forward] # 转发消息处理设置 +image_threshold = 3 # 图片数量阈值:转发消息中图片数量超过此值时使用占位符(避免麦麦VLM处理卡死) + [debug] level = "INFO" # 日志等级(DEBUG, INFO, WARNING, ERROR, CRITICAL) From e6b4c0cf3ad49d940db7e8cb3de5142e44a32c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 14:06:07 +0800 Subject: [PATCH 101/112] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E8=87=B30.1.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- template/template_config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/template_config.toml b/template/template_config.toml index 610e1fb..8613dbb 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -1,5 +1,5 @@ [inner] -version = "0.1.2" # 版本号 +version = "0.1.3" # 版本号 # 请勿修改版本号,除非你知道自己在做什么 [nickname] # 现在没用 From 74b050032dbe224612711ca391370db9dc75a90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 14:09:11 +0800 Subject: [PATCH 102/112] =?UTF-8?q?feat:=20=E5=A4=84=E7=90=86=E9=80=9A?= =?UTF-8?q?=E7=9F=A5notice=E6=B6=88=E6=81=AF=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E6=98=AF=E5=90=A6=E5=9C=A8=E7=BE=A4=E8=81=8A?= =?UTF-8?q?=E7=99=BD=E5=90=8D=E5=8D=95=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/notice_handler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index 1c9ea5d..ccfc633 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -142,21 +142,29 @@ async def handle_notice(self, raw_message: dict) -> None: handled_message, user_info = await self.handle_group_upload_notify(raw_message, group_id, user_id) system_notice = True case NoticeType.group_increase: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None sub_type = raw_message.get("sub_type") logger.info(f"处理群成员增加: {sub_type}") handled_message, user_info = await self.handle_group_increase_notify(raw_message, group_id, user_id) system_notice = True case NoticeType.group_decrease: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None sub_type = raw_message.get("sub_type") logger.info(f"处理群成员减少: {sub_type}") handled_message, user_info = await self.handle_group_decrease_notify(raw_message, group_id, user_id) system_notice = True case NoticeType.group_admin: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None sub_type = raw_message.get("sub_type") logger.info(f"处理群管理员变动: {sub_type}") handled_message, user_info = await self.handle_group_admin_notify(raw_message, group_id, user_id) system_notice = True case NoticeType.essence: + if not await message_handler.check_allow_to_chat(user_id, group_id, True, False): + return None sub_type = raw_message.get("sub_type") logger.info(f"处理精华消息: {sub_type}") handled_message, user_info = await self.handle_essence_notify(raw_message, group_id) From 616ab2b9d6b0fc693fdaa6928fedff8fd85b091d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 14:34:30 +0800 Subject: [PATCH 103/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=A7=E5=B0=8F=E8=AE=A1=E7=AE=97=E5=92=8C=E8=AD=A6?= =?UTF-8?q?=E5=91=8A=E6=97=A5=E5=BF=97=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8F=91=E9=80=81=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/recv_handler/message_sending.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/recv_handler/message_sending.py b/src/recv_handler/message_sending.py index e40ed99..6ba7bf4 100644 --- a/src/recv_handler/message_sending.py +++ b/src/recv_handler/message_sending.py @@ -1,4 +1,5 @@ from typing import Dict +import json from src.logger import logger from maim_message import MessageBase, Router @@ -20,9 +21,18 @@ async def message_send(self, message_base: MessageBase) -> bool: message_base: MessageBase: 消息基类,包含发送目标和消息内容等信息 """ try: + # 计算消息大小用于调试 + msg_dict = message_base.to_dict() + msg_json = json.dumps(msg_dict, ensure_ascii=False) + msg_size_kb = len(msg_json.encode('utf-8')) / 1024 + logger.debug(f"发送消息大小: {msg_size_kb:.2f} KB") + if msg_size_kb > 1024: # 超过 1MB 时警告 + logger.warning(f"发送的消息较大 ({msg_size_kb:.2f} KB),可能导致传输问题") + send_status = await self.maibot_router.send_message(message_base) if not send_status: raise RuntimeError("可能是路由未正确配置或连接异常") + logger.debug("消息发送成功") return send_status except Exception as e: logger.error(f"发送消息失败: {str(e)}") From 7b6b0d9593c2bf6743df7f533871e8a6f8c53a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Sat, 3 Jan 2026 19:59:02 +0800 Subject: [PATCH 104/112] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=A7=E5=B0=8F=E9=99=90=E5=88=B6=E5=92=8C=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8F=91=E9=80=81=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 message_sending.py 中引入了 95MB 的最大消息大小限制,以防止连接中断。 - 为超出大小限制的消息添加了调试和错误日志记录。 - 增强了 meta_event_handler.py 中的心跳处理,提供了更详细的机器人状态日志。 - 重构了 notice_handler.py 中的表情符号处理,使用集中式 qq_face 映射代替硬编码值。 - 更新了 qq_emoji_list.py 以修正格式并添加新的表情符号映射。 - 改进了 main_send_handler.py 中的命令处理,以便向平台发送结构化响应。 - 扩展了 send_command_handler.py 中的命令处理,添加了用于设置组名和管理组成员的新命令。 - 增强了 send_message_handler.py 中的文件消息处理,以支持文件路径和详细的文件信息。 --- command_args.md | 432 ++++++++++++++++++++- src/__init__.py | 17 + src/recv_handler/message_handler.py | 263 ++++++++++--- src/recv_handler/message_sending.py | 29 +- src/recv_handler/meta_event_handler.py | 22 +- src/recv_handler/notice_handler.py | 181 +-------- src/recv_handler/qq_emoji_list.py | 68 +++- src/send_handler/main_send_handler.py | 76 +++- src/send_handler/send_command_handler.py | 465 ++++++++++++++++++++++- src/send_handler/send_message_handler.py | 61 ++- 10 files changed, 1338 insertions(+), 276 deletions(-) diff --git a/command_args.md b/command_args.md index 3c8947d..6bc9319 100644 --- a/command_args.md +++ b/command_args.md @@ -1,8 +1,28 @@ # Command Arguments + ```python Seg.type = "command" ``` -## 群聊禁言 + +所有命令执行后都会通过自定义消息类型 `command_response` 返回响应,格式如下: + +```python +{ + "command_name": "命令名称", + "success": True/False, # 是否执行成功 + "timestamp": 1234567890.123, # 时间戳 + "data": {...}, # 返回数据(成功时) + "error": "错误信息" # 错误信息(失败时) +} +``` + +插件需要注册 `command_response` 自定义消息处理器来接收命令响应。 + +--- + +## 操作类命令 + +### 群聊禁言 ```python Seg.data: Dict[str, Any] = { "name": "GROUP_BAN", @@ -15,7 +35,8 @@ Seg.data: Dict[str, Any] = { 其中,群聊ID将会通过Group_Info.group_id自动获取。 **当`duration`为 0 时相当于解除禁言。** -## 群聊全体禁言 + +### 群聊全体禁言 ```python Seg.data: Dict[str, Any] = { "name": "GROUP_WHOLE_BAN", @@ -27,18 +48,36 @@ Seg.data: Dict[str, Any] = { 其中,群聊ID将会通过Group_Info.group_id自动获取。 `enable`的参数需要为boolean类型,True表示开启全体禁言,False表示关闭全体禁言。 -## 群聊踢人 + +### 群聊踢人 +将指定成员从群聊中踢出,可选拉黑。 + ```python Seg.data: Dict[str, Any] = { "name": "GROUP_KICK", "args": { - "qq_id": "用户QQ号", + "group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取 + "user_id": 12345678, # 必需,用户QQ号 + "reject_add_request": False # 可选,是否群拉黑,默认 False + }, +} +``` + +### 批量踢出群成员 +批量将多个成员从群聊中踢出,可选拉黑。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GROUP_KICK_MEMBERS", + "args": { + "group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取 + "user_id": [12345678, 87654321], # 必需,用户QQ号数组 + "reject_add_request": False # 可选,是否群拉黑,默认 False }, } ``` -其中,群聊ID将会通过Group_Info.group_id自动获取。 -## 戳一戳 +### 戳一戳 ```python Seg.data: Dict[str, Any] = { "name": "SEND_POKE", @@ -48,7 +87,7 @@ Seg.data: Dict[str, Any] = { } ``` -## 撤回消息 +### 撤回消息 ```python Seg.data: Dict[str, Any] = { "name": "DELETE_MSG", @@ -57,4 +96,381 @@ Seg.data: Dict[str, Any] = { } } ``` -其中message_id是消息的实际qq_id,于新版的mmc中可以从数据库获取(如果工作正常的话) \ No newline at end of file +其中message_id是消息的实际qq_id,于新版的mmc中可以从数据库获取(如果工作正常的话) + +### 给消息贴表情 +```python +Seg.data: Dict[str, Any] = { + "name": "MESSAGE_LIKE", + "args": { + "message_id": "消息ID", + "emoji_id": "表情ID" + } +} +``` + +### 设置群名 +设置指定群的群名称。 + +```python +Seg.data: Dict[str, Any] = { + "name": "SET_GROUP_NAME", + "args": { + "group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取 + "group_name": "新群名" # 必需,新的群名称 + } +} +``` + +### 设置账号信息 +设置Bot自己的QQ账号资料。 + +```python +Seg.data: Dict[str, Any] = { + "name": "SET_QQ_PROFILE", + "args": { + "nickname": "新昵称", # 必需,昵称 + "personal_note": "个性签名", # 可选,个性签名 + "sex": "male" # 可选,性别:"male" | "female" | "unknown" + } +} +``` + +**返回数据示例:** +```python +{ + "result": 0, # 结果码,0为成功 + "errMsg": "" # 错误信息 +} +``` + +--- + +## 查询类命令 + +### 获取登录号信息 +获取Bot自身的账号信息。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_LOGIN_INFO", + "args": {} +} +``` + +**返回数据示例:** +```python +{ + "user_id": 12345678, + "nickname": "Bot昵称" +} +``` + +### 获取陌生人信息 +```python +Seg.data: Dict[str, Any] = { + "name": "GET_STRANGER_INFO", + "args": { + "user_id": "用户QQ号" + } +} +``` + +**返回数据示例:** +```python +{ + "user_id": 12345678, + "nickname": "用户昵称", + "sex": "male/female/unknown", + "age": 0 +} +``` + +### 获取好友列表 +获取Bot的好友列表。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_FRIEND_LIST", + "args": { + "no_cache": False # 可选,是否不使用缓存,默认 False + } +} +``` + +**返回数据示例:** +```python +[ + { + "user_id": 12345678, + "nickname": "好友昵称", + "remark": "备注名", + "sex": "male", # "male" | "female" | "unknown" + "age": 18, + "qid": "QID字符串", + "level": 64, + "login_days": 365, + "birthday_year": 2000, + "birthday_month": 1, + "birthday_day": 1, + "phone_num": "电话号码", + "email": "邮箱", + "category_id": 0, # 分组ID + "categoryName": "我的好友", # 分组名称 + "categoryId": 0 + }, + ... +] +``` + +### 获取群信息 +获取指定群的详细信息。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_GROUP_INFO", + "args": { + "group_id": 123456789 # 可选,如果在群聊上下文中可从 group_info 自动获取 + } +} +``` + +**返回数据示例:** +```python +{ + "group_id": "123456789", # 群号(字符串) + "group_name": "群名称", + "group_remark": "群备注", + "group_all_shut": 0, # 群全员禁言状态(0=未禁言) + "member_count": 100, # 当前成员数量 + "max_member_count": 500 # 最大成员数量 +} +``` + +### 获取群详细信息 +获取指定群的详细信息(与 GET_GROUP_INFO 类似,可能提供更实时的数据)。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_GROUP_DETAIL_INFO", + "args": { + "group_id": 123456789 # 可选,如果在群聊上下文中可从 group_info 自动获取 + } +} +``` + +**返回数据示例:** +```python +{ + "group_id": 123456789, # 群号(数字) + "group_name": "群名称", + "group_remark": "群备注", + "group_all_shut": 0, # 群全员禁言状态(0=未禁言) + "member_count": 100, # 当前成员数量 + "max_member_count": 500 # 最大成员数量 +} +``` + +### 获取群列表 +获取Bot加入的所有群列表。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_GROUP_LIST", + "args": { + "no_cache": False # 可选,是否不使用缓存,默认 False + } +} +``` + +**返回数据示例:** +```python +[ + { + "group_id": "123456789", # 群号(字符串) + "group_name": "群名称", + "group_remark": "群备注", + "group_all_shut": 0, # 群全员禁言状态 + "member_count": 100, # 当前成员数量 + "max_member_count": 500 # 最大成员数量 + }, + ... +] +``` + +### 获取群@全体成员剩余次数 +查询指定群的@全体成员剩余使用次数。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_GROUP_AT_ALL_REMAIN", + "args": { + "group_id": 123456789 # 可选,如果在群聊上下文中可从 group_info 自动获取 + } +} +``` + +**返回数据示例:** +```python +{ + "can_at_all": True, # 是否可以@全体成员 + "remain_at_all_count_for_group": 10, # 群剩余@全体成员次数 + "remain_at_all_count_for_uin": 5 # Bot剩余@全体成员次数 +} +``` + +### 获取群成员信息 +获取指定群成员的详细信息。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_GROUP_MEMBER_INFO", + "args": { + "group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取 + "user_id": 12345678, # 必需,用户QQ号 + "no_cache": False # 可选,是否不使用缓存,默认 False + } +} +``` + +**返回数据示例:** +```python +{ + "group_id": 123456789, + "user_id": 12345678, + "nickname": "昵称", + "card": "群名片", + "sex": "male", # "male" | "female" | "unknown" + "age": 18, + "join_time": 1234567890, # 加群时间戳 + "last_sent_time": 1234567890, # 最后发言时间戳 + "level": 1, # 群等级 + "qq_level": 64, # QQ等级 + "role": "member", # "owner" | "admin" | "member" + "title": "专属头衔", + "area": "地区", + "unfriendly": False, # 是否不友好 + "title_expire_time": 1234567890, # 头衔过期时间 + "card_changeable": True, # 名片是否可修改 + "shut_up_timestamp": 0, # 禁言时间戳 + "is_robot": False, # 是否机器人 + "qage": "10年" # Q龄 +} +``` + +### 获取群成员列表 +获取指定群的所有成员列表。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_GROUP_MEMBER_LIST", + "args": { + "group_id": 123456789, # 可选,如果在群聊上下文中可从 group_info 自动获取 + "no_cache": False # 可选,是否不使用缓存,默认 False + } +} +``` + +**返回数据示例:** +```python +[ + { + "group_id": 123456789, + "user_id": 12345678, + "nickname": "昵称", + "card": "群名片", + "sex": "male", # "male" | "female" | "unknown" + "age": 18, + "join_time": 1234567890, + "last_sent_time": 1234567890, + "level": 1, + "qq_level": 64, + "role": "member", # "owner" | "admin" | "member" + "title": "专属头衔", + "area": "地区", + "unfriendly": False, + "title_expire_time": 1234567890, + "card_changeable": True, + "shut_up_timestamp": 0, + "is_robot": False, + "qage": "10年" + }, + ... +] +``` + +### 获取消息详情 +获取指定消息的完整详情信息。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_MSG", + "args": { + "message_id": 123456 # 必需,消息ID + } +} +``` + +**返回数据示例:** +```python +{ + "self_id": 12345678, # Bot自身ID + "user_id": 87654321, # 发送者ID + "time": 1234567890, # 时间戳 + "message_id": 123456, # 消息ID + "message_seq": 123456, # 消息序列号 + "real_id": 123456, # 真实消息ID + "real_seq": "123456", # 真实序列号(字符串) + "message_type": "group", # "private" | "group" + "sub_type": "normal", # 子类型 + "message_format": "array", # 消息格式 + "post_type": "message", # 事件类型 + "group_id": 123456789, # 群号(群消息时存在) + "sender": { + "user_id": 87654321, + "nickname": "昵称", + "sex": "male", # "male" | "female" | "unknown" + "age": 18, + "card": "群名片", # 群消息时存在 + "level": "1", # 群等级(字符串) + "role": "member" # "owner" | "admin" | "member" + }, + "message": [...], # 消息段数组 + "raw_message": "消息文本内容", # 原始消息文本 + "font": 0 # 字体 +} +``` + +### 获取合并转发消息 +获取合并转发消息的所有子消息内容。 + +```python +Seg.data: Dict[str, Any] = { + "name": "GET_FORWARD_MSG", + "args": { + "message_id": "7123456789012345678" # 必需,合并转发消息ID(字符串) + } +} +``` + +**返回数据示例:** +```python +{ + "messages": [ + { + "sender": { + "user_id": 87654321, + "nickname": "昵称", + "sex": "male", + "age": 18, + "card": "群名片", + "level": "1", + "role": "member" + }, + "time": 1234567890, + "message": [...] # 消息段数组 + }, + ... + ] +} +``` \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py index 646d0a9..4deadb0 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -7,13 +7,30 @@ class CommandType(Enum): """命令类型""" + # 操作类命令 GROUP_BAN = "set_group_ban" # 禁言用户 GROUP_WHOLE_BAN = "set_group_whole_ban" # 群全体禁言 GROUP_KICK = "set_group_kick" # 踢出群聊 + GROUP_KICK_MEMBERS = "set_group_kick_members" # 批量踢出群成员 + SET_GROUP_NAME = "set_group_name" # 设置群名 SEND_POKE = "send_poke" # 戳一戳 DELETE_MSG = "delete_msg" # 撤回消息 AI_VOICE_SEND = "send_group_ai_record" # 发送群AI语音 MESSAGE_LIKE = "message_like" # 给消息贴表情 + SET_QQ_PROFILE = "set_qq_profile" # 设置账号信息 + + # 查询类命令 + GET_LOGIN_INFO = "get_login_info" # 获取登录号信息 + GET_STRANGER_INFO = "get_stranger_info" # 获取陌生人信息 + GET_FRIEND_LIST = "get_friend_list" # 获取好友列表 + GET_GROUP_INFO = "get_group_info" # 获取群信息 + GET_GROUP_DETAIL_INFO = "get_group_detail_info" # 获取群详细信息 + GET_GROUP_LIST = "get_group_list" # 获取群列表 + GET_GROUP_AT_ALL_REMAIN = "get_group_at_all_remain" # 获取群@全体成员剩余次数 + GET_GROUP_MEMBER_INFO = "get_group_member_info" # 获取群成员信息 + GET_GROUP_MEMBER_LIST = "get_group_member_list" # 获取群成员列表 + GET_MSG = "get_msg" # 获取消息 + GET_FORWARD_MSG = "get_forward_msg" # 获取合并转发消息 def __str__(self) -> str: return self.value diff --git a/src/recv_handler/message_handler.py b/src/recv_handler/message_handler.py index c54186c..54a5b4b 100644 --- a/src/recv_handler/message_handler.py +++ b/src/recv_handler/message_handler.py @@ -307,9 +307,9 @@ async def handle_real_message( else: logger.warning("video处理失败") case RealMessageType.json: - ret_seg = await self.handle_json_message(sub_message) - if ret_seg: - seg_message.append(ret_seg) + ret_segs = await self.handle_json_message(sub_message) + if ret_segs: + seg_message.extend(ret_segs) else: logger.warning("json处理失败") case RealMessageType.file: @@ -486,13 +486,13 @@ async def handle_video_message(self, raw_message: dict) -> Seg | None: "url": url }) - async def handle_json_message(self, raw_message: dict) -> Seg | None: + async def handle_json_message(self, raw_message: dict) -> List[Seg] | None: """ 处理JSON卡片消息(小程序、分享、群公告等) Parameters: raw_message: dict: 原始消息 Returns: - seg_data: Seg: 处理后的消息段 + seg_data: List[Seg]: 处理后的消息段列表(可能包含文本和图片) """ message_data: dict = raw_message.get("data") json_data: str = message_data.get("data") @@ -505,90 +505,241 @@ async def handle_json_message(self, raw_message: dict) -> Seg | None: # 尝试解析JSON获取详细信息 parsed_json = json.loads(json_data) app = parsed_json.get("app", "") + meta = parsed_json.get("meta", {}) - # 检查是否为群公告 + # 群公告(由于图片URL是加密的,因此无法读取) if app == "com.tencent.mannounce": - meta = parsed_json.get("meta", {}) mannounce = meta.get("mannounce", {}) - title_encoded = mannounce.get("title", "") - text_encoded = mannounce.get("text", "") - - # 解码Base64编码的标题和内容 - title = "" - text = "" - try: - if title_encoded: - title = base64.b64decode(title_encoded).decode("utf-8") - if text_encoded: - text = base64.b64decode(text_encoded).decode("utf-8") - except Exception as e: - logger.warning(f"群公告Base64解码失败: {e}") - # 降级使用原始值 - title = title_encoded - text = text_encoded - - # 构建群公告文本 - announce_text = "[群公告]" - if title: - announce_text += f"\n标题: {title}" - if text: - announce_text += f"\n内容: {text}" - - return Seg(type="text", data=announce_text) + title = mannounce.get("title", "") + text = mannounce.get("text", "") + encode_flag = mannounce.get("encode", 0) + if encode_flag == 1: + try: + if title: + title = base64.b64decode(title).decode("utf-8", errors="ignore") + if text: + text = base64.b64decode(text).decode("utf-8", errors="ignore") + except Exception as e: + logger.warning(f"群公告Base64解码失败: {e}") + if title and text: + content = f"[{title}]:{text}" + elif title: + content = f"[{title}]" + elif text: + content = f"{text}" + else: + content = "[群公告]" + return [Seg(type="text", data=content)] - # 检查是否为音乐卡片 + # 音乐卡片 if app in ("com.tencent.music.lua", "com.tencent.structmsg"): - meta = parsed_json.get("meta", {}) music = meta.get("music", {}) - - # 尝试从music字段提取信息 if music: title = music.get("title", "") singer = music.get("desc", "") or music.get("singer", "") jump_url = music.get("jumpUrl", "") or music.get("jump_url", "") music_url = music.get("musicUrl", "") or music.get("music_url", "") - tag = music.get("tag", "") # 音乐来源标签,如"网易云音乐" - preview = music.get("preview", "") # 封面图URL + tag = music.get("tag", "") + preview = music.get("preview", "") - # 返回结构化的音乐卡片数据 - return Seg(type="music_card", data={ + return [Seg(type="music_card", data={ "title": title, "singer": singer, "jump_url": jump_url, "music_url": music_url, "tag": tag, "preview": preview - }) + })] - # 检查是否为小程序分享(如B站视频分享) + # QQ小程序分享(含预览图) if app == "com.tencent.miniapp_01": - meta = parsed_json.get("meta", {}) detail = meta.get("detail_1", {}) - if detail: - title = detail.get("title", "") # 小程序名称,如"哔哩哔哩" - desc = detail.get("desc", "") # 分享内容描述 - url = detail.get("url", "") # 小程序链接 - qqdocurl = detail.get("qqdocurl", "") # 原始链接(如B站链接) - preview = detail.get("preview", "") # 预览图 - icon = detail.get("icon", "") # 小程序图标 + title = detail.get("title", "") + desc = detail.get("desc", "") + url = detail.get("url", "") + qqdocurl = detail.get("qqdocurl", "") + preview_url = detail.get("preview", "") + icon = detail.get("icon", "") - # 返回结构化的小程序卡片数据 - return Seg(type="miniapp_card", data={ + seg_list = [Seg(type="miniapp_card", data={ "title": title, "desc": desc, "url": url, "source_url": qqdocurl, - "preview": preview, + "preview": preview_url, "icon": icon - }) + })] + + # 下载预览图 + if preview_url: + try: + image_base64 = await get_image_base64(preview_url) + seg_list.append(Seg(type="image", data=image_base64)) + except Exception as e: + logger.error(f"QQ小程序预览图下载失败: {e}") + + return seg_list + + # 礼物消息 + if app == "com.tencent.giftmall.giftark": + giftark = meta.get("giftark", {}) + if giftark: + gift_name = giftark.get("title", "礼物") + desc = giftark.get("desc", "") + gift_text = f"[赠送礼物: {gift_name}]" + if desc: + gift_text += f"\n{desc}" + return [Seg(type="text", data=gift_text)] + + # 推荐联系人 + if app == "com.tencent.contact.lua": + contact_info = meta.get("contact", {}) + name = contact_info.get("nickname", "未知联系人") + tag = contact_info.get("tag", "推荐联系人") + return [Seg(type="text", data=f"[{tag}] {name}")] + + # 推荐群聊 + if app == "com.tencent.troopsharecard": + contact_info = meta.get("contact", {}) + name = contact_info.get("nickname", "未知群聊") + tag = contact_info.get("tag", "推荐群聊") + return [Seg(type="text", data=f"[{tag}] {name}")] + + # 图文分享(如 哔哩哔哩HD、网页、群精华等) + if app == "com.tencent.tuwen.lua": + news = meta.get("news", {}) + title = news.get("title", "未知标题") + desc = (news.get("desc", "") or "").replace("[图片]", "").strip() + tag = news.get("tag", "图文分享") + preview_url = news.get("preview", "") + if tag and title and tag in title: + title = title.replace(tag, "", 1).strip(":: -— ") + text_content = f"[{tag}] {title}:{desc}" + seg_list = [Seg(type="text", data=text_content)] + + # 下载预览图 + if preview_url: + try: + image_base64 = await get_image_base64(preview_url) + seg_list.append(Seg(type="image", data=image_base64)) + except Exception as e: + logger.error(f"图文预览图下载失败: {e}") + + return seg_list + + # 群相册(含预览图) + if app == "com.tencent.feed.lua": + feed = meta.get("feed", {}) + title = feed.get("title", "群相册") + tag = feed.get("tagName", "群相册") + desc = feed.get("forwardMessage", "") + cover_url = feed.get("cover", "") + if tag and title and tag in title: + title = title.replace(tag, "", 1).strip(":: -— ") + text_content = f"[{tag}] {title}:{desc}" + seg_list = [Seg(type="text", data=text_content)] + + # 下载封面图 + if cover_url: + try: + image_base64 = await get_image_base64(cover_url) + seg_list.append(Seg(type="image", data=image_base64)) + except Exception as e: + logger.error(f"群相册封面下载失败: {e}") + + return seg_list + + # QQ收藏分享(含预览图) + if app == "com.tencent.template.qqfavorite.share": + news = meta.get("news", {}) + desc = news.get("desc", "").replace("[图片]", "").strip() + tag = news.get("tag", "QQ收藏") + preview_url = news.get("preview", "") + seg_list = [Seg(type="text", data=f"[{tag}] {desc}")] + + # 下载预览图 + if preview_url: + try: + image_base64 = await get_image_base64(preview_url) + seg_list.append(Seg(type="image", data=image_base64)) + except Exception as e: + logger.error(f"QQ收藏预览图下载失败: {e}") + + return seg_list + + # QQ空间分享(含预览图) + if app == "com.tencent.miniapp.lua": + miniapp = meta.get("miniapp", {}) + title = miniapp.get("title", "未知标题") + tag = miniapp.get("tag", "QQ空间") + preview_url = miniapp.get("preview", "") + seg_list = [Seg(type="text", data=f"[{tag}] {title}")] + + # 下载预览图 + if preview_url: + try: + image_base64 = await get_image_base64(preview_url) + seg_list.append(Seg(type="image", data=image_base64)) + except Exception as e: + logger.error(f"QQ空间预览图下载失败: {e}") + + return seg_list + + # QQ频道分享(含预览图) + if app == "com.tencent.forum": + detail = meta.get("detail") if isinstance(meta, dict) else None + if detail: + feed = detail.get("feed", {}) + poster = detail.get("poster", {}) + channel_info = detail.get("channel_info", {}) + guild_name = channel_info.get("guild_name", "") + nick = poster.get("nick", "QQ用户") + title = feed.get("title", {}).get("contents", [{}])[0].get("text_content", {}).get("text", "帖子") + face_content = "" + for item in feed.get("contents", {}).get("contents", []): + emoji = item.get("emoji_content") + if emoji: + eid = emoji.get("id") + if eid in qq_face: + face_content += qq_face.get(eid, "") + + seg_list = [Seg(type="text", data=f"[频道帖子] [{guild_name}]{nick}:{title}{face_content}")] + + # 下载帖子中的图片 + pic_urls = [img.get("pic_url") for img in feed.get("images", []) if img.get("pic_url")] + for pic_url in pic_urls: + try: + image_base64 = await get_image_base64(pic_url) + seg_list.append(Seg(type="image", data=image_base64)) + except Exception as e: + logger.error(f"QQ频道图片下载失败: {e}") + + return seg_list + + # QQ地图位置分享 + if app == "com.tencent.map": + location = meta.get("Location.Search", {}) + name = location.get("name", "未知地点") + address = location.get("address", "") + return [Seg(type="text", data=f"[位置] {address} · {name}")] + + # QQ一起听歌 + if app == "com.tencent.together": + invite = (meta or {}).get("invite", {}) + title = invite.get("title") or "一起听歌" + summary = invite.get("summary") or "" + return [Seg(type="text", data=f"[{title}] {summary}")] # 其他卡片消息使用prompt字段 prompt = parsed_json.get("prompt", "[卡片消息]") - return Seg(type="text", data=prompt) + return [Seg(type="text", data=prompt)] except json.JSONDecodeError: logger.warning("JSON消息解析失败") - return Seg(type="text", data="[卡片消息]") + return [Seg(type="text", data="[卡片消息]")] + except Exception as e: + logger.error(f"JSON消息处理异常: {e}") + return [Seg(type="text", data="[卡片消息]")] async def handle_file_message(self, raw_message: dict) -> Seg | None: """ diff --git a/src/recv_handler/message_sending.py b/src/recv_handler/message_sending.py index 6ba7bf4..2d92f02 100644 --- a/src/recv_handler/message_sending.py +++ b/src/recv_handler/message_sending.py @@ -4,6 +4,13 @@ from maim_message import MessageBase, Router +# 消息大小限制 (字节) +# WebSocket 服务端限制为 100MB,这里设置 95MB 留一点余量 +MAX_MESSAGE_SIZE_BYTES = 95 * 1024 * 1024 # 95MB +MAX_MESSAGE_SIZE_KB = MAX_MESSAGE_SIZE_BYTES / 1024 +MAX_MESSAGE_SIZE_MB = MAX_MESSAGE_SIZE_KB / 1024 + + class MessageSending: """ 负责把消息发送到麦麦 @@ -24,10 +31,27 @@ async def message_send(self, message_base: MessageBase) -> bool: # 计算消息大小用于调试 msg_dict = message_base.to_dict() msg_json = json.dumps(msg_dict, ensure_ascii=False) - msg_size_kb = len(msg_json.encode('utf-8')) / 1024 + msg_size_bytes = len(msg_json.encode('utf-8')) + msg_size_kb = msg_size_bytes / 1024 + msg_size_mb = msg_size_kb / 1024 + logger.debug(f"发送消息大小: {msg_size_kb:.2f} KB") + + # 检查消息是否超过大小限制 + if msg_size_bytes > MAX_MESSAGE_SIZE_BYTES: + logger.error( + f"消息大小 ({msg_size_mb:.2f} MB) 超过限制 ({MAX_MESSAGE_SIZE_MB:.0f} MB)," + f"消息已被丢弃以避免连接断开" + ) + logger.warning( + f"被丢弃的消息来源: platform={message_base.message_info.platform}, " + f"group_id={message_base.message_info.group_info.group_id if message_base.message_info.group_info else 'N/A'}, " + f"user_id={message_base.message_info.user_info.user_id if message_base.message_info.user_info else 'N/A'}" + ) + return False + if msg_size_kb > 1024: # 超过 1MB 时警告 - logger.warning(f"发送的消息较大 ({msg_size_kb:.2f} KB),可能导致传输问题") + logger.warning(f"发送的消息较大 ({msg_size_mb:.2f} MB),可能导致传输延迟") send_status = await self.maibot_router.send_message(message_base) if not send_status: @@ -37,6 +61,7 @@ async def message_send(self, message_base: MessageBase) -> bool: except Exception as e: logger.error(f"发送消息失败: {str(e)}") logger.error("请检查与MaiBot之间的连接") + return False async def send_custom_message(self, custom_message: Dict, platform: str, message_type: str) -> bool: """ diff --git a/src/recv_handler/meta_event_handler.py b/src/recv_handler/meta_event_handler.py index 289e551..40f5a1a 100644 --- a/src/recv_handler/meta_event_handler.py +++ b/src/recv_handler/meta_event_handler.py @@ -25,14 +25,26 @@ async def handle_meta_event(self, message: dict) -> None: logger.success(f"Bot {self_id} 连接成功") asyncio.create_task(self.check_heartbeat(self_id)) elif event_type == MetaEventType.heartbeat: - if message["status"].get("online") and message["status"].get("good"): + self_id = message.get("self_id") + status = message.get("status", {}) + is_online = status.get("online", False) + is_good = status.get("good", False) + + if is_online and is_good: + # 正常心跳 if not self._interval_checking: - asyncio.create_task(self.check_heartbeat()) + asyncio.create_task(self.check_heartbeat(self_id)) self.last_heart_beat = time.time() - self.interval = message.get("interval") / 1000 + self.interval = message.get("interval", 30000) / 1000 else: - self_id = message.get("self_id") - logger.warning(f"Bot {self_id} Napcat 端异常!") + # Bot 离线或状态异常 + if not is_online: + logger.error(f"🔴 Bot {self_id} 已下线 (online=false)") + logger.warning("Bot 可能被踢下线、网络断开或主动退出登录") + elif not is_good: + logger.warning(f"⚠️ Bot {self_id} 状态异常 (good=false)") + else: + logger.warning(f"Bot {self_id} Napcat 端异常!") async def check_heartbeat(self, id: int) -> None: self._interval_checking = True diff --git a/src/recv_handler/notice_handler.py b/src/recv_handler/notice_handler.py index ccfc633..add8913 100644 --- a/src/recv_handler/notice_handler.py +++ b/src/recv_handler/notice_handler.py @@ -10,6 +10,7 @@ from . import NoticeType, ACCEPT_FORMAT from .message_sending import message_send_instance from .message_handler import message_handler +from .qq_emoji_list import qq_face from maim_message import FormatInfo, UserInfo, GroupInfo, Seg, BaseMessageInfo, MessageBase from src.utils import ( @@ -402,185 +403,13 @@ async def handle_emoji_like_notify( likes = raw_message.get("likes", []) message_id = raw_message.get("message_id") - # 构建表情文本 + # 构建表情文本,直接使用 qq_face 映射 emoji_texts = [] - # QQ 官方表情映射表 (EmojiType=1 为 QQ 系统表情,EmojiType=2 为 Emoji Unicode) - emoji_map = { - # QQ 系统表情 (Type 1) - "4": "得意", - "5": "流泪", - "8": "睡", - "9": "大哭", - "10": "尴尬", - "12": "调皮", - "14": "微笑", - "16": "酷", - "21": "可爱", - "23": "傲慢", - "24": "饥饿", - "25": "困", - "26": "惊恐", - "27": "流汗", - "28": "憨笑", - "29": "悠闲", - "30": "奋斗", - "32": "疑问", - "33": "嘘", - "34": "晕", - "38": "敲打", - "39": "再见", - "41": "发抖", - "42": "爱情", - "43": "跳跳", - "49": "拥抱", - "53": "蛋糕", - "60": "咖啡", - "63": "玫瑰", - "66": "爱心", - "74": "太阳", - "75": "月亮", - "76": "赞", - "78": "握手", - "79": "胜利", - "85": "飞吻", - "89": "西瓜", - "96": "冷汗", - "97": "擦汗", - "98": "抠鼻", - "99": "鼓掌", - "100": "糗大了", - "101": "坏笑", - "102": "左哼哼", - "103": "右哼哼", - "104": "哈欠", - "106": "委屈", - "109": "左亲亲", - "111": "可怜", - "116": "示爱", - "118": "抱拳", - "120": "拳头", - "122": "爱你", - "123": "NO", - "124": "OK", - "125": "转圈", - "129": "挥手", - "144": "喝彩", - "147": "棒棒糖", - "171": "茶", - "173": "泪奔", - "174": "无奈", - "175": "卖萌", - "176": "小纠结", - "179": "doge", - "180": "惊喜", - "181": "骚扰", - "182": "笑哭", - "183": "我最美", - "201": "点赞", - "203": "托脸", - "212": "托腮", - "214": "啵啵", - "219": "蹭一蹭", - "222": "抱抱", - "227": "拍手", - "232": "佛系", - "240": "喷脸", - "243": "甩头", - "246": "加油抱抱", - "262": "脑阔疼", - "264": "捂脸", - "265": "辣眼睛", - "266": "哦哟", - "267": "头秃", - "268": "问号脸", - "269": "暗中观察", - "270": "emm", - "271": "吃瓜", - "272": "呵呵哒", - "273": "我酸了", - "277": "汪汪", - "278": "汗", - "281": "无眼笑", - "282": "敬礼", - "284": "面无表情", - "285": "摸鱼", - "287": "哦", - "289": "睁眼", - "290": "敲开心", - "293": "摸锦鲤", - "294": "期待", - "297": "拜谢", - "298": "元宝", - "299": "牛啊", - "305": "右亲亲", - "306": "牛气冲天", - "307": "喵喵", - "314": "仔细分析", - "315": "加油", - "318": "崇拜", - "319": "比心", - "320": "庆祝", - "322": "拒绝", - "324": "吃糖", - "326": "生气", - # Unicode Emoji (Type 2) - "9728": "☀", - "9749": "☕", - "9786": "☺", - "10024": "✨", - "10060": "❌", - "10068": "❔", - "127801": "🌹", - "127817": "🍉", - "127822": "🍎", - "127827": "🍓", - "127836": "🍜", - "127838": "🍞", - "127847": "🍧", - "127866": "🍺", - "127867": "🍻", - "127881": "🎉", - "128027": "🐛", - "128046": "🐮", - "128051": "🐳", - "128053": "🐵", - "128074": "👊", - "128076": "👌", - "128077": "👍", - "128079": "👏", - "128089": "👙", - "128102": "👦", - "128104": "👨", - "128147": "💓", - "128157": "💝", - "128164": "💤", - "128166": "💦", - "128168": "💨", - "128170": "💪", - "128235": "📫", - "128293": "🔥", - "128513": "😁", - "128514": "😂", - "128516": "😄", - "128522": "😊", - "128524": "😌", - "128527": "😏", - "128530": "😒", - "128531": "😓", - "128532": "😔", - "128536": "😘", - "128538": "😚", - "128540": "😜", - "128541": "😝", - "128557": "😭", - "128560": "😰", - "128563": "😳", - } - for like in likes: - emoji_id = like.get("emoji_id", "") + emoji_id = str(like.get("emoji_id", "")) count = like.get("count", 1) - emoji = emoji_map.get(emoji_id, f"表情{emoji_id}") + # 使用 qq_face 字典获取表情描述 + emoji = qq_face.get(emoji_id, f"[表情:未知{emoji_id}]") if count > 1: emoji_texts.append(f"{emoji}x{count}") else: diff --git a/src/recv_handler/qq_emoji_list.py b/src/recv_handler/qq_emoji_list.py index 51c3232..3b3c8bb 100644 --- a/src/recv_handler/qq_emoji_list.py +++ b/src/recv_handler/qq_emoji_list.py @@ -31,7 +31,7 @@ "30": "[表情:奋斗]", "31": "[表情:咒骂]", "32": "[表情:疑问]", - "33": "[表情: 嘘]", + "33": "[表情:嘘]", "34": "[表情:晕]", "35": "[表情:折磨]", "36": "[表情:衰]", @@ -117,7 +117,7 @@ "268": "[表情:问号脸]", "269": "[表情:暗中观察]", "270": "[表情:emm]", - "271": "[表情:吃 瓜]", + "271": "[表情:吃瓜]", "272": "[表情:呵呵哒]", "273": "[表情:我酸了]", "277": "[表情:汪汪]", @@ -146,7 +146,7 @@ "314": "[表情:仔细分析]", "317": "[表情:菜汪]", "318": "[表情:崇拜]", - "319": "[表情: 比心]", + "319": "[表情:比心]", "320": "[表情:庆祝]", "323": "[表情:嫌弃]", "324": "[表情:吃糖]", @@ -175,13 +175,65 @@ "355": "[表情:耶]", "356": "[表情:666]", "357": "[表情:裂开]", - "392": "[表情:龙年 快乐]", + "392": "[表情:龙年快乐]", "393": "[表情:新年中龙]", "394": "[表情:新年大龙]", "395": "[表情:略略略]", + "128522": "[表情:嘿嘿]", + "128524": "[表情:羞涩]", + "128538": "[表情:亲亲]", + "128531": "[表情:汗]", + "128560": "[表情:紧张]", + "128541": "[表情:吐舌]", + "128513": "[表情:呲牙]", + "128540": "[表情:淘气]", + "9786": "[表情:可爱]", + "128532": "[表情:失落]", + "128516": "[表情:高兴]", + "128527": "[表情:哼哼]", + "128530": "[表情:不屑]", + "128563": "[表情:瞪眼]", + "128536": "[表情:飞吻]", + "128557": "[表情:大哭]", + "128514": "[表情:激动]", + "128170": "[表情:肌肉]", + "128074": "[表情:拳头]", + "128077": "[表情:厉害]", + "128079": "[表情:鼓掌]", + "128076": "[表情:好的]", + "127836": "[表情:拉面]", + "127847": "[表情:刨冰]", + "127838": "[表情:面包]", + "127866": "[表情:啤酒]", + "127867": "[表情:干杯]", + "9749": "[表情:咖啡]", + "127822": "[表情:苹果]", + "127827": "[表情:草莓]", + "127817": "[表情:西瓜]", + "127801": "[表情:玫瑰]", + "127881": "[表情:庆祝]", + "128157": "[表情:礼物]", + "10024": "[表情:闪光]", + "128168": "[表情:吹气]", + "128166": "[表情:水]", + "128293": "[表情:火]", + "128164": "[表情:睡觉]", + "128235": "[表情:邮箱]", + "128103": "[表情:女孩]", + "128102": "[表情:男孩]", + "128053": "[表情:猴]", + "128046": "[表情:牛]", + "128027": "[表情:虫]", + "128051": "[表情:鲸鱼]", + "9728": "[表情:晴天]", + "10068": "[表情:问号]", + "128147": "[表情:爱心]", + "10060": "[表情:错误]", + "128089": "[表情:内衣]", + "128104": "[表情:爸爸]", "😊": "[表情:嘿嘿]", "😌": "[表情:羞涩]", - "😚": "[ 表情:亲亲]", + "😚": "[表情:亲亲]", "😓": "[表情:汗]", "😰": "[表情:紧张]", "😝": "[表情:吐舌]", @@ -200,7 +252,7 @@ "😂": "[表情:激动]", "💪": "[表情:肌肉]", "👊": "[表情:拳头]", - "👍": "[表情 :厉害]", + "👍": "[表情:厉害]", "👏": "[表情:鼓掌]", "👎": "[表情:鄙视]", "🙏": "[表情:合十]", @@ -245,6 +297,6 @@ "☀": "[表情:晴天]", "❔": "[表情:问号]", "🔫": "[表情:手枪]", - "💓": "[表情:爱 心]", + "💓": "[表情:爱心]", "🏪": "[表情:便利店]", -} +} \ No newline at end of file diff --git a/src/send_handler/main_send_handler.py b/src/send_handler/main_send_handler.py index 8cce8a9..cc2c945 100644 --- a/src/send_handler/main_send_handler.py +++ b/src/send_handler/main_send_handler.py @@ -1,4 +1,5 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional +import time from maim_message import ( UserInfo, GroupInfo, @@ -10,6 +11,7 @@ from .send_command_handler import SendCommandHandleClass from .send_message_handler import SendMessageHandleClass from .nc_sending import nc_message_sender +from src.recv_handler.message_sending import message_send_instance class SendHandler: @@ -34,21 +36,89 @@ async def send_command(self, raw_message_base: MessageBase) -> None: message_segment: Seg = raw_message_base.message_segment group_info: GroupInfo = message_info.group_info seg_data: Dict[str, Any] = message_segment.data + command_name = seg_data.get('name', 'UNKNOWN') + try: command, args_dict = SendCommandHandleClass.handle_command(seg_data, group_info) except Exception as e: logger.error(f"处理命令时出错: {str(e)}") + # 发送错误响应给麦麦 + await self._send_command_response( + platform=message_info.platform, + command_name=command_name, + success=False, + error=str(e) + ) return if not command or not args_dict: logger.error("命令或参数缺失") + await self._send_command_response( + platform=message_info.platform, + command_name=command_name, + success=False, + error="命令或参数缺失" + ) return None response = await nc_message_sender.send_message_to_napcat(command, args_dict) + + # 根据响应状态发送结果给麦麦 if response.get("status") == "ok": - logger.info(f"命令 {seg_data.get('name')} 执行成功") + logger.info(f"命令 {command_name} 执行成功") + await self._send_command_response( + platform=message_info.platform, + command_name=command_name, + success=True, + data=response.get("data") + ) else: - logger.warning(f"命令 {seg_data.get('name')} 执行失败,napcat返回:{str(response)}") + logger.warning(f"命令 {command_name} 执行失败,napcat返回:{str(response)}") + await self._send_command_response( + platform=message_info.platform, + command_name=command_name, + success=False, + error=str(response), + data=response.get("data") # 有些错误响应也可能包含部分数据 + ) + + async def _send_command_response( + self, + platform: str, + command_name: str, + success: bool, + data: Optional[Dict] = None, + error: Optional[str] = None + ) -> None: + """发送命令响应回麦麦 + + Args: + platform: 平台标识 + command_name: 命令名称 + success: 是否执行成功 + data: 返回数据(成功时) + error: 错误信息(失败时) + """ + response_data = { + "command_name": command_name, + "success": success, + "timestamp": time.time() + } + + if data is not None: + response_data["data"] = data + if error: + response_data["error"] = error + + try: + await message_send_instance.send_custom_message( + custom_message=response_data, + platform=platform, + message_type="command_response" + ) + logger.debug(f"已发送命令响应: {command_name}, success={success}") + except Exception as e: + logger.error(f"发送命令响应失败: {e}") async def send_normal_message(self, raw_message_base: MessageBase) -> None: """ diff --git a/src/send_handler/send_command_handler.py b/src/send_handler/send_command_handler.py index d7eeec1..e8a37ba 100644 --- a/src/send_handler/send_command_handler.py +++ b/src/send_handler/send_command_handler.py @@ -123,29 +123,108 @@ def handle_whole_ban_command(args: Dict[str, Any], group_info: Optional[GroupInf ) @staticmethod - @register_command(CommandType.GROUP_KICK, require_group=True) + @register_command(CommandType.GROUP_KICK, require_group=False) def handle_kick_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理群成员踢出命令 Args: - args: 参数字典 {"qq_id": int} - group_info: 群聊信息(对应目标群聊) + args: 参数字典 {"group_id": int, "user_id": int, "reject_add_request": bool (可选)} + group_info: 群聊信息(可选,可自动获取 group_id) Returns: Tuple[str, Dict[str, Any]]: (action, params) """ - user_id: int = int(args["qq_id"]) - group_id: int = int(group_info.group_id) + if not args: + raise ValueError("群踢人命令缺少参数") + + # 优先从 args 获取 group_id,否则从 group_info 获取 + group_id = args.get("group_id") + if not group_id and group_info: + group_id = int(group_info.group_id) + + user_id = args.get("user_id") + + if not group_id: + raise ValueError("群踢人命令缺少必要参数: group_id") + if not user_id: + raise ValueError("群踢人命令缺少必要参数: user_id") + + group_id = int(group_id) + user_id = int(user_id) if group_id <= 0: raise ValueError("群组ID无效") if user_id <= 0: raise ValueError("用户ID无效") + + # reject_add_request 是可选参数,默认 False + reject_add_request = args.get("reject_add_request", False) + return ( CommandType.GROUP_KICK.value, { "group_id": group_id, "user_id": user_id, - "reject_add_request": False, # 不拒绝加群请求 + "reject_add_request": bool(reject_add_request), + }, + ) + + @staticmethod + @register_command(CommandType.GROUP_KICK_MEMBERS, require_group=False) + def handle_kick_members_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """处理批量踢出群成员命令 + + Args: + args: 参数字典 {"group_id": int, "user_id": List[int], "reject_add_request": bool (可选)} + group_info: 群聊信息(可选,可自动获取 group_id) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("批量踢人命令缺少参数") + + # 优先从 args 获取 group_id,否则从 group_info 获取 + group_id = args.get("group_id") + if not group_id and group_info: + group_id = int(group_info.group_id) + + user_id = args.get("user_id") + + if not group_id: + raise ValueError("批量踢人命令缺少必要参数: group_id") + if not user_id: + raise ValueError("批量踢人命令缺少必要参数: user_id") + + # 验证 user_id 是数组 + if not isinstance(user_id, list): + raise ValueError("user_id 必须是数组类型") + if len(user_id) == 0: + raise ValueError("user_id 数组不能为空") + + # 转换并验证每个 user_id + user_id_list = [] + for uid in user_id: + try: + uid_int = int(uid) + if uid_int <= 0: + raise ValueError(f"用户ID无效: {uid}") + user_id_list.append(uid_int) + except (ValueError, TypeError) as e: + raise ValueError(f"用户ID格式错误: {uid} - {str(e)}") from None + + group_id = int(group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + + # reject_add_request 是可选参数,默认 False + reject_add_request = args.get("reject_add_request", False) + + return ( + CommandType.GROUP_KICK_MEMBERS.value, + { + "group_id": group_id, + "user_id": user_id_list, + "reject_add_request": bool(reject_add_request), }, ) @@ -178,6 +257,45 @@ def handle_poke_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) - }, ) + @staticmethod + @register_command(CommandType.SET_GROUP_NAME, require_group=False) + def handle_set_group_name_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """设置群名 + + Args: + args: 参数字典 {"group_id": int, "group_name": str} + group_info: 群聊信息(可选,可自动获取 group_id) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("设置群名命令缺少参数") + + # 优先从 args 获取 group_id,否则从 group_info 获取 + group_id = args.get("group_id") + if not group_id and group_info: + group_id = int(group_info.group_id) + + group_name = args.get("group_name") + + if not group_id: + raise ValueError("设置群名命令缺少必要参数: group_id") + if not group_name: + raise ValueError("设置群名命令缺少必要参数: group_name") + + group_id = int(group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + + return ( + CommandType.SET_GROUP_NAME.value, + { + "group_id": group_id, + "group_name": str(group_name), + }, + ) + @staticmethod @register_command(CommandType.DELETE_MSG, require_group=False) def delete_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: @@ -199,12 +317,40 @@ def delete_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> except (ValueError, TypeError) as e: raise ValueError(f"消息ID无效: {args['message_id']} - {str(e)}") from None - return ( - CommandType.DELETE_MSG.value, - { - "message_id": message_id, - }, - ) + return (CommandType.DELETE_MSG.value, {"message_id": message_id}) + + @staticmethod + @register_command(CommandType.SET_QQ_PROFILE, require_group=False) + def handle_set_qq_profile_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """设置账号信息 + + Args: + args: 参数字典 {"nickname": str, "personal_note": str (可选), "sex": str (可选)} + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("设置账号信息命令缺少参数") + + nickname = args.get("nickname") + if not nickname: + raise ValueError("设置账号信息命令缺少必要参数: nickname") + + params = {"nickname": str(nickname)} + + # 可选参数 + if "personal_note" in args: + params["personal_note"] = str(args["personal_note"]) + + if "sex" in args: + sex = str(args["sex"]).lower() + if sex not in ["male", "female", "unknown"]: + raise ValueError(f"性别参数无效: {sex},必须为 male/female/unknown 之一") + params["sex"] = sex + + return (CommandType.SET_QQ_PROFILE.value, params) @staticmethod @register_command(CommandType.AI_VOICE_SEND, require_group=True) @@ -276,3 +422,298 @@ def handle_message_like_command(args: Dict[str, Any], group_info: Optional[Group "set": True, }, ) + + # ============ 查询类命令处理器 ============ + + @staticmethod + @register_command(CommandType.GET_LOGIN_INFO, require_group=False) + def handle_get_login_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取登录号信息(Bot自身信息) + + Args: + args: 参数字典(无需参数) + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + return (CommandType.GET_LOGIN_INFO.value, {}) + + @staticmethod + @register_command(CommandType.GET_STRANGER_INFO, require_group=False) + def handle_get_stranger_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取陌生人信息 + + Args: + args: 参数字典 {"user_id": int} + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("获取陌生人信息命令缺少参数") + + user_id = args.get("user_id") + if not user_id: + raise ValueError("获取陌生人信息命令缺少必要参数: user_id") + + user_id = int(user_id) + if user_id <= 0: + raise ValueError("用户ID无效") + + return ( + CommandType.GET_STRANGER_INFO.value, + {"user_id": user_id}, + ) + + @staticmethod + @register_command(CommandType.GET_FRIEND_LIST, require_group=False) + def handle_get_friend_list_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取好友列表 + + Args: + args: 参数字典 {"no_cache": bool} (可选,默认 false) + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + # no_cache 参数是可选的,默认为 false + no_cache = args.get("no_cache", False) if args else False + + return (CommandType.GET_FRIEND_LIST.value, {"no_cache": bool(no_cache)}) + + @staticmethod + @register_command(CommandType.GET_GROUP_INFO, require_group=False) + def handle_get_group_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取群信息 + + Args: + args: 参数字典 {"group_id": int} 或从 group_info 自动获取 + group_info: 群聊信息(可选) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + # 优先从 args 获取,否则从 group_info 获取 + group_id = args.get("group_id") if args else None + if not group_id and group_info: + group_id = int(group_info.group_id) + + if not group_id: + raise ValueError("获取群信息命令缺少必要参数: group_id") + + group_id = int(group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + + return ( + CommandType.GET_GROUP_INFO.value, + {"group_id": group_id}, + ) + + @staticmethod + @register_command(CommandType.GET_GROUP_DETAIL_INFO, require_group=False) + def handle_get_group_detail_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取群详细信息 + + Args: + args: 参数字典 {"group_id": int} 或从 group_info 自动获取 + group_info: 群聊信息(可选) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + # 优先从 args 获取,否则从 group_info 获取 + group_id = args.get("group_id") if args else None + if not group_id and group_info: + group_id = int(group_info.group_id) + + if not group_id: + raise ValueError("获取群详细信息命令缺少必要参数: group_id") + + group_id = int(group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + + return ( + CommandType.GET_GROUP_DETAIL_INFO.value, + {"group_id": group_id}, + ) + + @staticmethod + @register_command(CommandType.GET_GROUP_LIST, require_group=False) + def handle_get_group_list_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取群列表 + + Args: + args: 参数字典 {"no_cache": bool} (可选,默认 false) + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + # no_cache 参数是可选的,默认为 false + no_cache = args.get("no_cache", False) if args else False + + return (CommandType.GET_GROUP_LIST.value, {"no_cache": bool(no_cache)}) + + @staticmethod + @register_command(CommandType.GET_GROUP_AT_ALL_REMAIN, require_group=False) + def handle_get_group_at_all_remain_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取群@全体成员剩余次数 + + Args: + args: 参数字典 {"group_id": int} 或从 group_info 自动获取 + group_info: 群聊信息(可选) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + # 优先从 args 获取,否则从 group_info 获取 + group_id = args.get("group_id") if args else None + if not group_id and group_info: + group_id = int(group_info.group_id) + + if not group_id: + raise ValueError("获取群@全体成员剩余次数命令缺少必要参数: group_id") + + group_id = int(group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + + return ( + CommandType.GET_GROUP_AT_ALL_REMAIN.value, + {"group_id": group_id}, + ) + + @staticmethod + @register_command(CommandType.GET_GROUP_MEMBER_INFO, require_group=False) + def handle_get_group_member_info_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取群成员信息 + + Args: + args: 参数字典 {"group_id": int, "user_id": int, "no_cache": bool} 或 group_id 从 group_info 自动获取 + group_info: 群聊信息(可选) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("获取群成员信息命令缺少参数") + + # 优先从 args 获取,否则从 group_info 获取 + group_id = args.get("group_id") + if not group_id and group_info: + group_id = int(group_info.group_id) + + user_id = args.get("user_id") + no_cache = args.get("no_cache", False) + + if not group_id: + raise ValueError("获取群成员信息命令缺少必要参数: group_id") + if not user_id: + raise ValueError("获取群成员信息命令缺少必要参数: user_id") + + group_id = int(group_id) + user_id = int(user_id) + if group_id <= 0: + raise ValueError("群组ID无效") + if user_id <= 0: + raise ValueError("用户ID无效") + + return ( + CommandType.GET_GROUP_MEMBER_INFO.value, + { + "group_id": group_id, + "user_id": user_id, + "no_cache": bool(no_cache), + }, + ) + + @staticmethod + @register_command(CommandType.GET_GROUP_MEMBER_LIST, require_group=False) + def handle_get_group_member_list_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取群成员列表 + + Args: + args: 参数字典 {"group_id": int, "no_cache": bool} 或 group_id 从 group_info 自动获取 + group_info: 群聊信息(可选) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + # 优先从 args 获取,否则从 group_info 获取 + group_id = args.get("group_id") if args else None + if not group_id and group_info: + group_id = int(group_info.group_id) + + no_cache = args.get("no_cache", False) if args else False + + if not group_id: + raise ValueError("获取群成员列表命令缺少必要参数: group_id") + + group_id = int(group_id) + if group_id <= 0: + raise ValueError("群组ID无效") + + return ( + CommandType.GET_GROUP_MEMBER_LIST.value, + { + "group_id": group_id, + "no_cache": bool(no_cache), + }, + ) + + @staticmethod + @register_command(CommandType.GET_MSG, require_group=False) + def handle_get_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取消息详情 + + Args: + args: 参数字典 {"message_id": int} + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("获取消息命令缺少参数") + + message_id = args.get("message_id") + if not message_id: + raise ValueError("获取消息命令缺少必要参数: message_id") + + message_id = int(message_id) + if message_id <= 0: + raise ValueError("消息ID无效") + + return ( + CommandType.GET_MSG.value, + {"message_id": message_id}, + ) + + @staticmethod + @register_command(CommandType.GET_FORWARD_MSG, require_group=False) + def handle_get_forward_msg_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + """获取合并转发消息 + + Args: + args: 参数字典 {"message_id": str} + group_info: 群聊信息(不使用) + + Returns: + Tuple[str, Dict[str, Any]]: (action, params) + """ + if not args: + raise ValueError("获取合并转发消息命令缺少参数") + + message_id = args.get("message_id") + if not message_id: + raise ValueError("获取合并转发消息命令缺少必要参数: message_id") + + return ( + CommandType.GET_FORWARD_MSG.value, + {"message_id": str(message_id)}, + ) diff --git a/src/send_handler/send_message_handler.py b/src/send_handler/send_message_handler.py index b5c70f5..101ef8d 100644 --- a/src/send_handler/send_message_handler.py +++ b/src/send_handler/send_message_handler.py @@ -216,12 +216,61 @@ def handle_videourl_message(video_url: str) -> dict: } @staticmethod - def handle_file_message(file_path: str) -> dict: - """处理文件消息""" - return { - "type": "file", - "data": {"file": f"file://{file_path}"}, - } + def handle_file_message(file_data) -> dict: + """处理文件消息 + + Args: + file_data: 可以是字符串(文件路径)或字典(完整文件信息) + - 字符串:简单的文件路径 + - 字典:包含 file, name, path, thumb, url 等字段 + + Returns: + NapCat 格式的文件消息段 + """ + # 如果是简单的字符串路径(兼容旧版本) + if isinstance(file_data, str): + return { + "type": "file", + "data": {"file": f"file://{file_data}"}, + } + + # 如果是完整的字典数据 + if isinstance(file_data, dict): + data = {} + + # file 字段是必需的 + if "file" in file_data: + file_value = file_data["file"] + # 如果是本地路径且没有协议前缀,添加 file:// 前缀 + if not any(file_value.startswith(prefix) for prefix in ["file://", "http://", "https://", "base64://"]): + data["file"] = f"file://{file_value}" + else: + data["file"] = file_value + else: + # 没有 file 字段,尝试使用 path 或 url + if "path" in file_data: + data["file"] = f"file://{file_data['path']}" + elif "url" in file_data: + data["file"] = file_data["url"] + else: + logger.warning("文件消息缺少必要的 file/path/url 字段") + return None + + # 添加可选字段 + if "name" in file_data: + data["name"] = file_data["name"] + if "thumb" in file_data: + data["thumb"] = file_data["thumb"] + if "url" in file_data and "file" not in file_data: + data["file"] = file_data["url"] + + return { + "type": "file", + "data": data, + } + + logger.warning(f"不支持的文件数据类型: {type(file_data)}") + return None @staticmethod def handle_imageurl_message(image_url: str) -> dict: From 29e852dcd07859f28e20a51e3924a52afe2b0ef9 Mon Sep 17 00:00:00 2001 From: sharkie Date: Sat, 10 Jan 2026 07:07:10 +0800 Subject: [PATCH 105/112] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=8D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E7=9A=84=E8=B4=B4=E8=A1=A8=E6=83=85action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- command_args.md | 2 +- src/__init__.py | 2 +- src/send_handler/send_command_handler.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/command_args.md b/command_args.md index 6bc9319..3f21a5b 100644 --- a/command_args.md +++ b/command_args.md @@ -101,7 +101,7 @@ Seg.data: Dict[str, Any] = { ### 给消息贴表情 ```python Seg.data: Dict[str, Any] = { - "name": "MESSAGE_LIKE", + "name": "SET_MSG_EMOJI_LIKE", "args": { "message_id": "消息ID", "emoji_id": "表情ID" diff --git a/src/__init__.py b/src/__init__.py index 4deadb0..81b187b 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -16,7 +16,7 @@ class CommandType(Enum): SEND_POKE = "send_poke" # 戳一戳 DELETE_MSG = "delete_msg" # 撤回消息 AI_VOICE_SEND = "send_group_ai_record" # 发送群AI语音 - MESSAGE_LIKE = "message_like" # 给消息贴表情 + SET_MSG_EMOJI_LIKE = "set_msg_emoji_like" # 给消息贴表情 SET_QQ_PROFILE = "set_qq_profile" # 设置账号信息 # 查询类命令 diff --git a/src/send_handler/send_command_handler.py b/src/send_handler/send_command_handler.py index e8a37ba..bdacb62 100644 --- a/src/send_handler/send_command_handler.py +++ b/src/send_handler/send_command_handler.py @@ -386,8 +386,8 @@ def handle_ai_voice_send_command(args: Dict[str, Any], group_info: Optional[Grou ) @staticmethod - @register_command(CommandType.MESSAGE_LIKE, require_group=False) - def handle_message_like_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: + @register_command(CommandType.SET_MSG_EMOJI_LIKE, require_group=False) + def handle_set_msg_emoji_like_command(args: Dict[str, Any], group_info: Optional[GroupInfo]) -> Tuple[str, Dict[str, Any]]: """处理给消息贴表情命令 Args: @@ -415,7 +415,7 @@ def handle_message_like_command(args: Dict[str, Any], group_info: Optional[Group raise ValueError("表情ID无效") return ( - CommandType.MESSAGE_LIKE.value, + CommandType.SET_MSG_EMOJI_LIKE.value, { "message_id": message_id, "emoji_id": emoji_id, From c6f892def05da53e3cb31feaf385ac90509f6c46 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Thu, 15 Jan 2026 19:25:51 +0800 Subject: [PATCH 106/112] =?UTF-8?q?=E7=A7=BB=E9=99=A4uvlock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a943d3c..f6eb732 100644 --- a/.gitignore +++ b/.gitignore @@ -277,4 +277,6 @@ config.toml.back test data/NapcatAdapter.db data/NapcatAdapter.db-shm -data/NapcatAdapter.db-wal \ No newline at end of file +data/NapcatAdapter.db-wal + +uv.lock \ No newline at end of file From 0af35595066879b7b69773146eb34ae341ee8c86 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 16 Jan 2026 00:26:20 +0800 Subject: [PATCH 107/112] =?UTF-8?q?=E5=88=A0=E9=99=A4uvlock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uv.lock | 1089 ------------------------------------------------------- 1 file changed, 1089 deletions(-) delete mode 100644 uv.lock diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 9ffe115..0000000 --- a/uv.lock +++ /dev/null @@ -1,1089 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload_time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload_time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.13.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload_time = "2025-10-28T20:59:39.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload_time = "2025-10-28T20:57:02.455Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload_time = "2025-10-28T20:57:04.784Z" }, - { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload_time = "2025-10-28T20:57:06.894Z" }, - { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload_time = "2025-10-28T20:57:08.685Z" }, - { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload_time = "2025-10-28T20:57:10.693Z" }, - { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload_time = "2025-10-28T20:57:12.563Z" }, - { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload_time = "2025-10-28T20:57:14.623Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload_time = "2025-10-28T20:57:16.399Z" }, - { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload_time = "2025-10-28T20:57:18.288Z" }, - { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload_time = "2025-10-28T20:57:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload_time = "2025-10-28T20:57:22.253Z" }, - { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload_time = "2025-10-28T20:57:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload_time = "2025-10-28T20:57:26.257Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload_time = "2025-10-28T20:57:28.349Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload_time = "2025-10-28T20:57:30.233Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload_time = "2025-10-28T20:57:32.105Z" }, - { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload_time = "2025-10-28T20:57:34.166Z" }, - { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload_time = "2025-10-28T20:57:36.415Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload_time = "2025-10-28T20:57:38.205Z" }, - { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload_time = "2025-10-28T20:57:40.122Z" }, - { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload_time = "2025-10-28T20:57:42.28Z" }, - { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload_time = "2025-10-28T20:57:44.869Z" }, - { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload_time = "2025-10-28T20:57:47.216Z" }, - { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload_time = "2025-10-28T20:57:49.337Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload_time = "2025-10-28T20:57:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload_time = "2025-10-28T20:57:53.554Z" }, - { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload_time = "2025-10-28T20:57:55.617Z" }, - { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload_time = "2025-10-28T20:57:57.59Z" }, - { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload_time = "2025-10-28T20:57:59.525Z" }, - { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload_time = "2025-10-28T20:58:01.914Z" }, - { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload_time = "2025-10-28T20:58:03.972Z" }, - { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload_time = "2025-10-28T20:58:06.189Z" }, - { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload_time = "2025-10-28T20:58:08.636Z" }, - { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload_time = "2025-10-28T20:58:11Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload_time = "2025-10-28T20:58:13.358Z" }, - { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload_time = "2025-10-28T20:58:15.339Z" }, - { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload_time = "2025-10-28T20:58:17.693Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload_time = "2025-10-28T20:58:20.113Z" }, - { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload_time = "2025-10-28T20:58:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload_time = "2025-10-28T20:58:24.672Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload_time = "2025-10-28T20:58:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload_time = "2025-10-28T20:58:29.787Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload_time = "2025-10-28T20:58:32.529Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload_time = "2025-10-28T20:58:34.618Z" }, - { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload_time = "2025-10-28T20:58:38.835Z" }, - { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload_time = "2025-10-28T20:58:41.507Z" }, - { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload_time = "2025-10-28T20:58:43.674Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload_time = "2025-10-28T20:58:45.787Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload_time = "2025-10-28T20:58:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload_time = "2025-10-28T20:58:50.642Z" }, - { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload_time = "2025-10-28T20:58:52.782Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload_time = "2025-07-03T22:54:43.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload_time = "2025-07-03T22:54:42.156Z" }, -] - -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload_time = "2025-11-10T22:07:42.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload_time = "2025-11-10T22:07:40.673Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload_time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload_time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload_time = "2025-11-28T23:37:38.911Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload_time = "2025-11-28T23:36:57.897Z" }, -] - -[[package]] -name = "asyncio" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/ea/26c489a11f7ca862d5705db67683a7361ce11c23a7b98fc6c2deaeccede2/asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b", size = 5371, upload_time = "2025-08-05T02:51:46.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/64/eff2564783bd650ca25e15938d1c5b459cda997574a510f7de69688cb0b4/asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b", size = 5555, upload_time = "2025-08-05T02:51:45.767Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload_time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload_time = "2025-10-06T13:54:43.17Z" }, -] - -[[package]] -name = "certifi" -version = "2025.11.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload_time = "2025-11-12T02:54:51.517Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload_time = "2025-11-12T02:54:49.735Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload_time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload_time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload_time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload_time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload_time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload_time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload_time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload_time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload_time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload_time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload_time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload_time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload_time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload_time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload_time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload_time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload_time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload_time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload_time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload_time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload_time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload_time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload_time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload_time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload_time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload_time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload_time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload_time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload_time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload_time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload_time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload_time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload_time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload_time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload_time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload_time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload_time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload_time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload_time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload_time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload_time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload_time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload_time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload_time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload_time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload_time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload_time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload_time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload_time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload_time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload_time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload_time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload_time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload_time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload_time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload_time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload_time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload_time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload_time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload_time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload_time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload_time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload_time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload_time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload_time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload_time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload_time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload_time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload_time = "2025-10-14T04:42:31.76Z" }, -] - -[[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload_time = "2025-11-15T20:45:42.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload_time = "2025-11-15T20:45:41.139Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "46.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload_time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload_time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload_time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload_time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload_time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload_time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload_time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload_time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload_time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload_time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload_time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload_time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload_time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload_time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload_time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload_time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload_time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload_time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload_time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload_time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload_time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload_time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload_time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload_time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload_time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload_time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload_time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload_time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload_time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload_time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload_time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload_time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload_time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload_time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload_time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload_time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload_time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload_time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload_time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload_time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload_time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload_time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload_time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload_time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload_time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload_time = "2025-10-15T23:18:12.277Z" }, -] - -[[package]] -name = "fastapi" -version = "0.128.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload_time = "2025-12-27T15:21:13.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload_time = "2025-12-27T15:21:12.154Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload_time = "2025-10-06T05:38:17.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload_time = "2025-10-06T05:36:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload_time = "2025-10-06T05:36:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload_time = "2025-10-06T05:36:29.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload_time = "2025-10-06T05:36:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload_time = "2025-10-06T05:36:32.531Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload_time = "2025-10-06T05:36:33.706Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload_time = "2025-10-06T05:36:34.947Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload_time = "2025-10-06T05:36:36.534Z" }, - { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload_time = "2025-10-06T05:36:38.582Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload_time = "2025-10-06T05:36:40.152Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload_time = "2025-10-06T05:36:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload_time = "2025-10-06T05:36:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload_time = "2025-10-06T05:36:44.251Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload_time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload_time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload_time = "2025-10-06T05:36:47.8Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload_time = "2025-10-06T05:36:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload_time = "2025-10-06T05:36:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload_time = "2025-10-06T05:36:50.851Z" }, - { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload_time = "2025-10-06T05:36:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload_time = "2025-10-06T05:36:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload_time = "2025-10-06T05:36:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload_time = "2025-10-06T05:36:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload_time = "2025-10-06T05:36:56.758Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload_time = "2025-10-06T05:36:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload_time = "2025-10-06T05:36:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload_time = "2025-10-06T05:37:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload_time = "2025-10-06T05:37:02.115Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload_time = "2025-10-06T05:37:03.711Z" }, - { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload_time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload_time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload_time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload_time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload_time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload_time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload_time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload_time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload_time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload_time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload_time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload_time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload_time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload_time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload_time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload_time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload_time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload_time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload_time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload_time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload_time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload_time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload_time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload_time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload_time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload_time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload_time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload_time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload_time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload_time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload_time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload_time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload_time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload_time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload_time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload_time = "2025-10-06T05:38:16.721Z" }, -] - -[[package]] -name = "greenlet" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload_time = "2025-12-04T14:49:44.05Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload_time = "2025-12-04T14:23:01.282Z" }, - { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload_time = "2025-12-04T14:50:08.309Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload_time = "2025-12-04T14:57:43.968Z" }, - { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload_time = "2025-12-04T15:07:14.697Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload_time = "2025-12-04T14:26:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload_time = "2025-12-04T15:04:25.276Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload_time = "2025-12-04T14:27:30.804Z" }, - { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload_time = "2025-12-04T14:32:23.929Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload_time = "2025-12-04T14:23:05.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload_time = "2025-12-04T14:50:10.026Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload_time = "2025-12-04T14:57:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload_time = "2025-12-04T15:07:15.789Z" }, - { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload_time = "2025-12-04T14:26:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload_time = "2025-12-04T15:04:27.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload_time = "2025-12-04T14:27:32.366Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload_time = "2025-12-04T14:26:51.063Z" }, - { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload_time = "2025-12-04T14:25:20.941Z" }, - { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload_time = "2025-12-04T14:50:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload_time = "2025-12-04T14:57:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload_time = "2025-12-04T15:07:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload_time = "2025-12-04T14:26:06.225Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload_time = "2025-12-04T15:04:28.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload_time = "2025-12-04T14:27:33.531Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload_time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload_time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload_time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload_time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "loguru" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "win32-setctime", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload_time = "2024-12-06T11:20:56.608Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload_time = "2024-12-06T11:20:54.538Z" }, -] - -[[package]] -name = "maibotnapcatadapter" -version = "0.5.5" -source = { virtual = "." } -dependencies = [ - { name = "aiohttp" }, - { name = "asyncio" }, - { name = "loguru" }, - { name = "maim-message" }, - { name = "pillow" }, - { name = "requests" }, - { name = "rich" }, - { name = "sqlmodel" }, - { name = "tomlkit" }, - { name = "watchdog" }, - { name = "websockets" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiohttp", specifier = ">=3.13.2" }, - { name = "asyncio", specifier = ">=4.0.0" }, - { name = "loguru", specifier = ">=0.7.3" }, - { name = "maim-message", specifier = ">=0.5.7" }, - { name = "pillow", specifier = ">=12.0.0" }, - { name = "requests", specifier = ">=2.32.5" }, - { name = "rich", specifier = ">=14.2.0" }, - { name = "sqlmodel", specifier = ">=0.0.27" }, - { name = "tomlkit", specifier = ">=0.13.3" }, - { name = "watchdog", specifier = ">=3.0.0" }, - { name = "websockets", specifier = ">=15.0.1" }, -] - -[[package]] -name = "maim-message" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "cryptography" }, - { name = "fastapi" }, - { name = "pydantic" }, - { name = "uvicorn" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/42/adea9b875803fe8a2d35e171a036acf57c7dc7829ad246289e25fd4ebc0b/maim_message-0.6.1.tar.gz", hash = "sha256:96be12e2c487856b825ee7ca2a4dd7056664357975be47ccdccab66855b87ae0", size = 731984, upload_time = "2025-12-26T11:29:40.69Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/21/f6f3bfbe6594edaf95693e521d59afbccd7d2fc624c6b9a177c0042e28e1/maim_message-0.6.1-py3-none-any.whl", hash = "sha256:e6c28c76adbdf90cb15123594506087cb02a6f6e8c9f34751828a62c4c64ec86", size = 98190, upload_time = "2025-12-26T11:29:39.073Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload_time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload_time = "2025-08-11T12:57:51.923Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload_time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload_time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "multidict" -version = "6.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload_time = "2025-10-06T14:52:30.657Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload_time = "2025-10-06T14:49:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload_time = "2025-10-06T14:49:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload_time = "2025-10-06T14:49:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload_time = "2025-10-06T14:49:58.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload_time = "2025-10-06T14:49:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload_time = "2025-10-06T14:50:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload_time = "2025-10-06T14:50:02.955Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload_time = "2025-10-06T14:50:04.446Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload_time = "2025-10-06T14:50:05.98Z" }, - { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload_time = "2025-10-06T14:50:07.511Z" }, - { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload_time = "2025-10-06T14:50:09.074Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload_time = "2025-10-06T14:50:10.714Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload_time = "2025-10-06T14:50:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload_time = "2025-10-06T14:50:14.16Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload_time = "2025-10-06T14:50:15.639Z" }, - { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload_time = "2025-10-06T14:50:17.066Z" }, - { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload_time = "2025-10-06T14:50:18.264Z" }, - { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload_time = "2025-10-06T14:50:19.853Z" }, - { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload_time = "2025-10-06T14:50:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload_time = "2025-10-06T14:50:22.871Z" }, - { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload_time = "2025-10-06T14:50:24.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload_time = "2025-10-06T14:50:25.716Z" }, - { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload_time = "2025-10-06T14:50:28.192Z" }, - { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload_time = "2025-10-06T14:50:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload_time = "2025-10-06T14:50:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload_time = "2025-10-06T14:50:33.26Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload_time = "2025-10-06T14:50:34.808Z" }, - { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload_time = "2025-10-06T14:50:36.436Z" }, - { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload_time = "2025-10-06T14:50:37.953Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload_time = "2025-10-06T14:50:39.574Z" }, - { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload_time = "2025-10-06T14:50:41.612Z" }, - { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload_time = "2025-10-06T14:50:43.972Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload_time = "2025-10-06T14:50:45.648Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload_time = "2025-10-06T14:50:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload_time = "2025-10-06T14:50:48.851Z" }, - { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload_time = "2025-10-06T14:50:50.16Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload_time = "2025-10-06T14:50:51.92Z" }, - { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload_time = "2025-10-06T14:50:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload_time = "2025-10-06T14:50:54.911Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload_time = "2025-10-06T14:50:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload_time = "2025-10-06T14:50:57.991Z" }, - { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload_time = "2025-10-06T14:50:59.589Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload_time = "2025-10-06T14:51:01.183Z" }, - { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload_time = "2025-10-06T14:51:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload_time = "2025-10-06T14:51:04.724Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload_time = "2025-10-06T14:51:06.306Z" }, - { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload_time = "2025-10-06T14:51:08.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload_time = "2025-10-06T14:51:10.365Z" }, - { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload_time = "2025-10-06T14:51:12.466Z" }, - { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload_time = "2025-10-06T14:51:14.48Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload_time = "2025-10-06T14:51:16.072Z" }, - { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload_time = "2025-10-06T14:51:17.544Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload_time = "2025-10-06T14:51:18.875Z" }, - { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload_time = "2025-10-06T14:51:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload_time = "2025-10-06T14:51:21.588Z" }, - { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload_time = "2025-10-06T14:51:22.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload_time = "2025-10-06T14:51:24.352Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload_time = "2025-10-06T14:51:25.822Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload_time = "2025-10-06T14:51:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload_time = "2025-10-06T14:51:29.664Z" }, - { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload_time = "2025-10-06T14:51:31.684Z" }, - { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload_time = "2025-10-06T14:51:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload_time = "2025-10-06T14:51:36.189Z" }, - { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload_time = "2025-10-06T14:51:41.291Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload_time = "2025-10-06T14:51:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload_time = "2025-10-06T14:51:45.265Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload_time = "2025-10-06T14:51:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload_time = "2025-10-06T14:51:48.541Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload_time = "2025-10-06T14:51:50.355Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload_time = "2025-10-06T14:51:51.883Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload_time = "2025-10-06T14:51:53.672Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload_time = "2025-10-06T14:51:55.415Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload_time = "2025-10-06T14:52:29.272Z" }, -] - -[[package]] -name = "pillow" -version = "12.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload_time = "2026-01-02T09:13:29.892Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload_time = "2026-01-02T09:11:31.566Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload_time = "2026-01-02T09:11:33.367Z" }, - { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload_time = "2026-01-02T09:11:35.011Z" }, - { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload_time = "2026-01-02T09:11:36.682Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload_time = "2026-01-02T09:11:38.535Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload_time = "2026-01-02T09:11:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload_time = "2026-01-02T09:11:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload_time = "2026-01-02T09:11:44.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload_time = "2026-01-02T09:11:46.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload_time = "2026-01-02T09:11:48.625Z" }, - { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload_time = "2026-01-02T09:11:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload_time = "2026-01-02T09:11:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload_time = "2026-01-02T09:11:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload_time = "2026-01-02T09:11:56.522Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload_time = "2026-01-02T09:11:58.227Z" }, - { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload_time = "2026-01-02T09:12:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload_time = "2026-01-02T09:12:02.572Z" }, - { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload_time = "2026-01-02T09:12:04.75Z" }, - { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload_time = "2026-01-02T09:12:06.626Z" }, - { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload_time = "2026-01-02T09:12:08.624Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload_time = "2026-01-02T09:12:10.488Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload_time = "2026-01-02T09:12:12.772Z" }, - { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload_time = "2026-01-02T09:12:14.775Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload_time = "2026-01-02T09:12:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload_time = "2026-01-02T09:12:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload_time = "2026-01-02T09:12:20.791Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload_time = "2026-01-02T09:12:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload_time = "2026-01-02T09:12:26.338Z" }, - { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload_time = "2026-01-02T09:12:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload_time = "2026-01-02T09:12:31.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload_time = "2026-01-02T09:12:32.936Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload_time = "2026-01-02T09:12:34.78Z" }, - { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload_time = "2026-01-02T09:12:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload_time = "2026-01-02T09:12:39.082Z" }, - { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload_time = "2026-01-02T09:12:40.946Z" }, - { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload_time = "2026-01-02T09:12:42.706Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload_time = "2026-01-02T09:12:44.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload_time = "2026-01-02T09:12:46.727Z" }, - { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload_time = "2026-01-02T09:12:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload_time = "2026-01-02T09:12:51.11Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload_time = "2026-01-02T09:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload_time = "2026-01-02T09:12:54.884Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload_time = "2026-01-02T09:12:56.802Z" }, - { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload_time = "2026-01-02T09:12:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload_time = "2026-01-02T09:13:00.885Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload_time = "2026-01-02T09:13:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload_time = "2026-01-02T09:13:05.276Z" }, - { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload_time = "2026-01-02T09:13:07.491Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload_time = "2026-01-02T09:13:09.841Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload_time = "2026-01-02T09:13:12.068Z" }, -] - -[[package]] -name = "propcache" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload_time = "2025-10-08T19:49:02.291Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload_time = "2025-10-08T19:47:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload_time = "2025-10-08T19:47:08.851Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload_time = "2025-10-08T19:47:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload_time = "2025-10-08T19:47:11.319Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload_time = "2025-10-08T19:47:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload_time = "2025-10-08T19:47:14.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload_time = "2025-10-08T19:47:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload_time = "2025-10-08T19:47:17.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload_time = "2025-10-08T19:47:19.355Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload_time = "2025-10-08T19:47:21.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload_time = "2025-10-08T19:47:23.059Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload_time = "2025-10-08T19:47:24.445Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload_time = "2025-10-08T19:47:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload_time = "2025-10-08T19:47:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload_time = "2025-10-08T19:47:27.961Z" }, - { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload_time = "2025-10-08T19:47:29.445Z" }, - { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload_time = "2025-10-08T19:47:30.579Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload_time = "2025-10-08T19:47:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload_time = "2025-10-08T19:47:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload_time = "2025-10-08T19:47:34.906Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload_time = "2025-10-08T19:47:36.338Z" }, - { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload_time = "2025-10-08T19:47:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload_time = "2025-10-08T19:47:39.659Z" }, - { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload_time = "2025-10-08T19:47:41.084Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload_time = "2025-10-08T19:47:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload_time = "2025-10-08T19:47:43.927Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload_time = "2025-10-08T19:47:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload_time = "2025-10-08T19:47:47.202Z" }, - { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload_time = "2025-10-08T19:47:48.336Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload_time = "2025-10-08T19:47:49.876Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload_time = "2025-10-08T19:47:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload_time = "2025-10-08T19:47:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload_time = "2025-10-08T19:47:54.073Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload_time = "2025-10-08T19:47:55.715Z" }, - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload_time = "2025-10-08T19:47:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload_time = "2025-10-08T19:47:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload_time = "2025-10-08T19:48:00.67Z" }, - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload_time = "2025-10-08T19:48:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload_time = "2025-10-08T19:48:04.499Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload_time = "2025-10-08T19:48:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload_time = "2025-10-08T19:48:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload_time = "2025-10-08T19:48:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload_time = "2025-10-08T19:48:11.232Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload_time = "2025-10-08T19:48:12.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload_time = "2025-10-08T19:48:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload_time = "2025-10-08T19:48:15.16Z" }, - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload_time = "2025-10-08T19:48:16.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload_time = "2025-10-08T19:48:17.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload_time = "2025-10-08T19:48:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload_time = "2025-10-08T19:48:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload_time = "2025-10-08T19:48:22.592Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload_time = "2025-10-08T19:48:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload_time = "2025-10-08T19:48:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload_time = "2025-10-08T19:48:27.207Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload_time = "2025-10-08T19:48:28.65Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload_time = "2025-10-08T19:48:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload_time = "2025-10-08T19:48:31.567Z" }, - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload_time = "2025-10-08T19:48:32.872Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload_time = "2025-10-08T19:48:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload_time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload_time = "2025-10-08T19:49:00.792Z" }, -] - -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload_time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload_time = "2025-09-09T13:23:46.651Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload_time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload_time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload_time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload_time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload_time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload_time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload_time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload_time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload_time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload_time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload_time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload_time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload_time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload_time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload_time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload_time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload_time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload_time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload_time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload_time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload_time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload_time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload_time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload_time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload_time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload_time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload_time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload_time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload_time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload_time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload_time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload_time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload_time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload_time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload_time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload_time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload_time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload_time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload_time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload_time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload_time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload_time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload_time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload_time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload_time = "2025-11-04T13:42:01.186Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload_time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload_time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload_time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload_time = "2025-08-18T20:46:00.542Z" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload_time = "2025-10-09T14:16:53.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload_time = "2025-10-09T14:16:51.245Z" }, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.45" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload_time = "2025-12-09T21:05:16.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload_time = "2025-12-09T22:11:06.167Z" }, - { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload_time = "2025-12-09T22:13:52.626Z" }, - { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload_time = "2025-12-09T22:11:08.093Z" }, - { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload_time = "2025-12-09T22:13:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload_time = "2025-12-09T21:39:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload_time = "2025-12-09T21:39:36.801Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload_time = "2025-12-09T22:13:28.622Z" }, - { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload_time = "2025-12-09T22:13:30.188Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload_time = "2025-12-09T22:11:09.662Z" }, - { url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload_time = "2025-12-09T22:13:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload_time = "2025-12-09T22:11:11.1Z" }, - { url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload_time = "2025-12-09T22:13:57.932Z" }, - { url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload_time = "2025-12-09T21:39:38.365Z" }, - { url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload_time = "2025-12-09T21:39:39.503Z" }, - { url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload_time = "2025-12-09T22:13:32.09Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload_time = "2025-12-09T22:13:33.739Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload_time = "2025-12-09T21:54:52.608Z" }, -] - -[[package]] -name = "sqlmodel" -version = "0.0.31" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "sqlalchemy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload_time = "2025-12-28T12:35:01.436Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload_time = "2025-12-28T12:35:00.108Z" }, -] - -[[package]] -name = "starlette" -version = "0.50.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload_time = "2025-11-01T15:25:27.516Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload_time = "2025-11-01T15:25:25.461Z" }, -] - -[[package]] -name = "tomlkit" -version = "0.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload_time = "2025-06-05T07:13:44.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload_time = "2025-06-05T07:13:43.546Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload_time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload_time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload_time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload_time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload_time = "2025-12-11T15:56:40.252Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload_time = "2025-12-11T15:56:38.584Z" }, -] - -[[package]] -name = "uvicorn" -version = "0.40.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload_time = "2025-12-21T14:16:22.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload_time = "2025-12-21T14:16:21.041Z" }, -] - -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload_time = "2024-11-01T14:07:13.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload_time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload_time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload_time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload_time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload_time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload_time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload_time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload_time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload_time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload_time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload_time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload_time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload_time = "2024-11-01T14:07:11.845Z" }, -] - -[[package]] -name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload_time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload_time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload_time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload_time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload_time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload_time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload_time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload_time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload_time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload_time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload_time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload_time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload_time = "2025-03-05T20:03:39.41Z" }, -] - -[[package]] -name = "win32-setctime" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload_time = "2024-12-07T15:28:28.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload_time = "2024-12-07T15:28:26.465Z" }, -] - -[[package]] -name = "yarl" -version = "1.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload_time = "2025-10-06T14:12:55.963Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload_time = "2025-10-06T14:10:14.601Z" }, - { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload_time = "2025-10-06T14:10:16.115Z" }, - { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload_time = "2025-10-06T14:10:17.993Z" }, - { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload_time = "2025-10-06T14:10:19.44Z" }, - { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload_time = "2025-10-06T14:10:21.124Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload_time = "2025-10-06T14:10:22.902Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload_time = "2025-10-06T14:10:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload_time = "2025-10-06T14:10:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload_time = "2025-10-06T14:10:28.461Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload_time = "2025-10-06T14:10:30.541Z" }, - { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload_time = "2025-10-06T14:10:33.352Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload_time = "2025-10-06T14:10:35.034Z" }, - { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload_time = "2025-10-06T14:10:37.76Z" }, - { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload_time = "2025-10-06T14:10:39.649Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload_time = "2025-10-06T14:10:41.313Z" }, - { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload_time = "2025-10-06T14:10:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload_time = "2025-10-06T14:10:44.643Z" }, - { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload_time = "2025-10-06T14:10:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload_time = "2025-10-06T14:10:48.007Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload_time = "2025-10-06T14:10:49.997Z" }, - { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload_time = "2025-10-06T14:10:52.004Z" }, - { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload_time = "2025-10-06T14:10:54.078Z" }, - { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload_time = "2025-10-06T14:10:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload_time = "2025-10-06T14:10:57.985Z" }, - { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload_time = "2025-10-06T14:10:59.633Z" }, - { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload_time = "2025-10-06T14:11:01.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload_time = "2025-10-06T14:11:03.452Z" }, - { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload_time = "2025-10-06T14:11:05.115Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload_time = "2025-10-06T14:11:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload_time = "2025-10-06T14:11:10.284Z" }, - { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload_time = "2025-10-06T14:11:11.739Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload_time = "2025-10-06T14:11:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload_time = "2025-10-06T14:11:15.465Z" }, - { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload_time = "2025-10-06T14:11:17.106Z" }, - { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload_time = "2025-10-06T14:11:19.064Z" }, - { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload_time = "2025-10-06T14:11:20.996Z" }, - { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload_time = "2025-10-06T14:11:22.847Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload_time = "2025-10-06T14:11:24.889Z" }, - { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload_time = "2025-10-06T14:11:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload_time = "2025-10-06T14:11:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload_time = "2025-10-06T14:11:31.423Z" }, - { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload_time = "2025-10-06T14:11:33.055Z" }, - { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload_time = "2025-10-06T14:11:35.136Z" }, - { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload_time = "2025-10-06T14:11:37.094Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload_time = "2025-10-06T14:11:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload_time = "2025-10-06T14:11:40.624Z" }, - { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload_time = "2025-10-06T14:11:42.578Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload_time = "2025-10-06T14:11:44.863Z" }, - { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload_time = "2025-10-06T14:11:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload_time = "2025-10-06T14:11:48.845Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload_time = "2025-10-06T14:11:50.897Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload_time = "2025-10-06T14:11:52.549Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload_time = "2025-10-06T14:11:54.225Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload_time = "2025-10-06T14:11:56.069Z" }, - { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload_time = "2025-10-06T14:11:58.783Z" }, - { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload_time = "2025-10-06T14:12:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload_time = "2025-10-06T14:12:02.628Z" }, - { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload_time = "2025-10-06T14:12:04.871Z" }, - { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload_time = "2025-10-06T14:12:06.624Z" }, - { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload_time = "2025-10-06T14:12:08.362Z" }, - { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload_time = "2025-10-06T14:12:10.994Z" }, - { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload_time = "2025-10-06T14:12:13.317Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload_time = "2025-10-06T14:12:15.398Z" }, - { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload_time = "2025-10-06T14:12:16.935Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload_time = "2025-10-06T14:12:53.872Z" }, -] From e0d4b6ee557497b3df437d97565f0c21f4421b5b Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Fri, 16 Jan 2026 00:27:20 +0800 Subject: [PATCH 108/112] =?UTF-8?q?=E5=88=A0=E9=99=A4uvlock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- uv.lock | 1089 ---------------------------------------------------- 2 files changed, 3 insertions(+), 1090 deletions(-) delete mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 116b41d..f99dbf3 100644 --- a/.gitignore +++ b/.gitignore @@ -276,4 +276,6 @@ config.toml.back test data/NapcatAdapter.db data/NapcatAdapter.db-shm -data/NapcatAdapter.db-wal \ No newline at end of file +data/NapcatAdapter.db-wal + +uv.lock \ No newline at end of file diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 9ffe115..0000000 --- a/uv.lock +++ /dev/null @@ -1,1089 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.13" - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload_time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload_time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.13.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload_time = "2025-10-28T20:59:39.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload_time = "2025-10-28T20:57:02.455Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload_time = "2025-10-28T20:57:04.784Z" }, - { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload_time = "2025-10-28T20:57:06.894Z" }, - { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload_time = "2025-10-28T20:57:08.685Z" }, - { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload_time = "2025-10-28T20:57:10.693Z" }, - { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload_time = "2025-10-28T20:57:12.563Z" }, - { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload_time = "2025-10-28T20:57:14.623Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload_time = "2025-10-28T20:57:16.399Z" }, - { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload_time = "2025-10-28T20:57:18.288Z" }, - { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload_time = "2025-10-28T20:57:20.241Z" }, - { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload_time = "2025-10-28T20:57:22.253Z" }, - { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload_time = "2025-10-28T20:57:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload_time = "2025-10-28T20:57:26.257Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload_time = "2025-10-28T20:57:28.349Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload_time = "2025-10-28T20:57:30.233Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload_time = "2025-10-28T20:57:32.105Z" }, - { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload_time = "2025-10-28T20:57:34.166Z" }, - { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload_time = "2025-10-28T20:57:36.415Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload_time = "2025-10-28T20:57:38.205Z" }, - { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload_time = "2025-10-28T20:57:40.122Z" }, - { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload_time = "2025-10-28T20:57:42.28Z" }, - { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload_time = "2025-10-28T20:57:44.869Z" }, - { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload_time = "2025-10-28T20:57:47.216Z" }, - { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload_time = "2025-10-28T20:57:49.337Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload_time = "2025-10-28T20:57:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload_time = "2025-10-28T20:57:53.554Z" }, - { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload_time = "2025-10-28T20:57:55.617Z" }, - { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload_time = "2025-10-28T20:57:57.59Z" }, - { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload_time = "2025-10-28T20:57:59.525Z" }, - { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload_time = "2025-10-28T20:58:01.914Z" }, - { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload_time = "2025-10-28T20:58:03.972Z" }, - { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload_time = "2025-10-28T20:58:06.189Z" }, - { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload_time = "2025-10-28T20:58:08.636Z" }, - { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload_time = "2025-10-28T20:58:11Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload_time = "2025-10-28T20:58:13.358Z" }, - { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload_time = "2025-10-28T20:58:15.339Z" }, - { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload_time = "2025-10-28T20:58:17.693Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload_time = "2025-10-28T20:58:20.113Z" }, - { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload_time = "2025-10-28T20:58:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload_time = "2025-10-28T20:58:24.672Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload_time = "2025-10-28T20:58:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload_time = "2025-10-28T20:58:29.787Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload_time = "2025-10-28T20:58:32.529Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload_time = "2025-10-28T20:58:34.618Z" }, - { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload_time = "2025-10-28T20:58:38.835Z" }, - { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload_time = "2025-10-28T20:58:41.507Z" }, - { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload_time = "2025-10-28T20:58:43.674Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload_time = "2025-10-28T20:58:45.787Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload_time = "2025-10-28T20:58:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload_time = "2025-10-28T20:58:50.642Z" }, - { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload_time = "2025-10-28T20:58:52.782Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload_time = "2025-07-03T22:54:43.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload_time = "2025-07-03T22:54:42.156Z" }, -] - -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload_time = "2025-11-10T22:07:42.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload_time = "2025-11-10T22:07:40.673Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload_time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload_time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload_time = "2025-11-28T23:37:38.911Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload_time = "2025-11-28T23:36:57.897Z" }, -] - -[[package]] -name = "asyncio" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/ea/26c489a11f7ca862d5705db67683a7361ce11c23a7b98fc6c2deaeccede2/asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b", size = 5371, upload_time = "2025-08-05T02:51:46.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/64/eff2564783bd650ca25e15938d1c5b459cda997574a510f7de69688cb0b4/asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b", size = 5555, upload_time = "2025-08-05T02:51:45.767Z" }, -] - -[[package]] -name = "attrs" -version = "25.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload_time = "2025-10-06T13:54:44.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload_time = "2025-10-06T13:54:43.17Z" }, -] - -[[package]] -name = "certifi" -version = "2025.11.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload_time = "2025-11-12T02:54:51.517Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload_time = "2025-11-12T02:54:49.735Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload_time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload_time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload_time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload_time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload_time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload_time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload_time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload_time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload_time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload_time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload_time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload_time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload_time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload_time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload_time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload_time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload_time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload_time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload_time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload_time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload_time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload_time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload_time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload_time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload_time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload_time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload_time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload_time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload_time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload_time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload_time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload_time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload_time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload_time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload_time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload_time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload_time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload_time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload_time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload_time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload_time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload_time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload_time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload_time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload_time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload_time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload_time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload_time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload_time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload_time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload_time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload_time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload_time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload_time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload_time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload_time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload_time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload_time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload_time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload_time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload_time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload_time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload_time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload_time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload_time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload_time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload_time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload_time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload_time = "2025-10-14T04:42:31.76Z" }, -] - -[[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload_time = "2025-11-15T20:45:42.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload_time = "2025-11-15T20:45:41.139Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "46.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload_time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload_time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload_time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload_time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload_time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload_time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload_time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload_time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload_time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload_time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload_time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload_time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload_time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload_time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload_time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload_time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload_time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload_time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload_time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload_time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload_time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload_time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload_time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload_time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload_time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload_time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload_time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload_time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload_time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload_time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload_time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload_time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload_time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload_time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload_time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload_time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload_time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload_time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload_time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload_time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload_time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload_time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload_time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload_time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload_time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload_time = "2025-10-15T23:18:12.277Z" }, -] - -[[package]] -name = "fastapi" -version = "0.128.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "pydantic" }, - { name = "starlette" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload_time = "2025-12-27T15:21:13.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload_time = "2025-12-27T15:21:12.154Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload_time = "2025-10-06T05:38:17.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload_time = "2025-10-06T05:36:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload_time = "2025-10-06T05:36:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload_time = "2025-10-06T05:36:29.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload_time = "2025-10-06T05:36:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload_time = "2025-10-06T05:36:32.531Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload_time = "2025-10-06T05:36:33.706Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload_time = "2025-10-06T05:36:34.947Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload_time = "2025-10-06T05:36:36.534Z" }, - { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload_time = "2025-10-06T05:36:38.582Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload_time = "2025-10-06T05:36:40.152Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload_time = "2025-10-06T05:36:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload_time = "2025-10-06T05:36:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload_time = "2025-10-06T05:36:44.251Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload_time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload_time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload_time = "2025-10-06T05:36:47.8Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload_time = "2025-10-06T05:36:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload_time = "2025-10-06T05:36:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload_time = "2025-10-06T05:36:50.851Z" }, - { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload_time = "2025-10-06T05:36:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload_time = "2025-10-06T05:36:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload_time = "2025-10-06T05:36:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload_time = "2025-10-06T05:36:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload_time = "2025-10-06T05:36:56.758Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload_time = "2025-10-06T05:36:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload_time = "2025-10-06T05:36:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload_time = "2025-10-06T05:37:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload_time = "2025-10-06T05:37:02.115Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload_time = "2025-10-06T05:37:03.711Z" }, - { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload_time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload_time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload_time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload_time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload_time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload_time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload_time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload_time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload_time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload_time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload_time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload_time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload_time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload_time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload_time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload_time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload_time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload_time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload_time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload_time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload_time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload_time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload_time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload_time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload_time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload_time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload_time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload_time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload_time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload_time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload_time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload_time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload_time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload_time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload_time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload_time = "2025-10-06T05:38:16.721Z" }, -] - -[[package]] -name = "greenlet" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload_time = "2025-12-04T14:49:44.05Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload_time = "2025-12-04T14:23:01.282Z" }, - { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload_time = "2025-12-04T14:50:08.309Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload_time = "2025-12-04T14:57:43.968Z" }, - { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload_time = "2025-12-04T15:07:14.697Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload_time = "2025-12-04T14:26:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload_time = "2025-12-04T15:04:25.276Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload_time = "2025-12-04T14:27:30.804Z" }, - { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload_time = "2025-12-04T14:32:23.929Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload_time = "2025-12-04T14:23:05.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload_time = "2025-12-04T14:50:10.026Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload_time = "2025-12-04T14:57:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload_time = "2025-12-04T15:07:15.789Z" }, - { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload_time = "2025-12-04T14:26:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload_time = "2025-12-04T15:04:27.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload_time = "2025-12-04T14:27:32.366Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload_time = "2025-12-04T14:26:51.063Z" }, - { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload_time = "2025-12-04T14:25:20.941Z" }, - { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload_time = "2025-12-04T14:50:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload_time = "2025-12-04T14:57:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload_time = "2025-12-04T15:07:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload_time = "2025-12-04T14:26:06.225Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload_time = "2025-12-04T15:04:28.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload_time = "2025-12-04T14:27:33.531Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload_time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload_time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload_time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload_time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "loguru" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "win32-setctime", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload_time = "2024-12-06T11:20:56.608Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload_time = "2024-12-06T11:20:54.538Z" }, -] - -[[package]] -name = "maibotnapcatadapter" -version = "0.5.5" -source = { virtual = "." } -dependencies = [ - { name = "aiohttp" }, - { name = "asyncio" }, - { name = "loguru" }, - { name = "maim-message" }, - { name = "pillow" }, - { name = "requests" }, - { name = "rich" }, - { name = "sqlmodel" }, - { name = "tomlkit" }, - { name = "watchdog" }, - { name = "websockets" }, -] - -[package.metadata] -requires-dist = [ - { name = "aiohttp", specifier = ">=3.13.2" }, - { name = "asyncio", specifier = ">=4.0.0" }, - { name = "loguru", specifier = ">=0.7.3" }, - { name = "maim-message", specifier = ">=0.5.7" }, - { name = "pillow", specifier = ">=12.0.0" }, - { name = "requests", specifier = ">=2.32.5" }, - { name = "rich", specifier = ">=14.2.0" }, - { name = "sqlmodel", specifier = ">=0.0.27" }, - { name = "tomlkit", specifier = ">=0.13.3" }, - { name = "watchdog", specifier = ">=3.0.0" }, - { name = "websockets", specifier = ">=15.0.1" }, -] - -[[package]] -name = "maim-message" -version = "0.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "cryptography" }, - { name = "fastapi" }, - { name = "pydantic" }, - { name = "uvicorn" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/42/adea9b875803fe8a2d35e171a036acf57c7dc7829ad246289e25fd4ebc0b/maim_message-0.6.1.tar.gz", hash = "sha256:96be12e2c487856b825ee7ca2a4dd7056664357975be47ccdccab66855b87ae0", size = 731984, upload_time = "2025-12-26T11:29:40.69Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/21/f6f3bfbe6594edaf95693e521d59afbccd7d2fc624c6b9a177c0042e28e1/maim_message-0.6.1-py3-none-any.whl", hash = "sha256:e6c28c76adbdf90cb15123594506087cb02a6f6e8c9f34751828a62c4c64ec86", size = 98190, upload_time = "2025-12-26T11:29:39.073Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload_time = "2025-08-11T12:57:52.854Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload_time = "2025-08-11T12:57:51.923Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload_time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload_time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "multidict" -version = "6.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload_time = "2025-10-06T14:52:30.657Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload_time = "2025-10-06T14:49:54.26Z" }, - { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload_time = "2025-10-06T14:49:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload_time = "2025-10-06T14:49:57.048Z" }, - { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload_time = "2025-10-06T14:49:58.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload_time = "2025-10-06T14:49:59.89Z" }, - { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload_time = "2025-10-06T14:50:01.485Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload_time = "2025-10-06T14:50:02.955Z" }, - { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload_time = "2025-10-06T14:50:04.446Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload_time = "2025-10-06T14:50:05.98Z" }, - { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload_time = "2025-10-06T14:50:07.511Z" }, - { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload_time = "2025-10-06T14:50:09.074Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload_time = "2025-10-06T14:50:10.714Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload_time = "2025-10-06T14:50:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload_time = "2025-10-06T14:50:14.16Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload_time = "2025-10-06T14:50:15.639Z" }, - { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload_time = "2025-10-06T14:50:17.066Z" }, - { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload_time = "2025-10-06T14:50:18.264Z" }, - { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload_time = "2025-10-06T14:50:19.853Z" }, - { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload_time = "2025-10-06T14:50:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload_time = "2025-10-06T14:50:22.871Z" }, - { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload_time = "2025-10-06T14:50:24.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload_time = "2025-10-06T14:50:25.716Z" }, - { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload_time = "2025-10-06T14:50:28.192Z" }, - { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload_time = "2025-10-06T14:50:29.82Z" }, - { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload_time = "2025-10-06T14:50:31.731Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload_time = "2025-10-06T14:50:33.26Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload_time = "2025-10-06T14:50:34.808Z" }, - { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload_time = "2025-10-06T14:50:36.436Z" }, - { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload_time = "2025-10-06T14:50:37.953Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload_time = "2025-10-06T14:50:39.574Z" }, - { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload_time = "2025-10-06T14:50:41.612Z" }, - { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload_time = "2025-10-06T14:50:43.972Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload_time = "2025-10-06T14:50:45.648Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload_time = "2025-10-06T14:50:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload_time = "2025-10-06T14:50:48.851Z" }, - { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload_time = "2025-10-06T14:50:50.16Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload_time = "2025-10-06T14:50:51.92Z" }, - { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload_time = "2025-10-06T14:50:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload_time = "2025-10-06T14:50:54.911Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload_time = "2025-10-06T14:50:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload_time = "2025-10-06T14:50:57.991Z" }, - { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload_time = "2025-10-06T14:50:59.589Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload_time = "2025-10-06T14:51:01.183Z" }, - { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload_time = "2025-10-06T14:51:02.794Z" }, - { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload_time = "2025-10-06T14:51:04.724Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload_time = "2025-10-06T14:51:06.306Z" }, - { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload_time = "2025-10-06T14:51:08.091Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload_time = "2025-10-06T14:51:10.365Z" }, - { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload_time = "2025-10-06T14:51:12.466Z" }, - { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload_time = "2025-10-06T14:51:14.48Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload_time = "2025-10-06T14:51:16.072Z" }, - { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload_time = "2025-10-06T14:51:17.544Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload_time = "2025-10-06T14:51:18.875Z" }, - { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload_time = "2025-10-06T14:51:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload_time = "2025-10-06T14:51:21.588Z" }, - { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload_time = "2025-10-06T14:51:22.93Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload_time = "2025-10-06T14:51:24.352Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload_time = "2025-10-06T14:51:25.822Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload_time = "2025-10-06T14:51:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload_time = "2025-10-06T14:51:29.664Z" }, - { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload_time = "2025-10-06T14:51:31.684Z" }, - { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload_time = "2025-10-06T14:51:33.699Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload_time = "2025-10-06T14:51:36.189Z" }, - { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload_time = "2025-10-06T14:51:41.291Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload_time = "2025-10-06T14:51:43.55Z" }, - { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload_time = "2025-10-06T14:51:45.265Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload_time = "2025-10-06T14:51:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload_time = "2025-10-06T14:51:48.541Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload_time = "2025-10-06T14:51:50.355Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload_time = "2025-10-06T14:51:51.883Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload_time = "2025-10-06T14:51:53.672Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload_time = "2025-10-06T14:51:55.415Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload_time = "2025-10-06T14:52:29.272Z" }, -] - -[[package]] -name = "pillow" -version = "12.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload_time = "2026-01-02T09:13:29.892Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload_time = "2026-01-02T09:11:31.566Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload_time = "2026-01-02T09:11:33.367Z" }, - { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload_time = "2026-01-02T09:11:35.011Z" }, - { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload_time = "2026-01-02T09:11:36.682Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload_time = "2026-01-02T09:11:38.535Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload_time = "2026-01-02T09:11:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload_time = "2026-01-02T09:11:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload_time = "2026-01-02T09:11:44.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload_time = "2026-01-02T09:11:46.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload_time = "2026-01-02T09:11:48.625Z" }, - { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload_time = "2026-01-02T09:11:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload_time = "2026-01-02T09:11:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload_time = "2026-01-02T09:11:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload_time = "2026-01-02T09:11:56.522Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload_time = "2026-01-02T09:11:58.227Z" }, - { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload_time = "2026-01-02T09:12:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload_time = "2026-01-02T09:12:02.572Z" }, - { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload_time = "2026-01-02T09:12:04.75Z" }, - { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload_time = "2026-01-02T09:12:06.626Z" }, - { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload_time = "2026-01-02T09:12:08.624Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload_time = "2026-01-02T09:12:10.488Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload_time = "2026-01-02T09:12:12.772Z" }, - { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload_time = "2026-01-02T09:12:14.775Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload_time = "2026-01-02T09:12:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload_time = "2026-01-02T09:12:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload_time = "2026-01-02T09:12:20.791Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload_time = "2026-01-02T09:12:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload_time = "2026-01-02T09:12:26.338Z" }, - { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload_time = "2026-01-02T09:12:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload_time = "2026-01-02T09:12:31.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload_time = "2026-01-02T09:12:32.936Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload_time = "2026-01-02T09:12:34.78Z" }, - { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload_time = "2026-01-02T09:12:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload_time = "2026-01-02T09:12:39.082Z" }, - { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload_time = "2026-01-02T09:12:40.946Z" }, - { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload_time = "2026-01-02T09:12:42.706Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload_time = "2026-01-02T09:12:44.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload_time = "2026-01-02T09:12:46.727Z" }, - { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload_time = "2026-01-02T09:12:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload_time = "2026-01-02T09:12:51.11Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload_time = "2026-01-02T09:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload_time = "2026-01-02T09:12:54.884Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload_time = "2026-01-02T09:12:56.802Z" }, - { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload_time = "2026-01-02T09:12:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload_time = "2026-01-02T09:13:00.885Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload_time = "2026-01-02T09:13:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload_time = "2026-01-02T09:13:05.276Z" }, - { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload_time = "2026-01-02T09:13:07.491Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload_time = "2026-01-02T09:13:09.841Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload_time = "2026-01-02T09:13:12.068Z" }, -] - -[[package]] -name = "propcache" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload_time = "2025-10-08T19:49:02.291Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload_time = "2025-10-08T19:47:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload_time = "2025-10-08T19:47:08.851Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload_time = "2025-10-08T19:47:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload_time = "2025-10-08T19:47:11.319Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload_time = "2025-10-08T19:47:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload_time = "2025-10-08T19:47:14.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload_time = "2025-10-08T19:47:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload_time = "2025-10-08T19:47:17.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload_time = "2025-10-08T19:47:19.355Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload_time = "2025-10-08T19:47:21.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload_time = "2025-10-08T19:47:23.059Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload_time = "2025-10-08T19:47:24.445Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload_time = "2025-10-08T19:47:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload_time = "2025-10-08T19:47:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload_time = "2025-10-08T19:47:27.961Z" }, - { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload_time = "2025-10-08T19:47:29.445Z" }, - { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload_time = "2025-10-08T19:47:30.579Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload_time = "2025-10-08T19:47:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload_time = "2025-10-08T19:47:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload_time = "2025-10-08T19:47:34.906Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload_time = "2025-10-08T19:47:36.338Z" }, - { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload_time = "2025-10-08T19:47:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload_time = "2025-10-08T19:47:39.659Z" }, - { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload_time = "2025-10-08T19:47:41.084Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload_time = "2025-10-08T19:47:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload_time = "2025-10-08T19:47:43.927Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload_time = "2025-10-08T19:47:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload_time = "2025-10-08T19:47:47.202Z" }, - { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload_time = "2025-10-08T19:47:48.336Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload_time = "2025-10-08T19:47:49.876Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload_time = "2025-10-08T19:47:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload_time = "2025-10-08T19:47:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload_time = "2025-10-08T19:47:54.073Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload_time = "2025-10-08T19:47:55.715Z" }, - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload_time = "2025-10-08T19:47:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload_time = "2025-10-08T19:47:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload_time = "2025-10-08T19:48:00.67Z" }, - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload_time = "2025-10-08T19:48:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload_time = "2025-10-08T19:48:04.499Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload_time = "2025-10-08T19:48:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload_time = "2025-10-08T19:48:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload_time = "2025-10-08T19:48:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload_time = "2025-10-08T19:48:11.232Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload_time = "2025-10-08T19:48:12.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload_time = "2025-10-08T19:48:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload_time = "2025-10-08T19:48:15.16Z" }, - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload_time = "2025-10-08T19:48:16.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload_time = "2025-10-08T19:48:17.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload_time = "2025-10-08T19:48:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload_time = "2025-10-08T19:48:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload_time = "2025-10-08T19:48:22.592Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload_time = "2025-10-08T19:48:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload_time = "2025-10-08T19:48:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload_time = "2025-10-08T19:48:27.207Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload_time = "2025-10-08T19:48:28.65Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload_time = "2025-10-08T19:48:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload_time = "2025-10-08T19:48:31.567Z" }, - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload_time = "2025-10-08T19:48:32.872Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload_time = "2025-10-08T19:48:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload_time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload_time = "2025-10-08T19:49:00.792Z" }, -] - -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload_time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload_time = "2025-09-09T13:23:46.651Z" }, -] - -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload_time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload_time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload_time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload_time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload_time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload_time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload_time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload_time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload_time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload_time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload_time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload_time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload_time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload_time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload_time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload_time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload_time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload_time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload_time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload_time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload_time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload_time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload_time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload_time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload_time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload_time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload_time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload_time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload_time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload_time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload_time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload_time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload_time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload_time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload_time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload_time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload_time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload_time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload_time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload_time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload_time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload_time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload_time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload_time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload_time = "2025-11-04T13:42:01.186Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload_time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload_time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload_time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload_time = "2025-08-18T20:46:00.542Z" }, -] - -[[package]] -name = "rich" -version = "14.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload_time = "2025-10-09T14:16:53.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload_time = "2025-10-09T14:16:51.245Z" }, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.45" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload_time = "2025-12-09T21:05:16.737Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload_time = "2025-12-09T22:11:06.167Z" }, - { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload_time = "2025-12-09T22:13:52.626Z" }, - { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload_time = "2025-12-09T22:11:08.093Z" }, - { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload_time = "2025-12-09T22:13:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload_time = "2025-12-09T21:39:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload_time = "2025-12-09T21:39:36.801Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload_time = "2025-12-09T22:13:28.622Z" }, - { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload_time = "2025-12-09T22:13:30.188Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload_time = "2025-12-09T22:11:09.662Z" }, - { url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload_time = "2025-12-09T22:13:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload_time = "2025-12-09T22:11:11.1Z" }, - { url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload_time = "2025-12-09T22:13:57.932Z" }, - { url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload_time = "2025-12-09T21:39:38.365Z" }, - { url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload_time = "2025-12-09T21:39:39.503Z" }, - { url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload_time = "2025-12-09T22:13:32.09Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload_time = "2025-12-09T22:13:33.739Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload_time = "2025-12-09T21:54:52.608Z" }, -] - -[[package]] -name = "sqlmodel" -version = "0.0.31" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "sqlalchemy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/b8/e7cd6def4a773f25d6e29ffce63ccbfd6cf9488b804ab6fb9b80d334b39d/sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c", size = 94952, upload_time = "2025-12-28T12:35:01.436Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/72/5aa5be921800f6418a949a73c9bb7054890881143e6bc604a93d228a95a3/sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513", size = 27093, upload_time = "2025-12-28T12:35:00.108Z" }, -] - -[[package]] -name = "starlette" -version = "0.50.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload_time = "2025-11-01T15:25:27.516Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload_time = "2025-11-01T15:25:25.461Z" }, -] - -[[package]] -name = "tomlkit" -version = "0.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload_time = "2025-06-05T07:13:44.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload_time = "2025-06-05T07:13:43.546Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload_time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload_time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload_time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload_time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload_time = "2025-12-11T15:56:40.252Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload_time = "2025-12-11T15:56:38.584Z" }, -] - -[[package]] -name = "uvicorn" -version = "0.40.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload_time = "2025-12-21T14:16:22.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload_time = "2025-12-21T14:16:21.041Z" }, -] - -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload_time = "2024-11-01T14:07:13.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload_time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload_time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload_time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload_time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload_time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload_time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload_time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload_time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload_time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload_time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload_time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload_time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload_time = "2024-11-01T14:07:11.845Z" }, -] - -[[package]] -name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload_time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload_time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload_time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload_time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload_time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload_time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload_time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload_time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload_time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload_time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload_time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload_time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload_time = "2025-03-05T20:03:39.41Z" }, -] - -[[package]] -name = "win32-setctime" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload_time = "2024-12-07T15:28:28.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload_time = "2024-12-07T15:28:26.465Z" }, -] - -[[package]] -name = "yarl" -version = "1.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload_time = "2025-10-06T14:12:55.963Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload_time = "2025-10-06T14:10:14.601Z" }, - { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload_time = "2025-10-06T14:10:16.115Z" }, - { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload_time = "2025-10-06T14:10:17.993Z" }, - { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload_time = "2025-10-06T14:10:19.44Z" }, - { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload_time = "2025-10-06T14:10:21.124Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload_time = "2025-10-06T14:10:22.902Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload_time = "2025-10-06T14:10:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload_time = "2025-10-06T14:10:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload_time = "2025-10-06T14:10:28.461Z" }, - { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload_time = "2025-10-06T14:10:30.541Z" }, - { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload_time = "2025-10-06T14:10:33.352Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload_time = "2025-10-06T14:10:35.034Z" }, - { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload_time = "2025-10-06T14:10:37.76Z" }, - { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload_time = "2025-10-06T14:10:39.649Z" }, - { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload_time = "2025-10-06T14:10:41.313Z" }, - { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload_time = "2025-10-06T14:10:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload_time = "2025-10-06T14:10:44.643Z" }, - { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload_time = "2025-10-06T14:10:46.554Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload_time = "2025-10-06T14:10:48.007Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload_time = "2025-10-06T14:10:49.997Z" }, - { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload_time = "2025-10-06T14:10:52.004Z" }, - { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload_time = "2025-10-06T14:10:54.078Z" }, - { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload_time = "2025-10-06T14:10:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload_time = "2025-10-06T14:10:57.985Z" }, - { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload_time = "2025-10-06T14:10:59.633Z" }, - { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload_time = "2025-10-06T14:11:01.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload_time = "2025-10-06T14:11:03.452Z" }, - { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload_time = "2025-10-06T14:11:05.115Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload_time = "2025-10-06T14:11:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload_time = "2025-10-06T14:11:10.284Z" }, - { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload_time = "2025-10-06T14:11:11.739Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload_time = "2025-10-06T14:11:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload_time = "2025-10-06T14:11:15.465Z" }, - { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload_time = "2025-10-06T14:11:17.106Z" }, - { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload_time = "2025-10-06T14:11:19.064Z" }, - { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload_time = "2025-10-06T14:11:20.996Z" }, - { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload_time = "2025-10-06T14:11:22.847Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload_time = "2025-10-06T14:11:24.889Z" }, - { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload_time = "2025-10-06T14:11:27.307Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload_time = "2025-10-06T14:11:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload_time = "2025-10-06T14:11:31.423Z" }, - { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload_time = "2025-10-06T14:11:33.055Z" }, - { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload_time = "2025-10-06T14:11:35.136Z" }, - { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload_time = "2025-10-06T14:11:37.094Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload_time = "2025-10-06T14:11:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload_time = "2025-10-06T14:11:40.624Z" }, - { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload_time = "2025-10-06T14:11:42.578Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload_time = "2025-10-06T14:11:44.863Z" }, - { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload_time = "2025-10-06T14:11:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload_time = "2025-10-06T14:11:48.845Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload_time = "2025-10-06T14:11:50.897Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload_time = "2025-10-06T14:11:52.549Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload_time = "2025-10-06T14:11:54.225Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload_time = "2025-10-06T14:11:56.069Z" }, - { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload_time = "2025-10-06T14:11:58.783Z" }, - { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload_time = "2025-10-06T14:12:00.686Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload_time = "2025-10-06T14:12:02.628Z" }, - { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload_time = "2025-10-06T14:12:04.871Z" }, - { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload_time = "2025-10-06T14:12:06.624Z" }, - { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload_time = "2025-10-06T14:12:08.362Z" }, - { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload_time = "2025-10-06T14:12:10.994Z" }, - { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload_time = "2025-10-06T14:12:13.317Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload_time = "2025-10-06T14:12:15.398Z" }, - { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload_time = "2025-10-06T14:12:16.935Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload_time = "2025-10-06T14:12:53.872Z" }, -] From 58d7be7f0811e0c9bbd3900a23427630e44022c4 Mon Sep 17 00:00:00 2001 From: tcmofashi Date: Fri, 16 Jan 2026 07:21:15 +0000 Subject: [PATCH 109/112] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81maim=5Fmessag?= =?UTF-8?q?e=20API=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +- src/config/official_configs.py | 9 ++ src/mmc_com_layer.py | 165 ++++++++++++++++++++++++++++++--- template/template_config.toml | 3 + 4 files changed, 165 insertions(+), 15 deletions(-) diff --git a/main.py b/main.py index 6f824a6..5761593 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ from src.recv_handler.message_sending import message_send_instance from src.send_handler.nc_sending import nc_message_sender from src.config import global_config -from src.mmc_com_layer import mmc_start_com, mmc_stop_com, router +from src.mmc_com_layer import mmc_start_com, mmc_stop_com from src.response_pool import put_response, check_timeout_response message_queue = asyncio.Queue() @@ -47,7 +47,6 @@ async def message_process(): async def main(): - message_send_instance.maibot_router = router _ = await asyncio.gather(napcat_server(), mmc_start_com(), message_process(), check_timeout_response()) def check_napcat_server_token(conn, request): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 98b4552..245501b 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -46,6 +46,15 @@ class MaiBotServerConfig(ConfigBase): port: int = 8000 """MaiMCore的端口号""" + enable_api_server: bool = False + """是否启用API-Server模式连接""" + + base_url: str = "" + """API-Server连接地址 (ws://ipp:port/path)""" + + api_key: str = "" + """API Key (仅在enable_api_server为True时使用)""" + @dataclass class ChatConfig(ConfigBase): diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index 012d153..f2a5206 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -1,24 +1,163 @@ -from maim_message import Router, RouteConfig, TargetConfig +from maim_message import Router, RouteConfig, TargetConfig, MessageBase from .config import global_config from .logger import logger, custom_logger from .send_handler.main_send_handler import send_handler +from .recv_handler.message_sending import message_send_instance +from maim_message.client import create_client_config, WebSocketClient +from maim_message.message import APIMessageBase +from typing import Dict, Any +import importlib.metadata -route_config = RouteConfig( - route_config={ - global_config.maibot_server.platform_name: TargetConfig( - url=f"ws://{global_config.maibot_server.host}:{global_config.maibot_server.port}/ws", - token=None, +# 检查 maim_message 版本是否支持 MessageConverter (>= 0.6.2) +try: + maim_message_version = importlib.metadata.version("maim_message") + version_int = [int(x) for x in maim_message_version.split(".")] + HAS_MESSAGE_CONVERTER = version_int >= [0, 6, 2] +except (importlib.metadata.PackageNotFoundError, ValueError): + HAS_MESSAGE_CONVERTER = False + +# router = Router(route_config, custom_logger) +# router will be initialized in mmc_start_com +router = None + + +class APIServerWrapper: + """ + Wrapper to make WebSocketClient compatible with legacy Router interface + """ + def __init__(self, client: WebSocketClient): + self.client = client + self.platform = global_config.maibot_server.platform_name + + def register_class_handler(self, handler): + # In API Server mode, we register the on_message callback in config, + # but here we might need to bridge it if the handler structure is different. + # However, WebSocketClient config handles on_message. + # The legacy Router.register_class_handler registers a handler for received messages. + # We need to adapt the callback style. + pass + + async def send_message(self, message: MessageBase) -> bool: + # 使用 MessageConverter 转换 Legacy MessageBase 到 APIMessageBase + # 接收场景:Adapter 收到来自 Napcat 的消息,发送给 MaiMBot + # group_info/user_info 是消息发送者信息,放入 sender_info + from maim_message import MessageConverter + + api_message = MessageConverter.to_api_receive( + message=message, + api_key=global_config.maibot_server.api_key, + platform=message.message_info.platform or self.platform, ) - } -) -router = Router(route_config, custom_logger) + return await self.client.send_message(api_message) + + async def send_custom_message(self, platform: str, message_type_name: str, message: Dict) -> bool: + return await self.client.send_custom_message(message_type_name, message) + async def run(self): + await self.client.start() + await self.client.connect() + + async def stop(self): + await self.client.stop() + +# Global variable to hold the communication object (Router or Wrapper) +router = None + +async def _legacy_message_handler_adapter(message: APIMessageBase, metadata: dict): + # Adapter to call the legacy handler with dict as expected by main_send_handler + # send_handler.handle_message expects a dict. + # We need to convert APIMessageBase back to dict legacy format if possible. + # Or check what handle_message expects. + # main_send_handler.py: handle_message takes raw_message_base_dict: dict + # and does MessageBase.from_dict(raw_message_base_dict). + + # So we need to serialize APIMessageBase to a dict that looks like legacy MessageBase dict. + # This might be tricky if structures diverged. + # Let's try `to_dict()` if available, otherwise construct it. + + # Inspecting APIMessageBase structure from docs: + # APIMessageBase has message_info, message_segment, message_dim. + # Legacy MessageBase has message_info, message_segment. + + # We can try to construct the dict. + data = { + "message_info": { + "id": message.message_info.message_id, + "timestamp": message.message_info.time, + "group_info": {}, # Fill if available + "user_info": {}, # Fill if available + }, + "message_segment": { + "type": message.message_segment.type, + "data": message.message_segment.data + } + } + # Note: This is an approximation. Ideally we should check strict compatibility. + # However, for the adapter -> bot direction (sending to napcat), + # the bot sends messages to adapter? No, Adapter sends to Bot? + # mmc_com_layer seems to be for Adapter talking to MaiBot Core. + # recv_handler/message_sending.py uses this router to send TO MaiBot. + # The `register_class_handler` in `mmc_start_com` suggests MaiBot sends messages TO Adapter? + # Wait, `send_handler.handle_message` seems to be handling messages RECEIVED FROM MaiBot. + # So `router` is bidirectional. + + # If explicit to_dict is needed: + await send_handler.handle_message(data) async def mmc_start_com(): - logger.info("正在连接MaiBot") - router.register_class_handler(send_handler.handle_message) - await router.run() + global router + config = global_config.maibot_server + + if config.enable_api_server and HAS_MESSAGE_CONVERTER: + logger.info("使用 API-Server 模式连接 MaiBot") + + # Create legacy adapter handler + # We need to define the on_message callback here to bridge to send_handler + async def on_message_bridge(message: APIMessageBase, metadata: Dict[str, Any]): + # 使用 MessageConverter 转换 APIMessageBase 到 Legacy MessageBase + # 发送场景:收到来自 MaiMBot 的回复消息,需要发送给 Napcat + # receiver_info 包含消息接收者信息,需要提取到 group_info/user_info + try: + from maim_message import MessageConverter + + legacy_message = MessageConverter.from_api_send(message) + msg_dict = legacy_message.to_dict() + + await send_handler.handle_message(msg_dict) + + except Exception as e: + logger.error(f"消息桥接转换失败: {e}") + import traceback + logger.error(traceback.format_exc()) + + client_config = create_client_config( + url=config.base_url, + api_key=config.api_key, + platform=config.platform_name, + on_message=on_message_bridge + ) + + client = WebSocketClient(client_config) + router = APIServerWrapper(client) + message_send_instance.maibot_router = router + await router.run() + + else: + logger.info("使用 Legacy WebSocket 模式连接 MaiBot") + route_config = RouteConfig( + route_config={ + config.platform_name: TargetConfig( + url=f"ws://{config.host}:{config.port}/ws", + token=None, + ) + } + ) + router = Router(route_config, custom_logger) + router.register_class_handler(send_handler.handle_message) + message_send_instance.maibot_router = router + await router.run() async def mmc_stop_com(): - await router.stop() + if router: + await router.stop() diff --git a/template/template_config.toml b/template/template_config.toml index 63b55ae..d217f8c 100644 --- a/template/template_config.toml +++ b/template/template_config.toml @@ -14,6 +14,9 @@ heartbeat_interval = 30 # 与Napcat设置的心跳相同(按秒计) [maibot_server] # 连接麦麦的ws服务设置 host = "localhost" # 麦麦在.env文件中设置的主机地址,即HOST字段 port = 8000 # 麦麦在.env文件中设置的端口,即PORT字段 +enable_api_server = false # 是否启用API-Server模式连接 +base_url = "" # API-Server连接地址 (ws://ip:port/path),仅在enable_api_server为true时使用 +api_key = "" # API Key (仅在enable_api_server为true时使用) [chat] # 黑白名单功能 group_list_type = "whitelist" # 群组名单类型,可选为:whitelist, blacklist From 866637b658ceefd1dd1843a350ad00f20cbfd641 Mon Sep 17 00:00:00 2001 From: tcmofashi Date: Fri, 16 Jan 2026 09:31:09 +0000 Subject: [PATCH 110/112] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0maim=5Fmessag?= =?UTF-8?q?e=E8=87=AA=E5=AE=9A=E4=B9=89logger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 ++- src/mmc_com_layer.py | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 749e59a..583e332 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,11 +2,12 @@ name = "MaiBotNapcatAdapter" version = "0.5.5" description = "A MaiBot adapter for Napcat" +requires-python = ">=3.10" dependencies = [ "aiohttp>=3.13.2", "asyncio>=4.0.0", "loguru>=0.7.3", - "maim-message>=0.5.7", + # maim-message - 使用本地开发版本,通过uv pip install -e手动安装 "pillow>=12.0.0", "requests>=2.32.5", "rich>=14.2.0", diff --git a/src/mmc_com_layer.py b/src/mmc_com_layer.py index f2a5206..659b934 100644 --- a/src/mmc_com_layer.py +++ b/src/mmc_com_layer.py @@ -107,10 +107,10 @@ async def _legacy_message_handler_adapter(message: APIMessageBase, metadata: dic async def mmc_start_com(): global router config = global_config.maibot_server - + if config.enable_api_server and HAS_MESSAGE_CONVERTER: logger.info("使用 API-Server 模式连接 MaiBot") - + # Create legacy adapter handler # We need to define the on_message callback here to bridge to send_handler async def on_message_bridge(message: APIMessageBase, metadata: Dict[str, Any]): @@ -119,12 +119,12 @@ async def on_message_bridge(message: APIMessageBase, metadata: Dict[str, Any]): # receiver_info 包含消息接收者信息,需要提取到 group_info/user_info try: from maim_message import MessageConverter - + legacy_message = MessageConverter.from_api_send(message) msg_dict = legacy_message.to_dict() - + await send_handler.handle_message(msg_dict) - + except Exception as e: logger.error(f"消息桥接转换失败: {e}") import traceback @@ -134,14 +134,15 @@ async def on_message_bridge(message: APIMessageBase, metadata: Dict[str, Any]): url=config.base_url, api_key=config.api_key, platform=config.platform_name, - on_message=on_message_bridge + on_message=on_message_bridge, + custom_logger=custom_logger # 传入自定义logger ) - + client = WebSocketClient(client_config) router = APIServerWrapper(client) message_send_instance.maibot_router = router await router.run() - + else: logger.info("使用 Legacy WebSocket 模式连接 MaiBot") route_config = RouteConfig( From b165eff6b979a9459953a80c4483e3ba71b352ce Mon Sep 17 00:00:00 2001 From: tcmofashi Date: Fri, 16 Jan 2026 15:31:04 +0000 Subject: [PATCH 111/112] =?UTF-8?q?req:=20=E4=BF=AE=E5=A4=8D=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 583e332..8c3316d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ dependencies = [ "aiohttp>=3.13.2", "asyncio>=4.0.0", "loguru>=0.7.3", - # maim-message - 使用本地开发版本,通过uv pip install -e手动安装 + "maim-message>=0.6.2", "pillow>=12.0.0", "requests>=2.32.5", "rich>=14.2.0", @@ -35,7 +35,7 @@ select = [ "B", # flake8-bugbear ] -ignore = ["E711","E501"] +ignore = ["E711", "E501"] [tool.ruff.format] docstring-code-format = true From 4f928b42c1c35960b21169352a69d2e8094f16d1 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Mon, 19 Jan 2026 10:08:37 +0800 Subject: [PATCH 112/112] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8c3316d..3ecd8b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "MaiBotNapcatAdapter" -version = "0.5.5" +version = "0.7.0" description = "A MaiBot adapter for Napcat" requires-python = ">=3.10" dependencies = [