From 8a7e9d9188afcdb82538e9b268a84fc688be699f Mon Sep 17 00:00:00 2001 From: Haining Date: Fri, 19 Dec 2025 14:07:51 +0800 Subject: [PATCH 1/3] feat: enhance UI/UX with PDF support, library filtering, and enhanced toolset --- .gitignore | 64 +- CLAUDE.md | 56 + GEMINI.md | 16 + LICENSE | 21 + docs/GUIDE.md | 227 ++ docs/INTRODUCTION.md | 137 + README.md => docs/Original README.md | 0 package/.env.example | 5 + package/pack.sh | 52 + package/setup.sh | 64 + package/start.sh | 17 + pyproject.toml | 6 + reader3.png | Bin 238611 -> 0 bytes reader3.py | 198 +- requirements.txt | 55 + server.py | 2002 +++++++++- templates/library.html | 1287 +++++- templates/pdf_reader.html | 2132 ++++++++++ templates/reader.html | 5440 +++++++++++++++++++++++++- tools/_md2pdf.py | 219 ++ tools/export_txt.md | 32 + tools/export_txt.py | 126 + uv.lock | 1225 +++++- 23 files changed, 13104 insertions(+), 277 deletions(-) create mode 100644 CLAUDE.md create mode 100644 GEMINI.md create mode 100644 LICENSE create mode 100644 docs/GUIDE.md create mode 100644 docs/INTRODUCTION.md rename README.md => docs/Original README.md (100%) create mode 100644 package/.env.example create mode 100755 package/pack.sh create mode 100755 package/setup.sh create mode 100755 package/start.sh delete mode 100644 reader3.png create mode 100644 requirements.txt create mode 100644 templates/pdf_reader.html create mode 100644 tools/_md2pdf.py create mode 100644 tools/export_txt.md create mode 100644 tools/export_txt.py diff --git a/.gitignore b/.gitignore index 9e1d25d4..fce13eb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,56 @@ -# Python-generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info +# Book data (extracted images, pkl, etc.) +*_data/ -# Virtual environments -.venv +# TTS cache +.tts_cache/ -# Custom -*_data/ +# Library metadata cache +.library_index.json + +# EPUB source files *.epub + +# Dictionary data +dict/ + +# Test files +test_*.py + +# Python +__pycache__/ +*.pyc +.venv/ + +# OS +.DS_Store + +# AI config (contains API keys) +ai_config.json + +# Package build artifacts +package/*.pdf +package/reader3-release/ +package/reader3-release.zip +# Allow .env.example (overrides parent .env.* rule) +!package/.env.example +package/*.epub + +# --- bkit / PDCA metadata --- +docs/.pdca-snapshots/ +docs/.pdca-status.json +docs/.bkit-memory.json + +# 敏感环境配置文件(截获 .env) +.env +.env.* + +# 插件及工具生成的本地配置(截获 settings.local.json) +settings.local.json +.claude/ +.gemini/ + +# 日志文件(截获 server.log) +*.log + +# PDCA 状态文件(补全根目录下的路径,截获 .pdca-status.json) +.pdca-status.json \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..33d48db3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,56 @@ +# Reader3 — AI 智能电子书阅读器 + +## 项目简介 + +本地部署的 AI EPUB 阅读器,集成划词查词、AI 翻译/对话、TTS 朗读、高亮笔记系统。灵感源自 Karpathy 的同名项目,在其基础上深度重构。 + +## 架构 + +| 文件 | 职责 | +|------|------| +| `server.py` | FastAPI 后端 — 图书加载、AI 路由(20+ 提供商)、TTS (edge-tts)、Google Translate、ECDICT 词典、EPUB 上传 | +| `reader3.py` | EPUB 解析模块 | +| `templates/reader.html` | 阅读器页面(CSS + HTML + JS 单文件) | +| `templates/library.html` | 图书馆页面(封面墙、上传、Apple Books 扫描) | +| `tools/_md2pdf.py` | 文档转 PDF(Playwright/Chromium),源文件在 `docs/`,输出到 `package/` | +| `package/pack.sh` | 发行版打包脚本,自动生成 PDF 并打包为 `reader3-release.zip` | + +## 常用命令 + +```bash +# 启动开发服务 +uvicorn server:app --host 0.0.0.0 --port 8000 --reload + +# 打包发行版 +cd package && bash pack.sh + +# 生成文档 PDF +python tools/_md2pdf.py +``` + +## 开发规范 + +- **包管理**:始终使用 `uv pip install`,不用 pip +- **语言**:始终用中文回复 +- **前端**:reader.html 是单文件架构(CSS + HTML + JS),不拆分 +- **数据存储**:图书数据 `{name}_data/book.pkl`(pickle),高亮笔记在浏览器 localStorage +- **AI 配置**:`ai_config.json` 存储提供商设置,`.env` 存储 API Key + +## 目录说明 + +``` +reader3/ +├── server.py / reader3.py # 后端 +├── templates/ # 前端页面 +├── docs/ # 源文档 (md) +├── package/ # 打包相关(pack.sh, setup.sh, start.sh, .env.example, 生成的 PDF) +├── tools/ # 开发工具脚本 +├── dict/ # 词典文件(应用内下载) +└── books/ # 导入的电子书数据 +``` + +## 隐私约束 + +- `books/` 下的电子书数据、`.env`、`ai_config.json` 禁止提交 +- 词典文件 (`dict/*.db`) 不提交 +- 服务器日志 `server.log` 不提交 diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..23ba4a7d --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,16 @@ +# Project Intelligence: Reader3 + +一个现代化的、支持 AI 交互的 EPUB 阅读器。 + +## 🚀 运行环境 (Runtime) +- **后端**: FastAPI +- **音频引擎**: Edge-TTS +- **环境管理**: `uv` (强制) + +## 🧠 AI 协作规范 (AI Patterns) +- **模型选型**: 已固定为 **gemini-1.5-flash** (追求极速响应)。 +- **核心功能**: 负责全文翻译、内容摘要及 TTS 文本预处理。 + +## 📁 隔离规范 (Isolation) +- **数据缓存**: `books/` 目录下的解析结果和 `cache/` 音频严禁提交。 +- **字典**: `dict/` 下的本地词库不被跟踪。 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..64b0f018 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Haining Yu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/GUIDE.md b/docs/GUIDE.md new file mode 100644 index 00000000..9ae51d84 --- /dev/null +++ b/docs/GUIDE.md @@ -0,0 +1,227 @@ +# Reader3 安装与使用指南 + +本指南帮助你从零开始安装和使用 Reader3。全程只需终端输入几条命令,不需要任何编程经验。 + +--- + +## 系统要求 + +- **操作系统**:macOS 或 Linux(Windows 需要 WSL) +- **Python**:3.9 或更高版本 +- **磁盘空间**:约 200MB(含 Python 依赖) +- **网络**:安装依赖时需要联网 + +### 如何检查是否已安装 Python? + +打开「终端」应用,输入: + +```bash +python3 --version +``` + +如果显示类似 `Python 3.10.x` 的版本号,说明已安装。如果提示"未找到命令",请先安装 Python: + +- **macOS**:在终端输入 `xcode-select --install`,或从 [python.org](https://www.python.org/downloads/) 下载 +- **Linux**:`sudo apt install python3 python3-venv`(Ubuntu/Debian) + +--- + +## 安装步骤 + +### 1. 解压文件 + +将收到的 `reader3-release.zip` 解压到你想放置的位置。例如放在桌面: + +```bash +cd ~/Desktop +unzip reader3-release.zip +cd reader3-release +``` + +### 2. 运行安装脚本 + +```bash +bash setup.sh +``` + +安装脚本会自动完成以下操作: +- 检查 Python 版本 +- 创建独立的虚拟环境(不会影响系统 Python) +- 安装所有依赖包(首次约需 1-2 分钟) +- 创建配置文件 + +看到 `✅ 安装完成!` 表示安装成功。 + +### 3. 启动应用 + +```bash +bash start.sh +``` + +看到 `Reader3 启动中...` 后,打开浏览器访问: + +👉 **http://localhost:8000** + +按 `Ctrl + C` 可以停止服务。 + +--- + +## 首次使用 + +### 导入电子书 + +打开 http://localhost:8000 后会看到空白的图书馆页面。导入书籍有三种方式: + +1. **拖拽导入**:将 `.epub` 文件直接拖到浏览器页面上 +2. **点击导入**:点击右上角 `Import` → `Upload EPUB`,选择文件 +3. **Apple Books 导入**(仅 macOS):点击 `Import` → `Apple Books`,选择已有藏书批量导入 + +导入后如果书籍没有封面,会自动联网搜索匹配的封面供你选择。 + +### 开始阅读 + +点击任意书籍封面即可进入阅读界面。 + +--- + +## 功能配置(按需) + +### 安装离线词典 + +词典用于「划词查词」功能,可以在阅读时选中单词快速查看释义。 + +1. 进入阅读页面,点击左上角菜单按钮(三横线图标) +2. 在弹出的子菜单中点击齿轮图标打开「设置」面板 +3. 在设置面板底部找到「词典管理」区域 +4. 点击 `下载` 按钮安装词典: + - **ECDICT 英文词典**(~134MB 下载,307MB 解压) + - **中文词典**(~25MB 下载,48MB 解压) + +下载完成后即可使用划词查词功能。 + +### 配置 AI 功能(可选) + +AI 功能包括:智能翻译、段落分析、对话问答等。需要配置至少一个 AI 提供商。 + +**方式一:编辑配置文件** + +打开 `reader3-release` 目录下的 `.env` 文件,填入你的 API Key: + +``` +GEMINI_API_KEY="你的密钥" +``` + +保存后重启应用(`Ctrl+C` 停止后重新 `bash start.sh`)。 + +> 免费获取 Gemini API Key:访问 [Google AI Studio](https://aistudio.google.com/apikey),登录 Google 账号即可免费申请。 + +**方式二:在应用内配置(高级)** + +1. 在阅读页面打开「设置」面板 +2. 连续快速按下 `↑ ↑ ↓ ↓ ← → ← → B A`(Konami Code 彩蛋)解锁 AI 提供商管理面板 +3. 点击「+ 添加提供商」,选择服务商并输入 API Key +4. 点击「测试」验证连接 + +应用内配置支持 20+ AI 服务商,可设置优先级和任务路由。 + +--- + +## 阅读界面快速上手 + +### 基本操作 + +| 操作 | 说明 | +|------|------| +| **划词** | 选中文字后弹出工具栏:查词、翻译、高亮、笔记、问 AI | +| **段落翻译** | 鼠标悬停段落左侧,出现「译」按钮,点击翻译整段 | +| **段落朗读** | 鼠标悬停段落左侧,出现「▶」按钮,点击朗读 | +| **切换章节** | 底部导航栏,或左侧目录点击跳转 | +| **书签** | 底部状态栏右侧的书签图标 | + +### 界面布局 + +``` +┌──────────┬─────────────────────┬───────────┐ +│ │ │ │ +│ 目录 │ 正文阅读区 │ AI 面板 │ +│ (左栏) │ │ (右栏) │ +│ │ ▶ 朗读 译 翻译 │ │ +│ │ │ 对话聊天 │ +│ │ 选中文字 → 工具栏 │ 快捷分析 │ +│ │ │ │ +├──────────┴─────────────────────┴───────────┤ +│ 底部状态栏 / 章节导航 │ +└──────────────────────────────────────────────┘ +``` + +- 左上角按钮组:目录开关、设置、搜索、笔记本 +- 右上角按钮:AI 面板开关 +- 各栏之间的分隔线可拖动调整宽度 + +### 切换主题 + +打开设置面板,在顶部可选择 6 种预设主题颜色,也可通过彩色圆点自定义主色调。 + +### 切换语言 + +设置面板右上角有 `EN` / `中` 切换按钮,点击切换界面语言。 + +--- + +## 常见问题 + +### Q: 启动时报错 `ModuleNotFoundError` + +重新运行安装脚本: + +```bash +bash setup.sh +``` + +### Q: 端口 8000 被占用 + +修改 `start.sh` 中的端口号,将 `--port 8000` 改为其他数字,例如 `--port 8080`。 + +### Q: 如何在其他设备上访问? + +Reader3 默认监听所有网络接口。在同一 Wi-Fi 网络下,用其他设备浏览器访问: + +``` +http://你电脑的IP地址:8000 +``` + +可在终端输入 `ifconfig | grep "inet "` 查看本机 IP 地址。 + +### Q: AI 翻译 / 对话没有反应 + +- 检查 `.env` 中的 API Key 是否正确 +- 确认网络可以访问对应的 AI 服务 +- 在设置面板的 AI 提供商管理中点击「测试」按钮检查连接 + +### Q: 导入书籍后页面空白 + +部分 EPUB 文件格式不标准,可尝试: +1. 在图书馆页面点击 `Manage` 进入管理模式 +2. 选中有问题的书籍 +3. 点击 `Reprocess` 重新处理 + +--- + +## 文件结构说明 + +``` +reader3-release/ +├── server.py # 后端服务 +├── reader3.py # EPUB 解析模块 +├── requirements.txt # Python 依赖列表 +├── setup.sh # 安装脚本 +├── start.sh # 启动脚本 +├── .env # API Key 配置(首次安装自动创建) +├── .env.example # 配置模板 +├── templates/ # 前端页面 +│ ├── reader.html # 阅读器页面 +│ └── library.html # 图书馆页面 +├── dict/ # 词典文件(应用内下载) +├── books/ # 导入的电子书数据 +└── .venv/ # Python 虚拟环境(自动创建) +``` diff --git a/docs/INTRODUCTION.md b/docs/INTRODUCTION.md new file mode 100644 index 00000000..7753e5f3 --- /dev/null +++ b/docs/INTRODUCTION.md @@ -0,0 +1,137 @@ +# Reader3 — AI 智能电子书阅读器 + +**一句话介绍**:Reader3 是一款本地部署的 AI 电子书阅读器,将沉浸式阅读体验与智能翻译、词典查询、语音朗读和 AI 对话深度融合,让你真正「读懂」每一本外语书。 + +--- + +## 项目起源 + +Reader3 的灵感来自 AI 领域传奇人物 [Andrej Karpathy](https://x.com/karpathy/status/1990577951671509438)(前 Tesla AI 总监、OpenAI 联合创始人)开源的同名阅读器项目。Karpathy 的原版是一个极简的 EPUB 阅读器,核心理念是「和 LLM 一起读书」——把章节内容复制粘贴给 AI,边读边聊。他说这个项目 90% 由 vibe coding 完成,不打算维护,鼓励大家用 LLM 随意改造。 + +我们深受这个理念启发,在原版基础上进行了全面的深度重构和功能扩展:将"手动复制粘贴给 AI"的工作流,变成了划词即查、一键翻译、内嵌对话的无缝体验;从单一的文本展示,进化为支持 20+ AI 提供商、离线词典、TTS 语音朗读、高亮笔记系统的全功能阅读器。保留了 Karpathy 倡导的「轻量、本地、开放」精神,同时让「和 AI 一起读书」这件事真正变得优雅且实用。 + +--- + +## 为什么选择 Reader3? + +大多数阅读器只做一件事——显示文字。而 Reader3 把「阅读」变成了一场完整的学习体验:遇到不认识的词,划一下就能看到词典释义;看不懂的段落,点一下就有 AI 翻译;想深入理解上下文,直接和 AI 对话讨论。所有这些,都在一个干净优雅的界面里完成,无需在多个应用之间切换。 + +--- + +## 亮点功能 + +### 📖 极致阅读体验 + +- **6 种精选主题**:薰衣草、海洋、森林、复古纸张、石板蓝、暗黑模式,还支持自定义主色调 +- **完全可调排版**:字号、行距、内容宽度、文字对齐方式,一切随你喜好 +- **三栏可调布局**:左侧目录、中间正文、右侧 AI 面板,每栏宽度可自由拖动调整 +- **阅读进度记忆**:自动保存每本书的阅读位置,下次打开精确回到上次所读段落 +- **底部状态栏**:Kindle 风格,点击切换显示章节名 → 本章阅读时长 → 全书时长 → 阅读百分比 → 页码 +- **顶部进度条**:实时显示当前章节阅读进度 +- **移动端适配**:手机、平板完美适配,目录和 AI 面板变为侧滑抽屉 + +### 🔍 划词即查——选中文字的瞬间,一切触手可及 + +选中任意文字,弹出工具栏提供: + +- **查**:离线词典即时查询(英文 ECDICT 词典 + 中文词典) +- **译**:Google Translate 快速翻译(自动检测语言方向) +- **5 色高亮**:黄、绿、蓝、粉、灰,一键标记重点段落 +- **笔记**:为高亮内容添加批注 +- **问 AI**:将选中内容发送给 AI 深入讨论 +- **朗读**:TTS 语音朗读选中文本,自动识别语言选择对应发音 + +### 🤖 AI 深度集成——不只是翻译,更是你的阅读伙伴 + +- **段落级翻译**:鼠标悬停段落左侧出现「译」按钮,一键获取 AI 高质量翻译,翻译结果以优雅的引用样式内嵌在原文下方 +- **流式对话**:右侧 AI 面板支持流式输出,实时看到 AI 的思考过程 +- **多轮上下文聊天**:会话历史完整保留,可以就书中内容持续追问 +- **快捷提问**:预设"总结本章"、"解释这段"等快捷按钮,一键触发常用分析 +- **多会话管理**:每本书可创建多个 AI 对话会话,随时切换 + +### 🌐 20+ AI 提供商,随心切换 + +内置支持主流 AI 服务,一个界面管理所有 API Key: + +| 国际 | 国内 | +|------|------| +| OpenAI (GPT-4o) | 阿里云百炼 (通义千问) | +| Anthropic (Claude) | 火山引擎 (豆包) | +| Google Gemini | 腾讯混元 | +| DeepSeek | 智谱 AI (GLM) | +| Grok (xAI) | 月之暗面 (Kimi) | +| Groq、Mistral、Together AI | 硅基流动、MiniMax | +| Cerebras、SambaNova | ModelScope | +| OpenRouter、DeepInfra | 自定义 OpenAI 兼容端点 | + +- **多提供商优先级排序**:拖拽排列优先级,主力模型故障时自动切换到备用 +- **按任务路由**:翻译用便宜模型、深度分析用强模型,精细控制 AI 调用 +- **一键获取模型列表**:自动拉取当前 Key 可用的模型 +- **连接测试**:配置后即时测试 API 连通性 +- **导入/导出配置**:在多台电脑间同步 AI 设置 + +### 🔊 自然语音朗读 (TTS) + +- **90+ 种高质量语音**:基于 Edge TTS,涵盖中文、英文、日文等多语种 +- **可调语速**:0.5x 到 2.0x 自由调节 +- **段落朗读**:悬停段落左侧出现「▶」按钮,点击即读当前段落 +- **连续播放**:支持上一段 / 下一段,配合阅读轻松听书 +- **划词发音**:选中任何单词或句子,TTS 按检测语言自动朗读 + +### ✏️ 高亮与笔记系统 + +- **5 色高亮**:黄、绿、蓝、粉、灰五色标记,支持随时更换颜色 +- **行内笔记**:每条高亮可附加批注,笔记图标悬浮显示 +- **笔记本面板**:左上角菜单打开,集中查看所有高亮和笔记 + - 按颜色筛选、按章节分组 + - 搜索笔记内容 + - 点击定位到原文位置 + - 支持编辑和删除 +- **书签系统**:底部状态栏的书签按钮,一键标记当前位置 +- **本地持久化**:所有高亮和笔记保存在浏览器 localStorage,无需账号 + +### 🔎 全书搜索 + +- **当前章节搜索 / 全书搜索**:一键切换搜索范围 +- **关键词高亮**:搜索结果中关键词醒目标记 +- **点击跳转**:搜索结果点击直接跳转到对应位置 + +### 📚 图书管理 + +- **封面墙展示**:精美书架式图书馆页面 +- **多种导入方式**: + - 拖拽 EPUB 文件直接导入 + - 点击按钮选择文件上传 + - 一键扫描 Apple Books(macOS 专属),批量导入已有藏书 +- **智能封面搜索**:导入的书没有封面?自动联网搜索匹配封面 +- **图书管理**:支持批量删除、重新处理 + +### 🌍 中英双语界面 + +- 一键切换中文 / 英文界面,所有标签和提示自动翻译 + +--- + +## 隐私与安全 + +- **完全本地运行**:所有数据保存在你的电脑上,无需注册账号 +- **API Key 自管理**:你的 AI API Key 仅保存在本地配置文件中 +- **零数据上传**:除了你主动调用的 AI/翻译/TTS 请求外,不会向任何服务器发送数据 + +--- + +## 技术亮点 + +- **轻量部署**:纯 Python 后端 (FastAPI),无需数据库,无需 Node.js +- **打包仅 ~100KB**:解压、安装依赖、启动,三步开始阅读 +- **离线词典**:应用内一键下载安装,无需手动配置 +- **高性能**:GZip 压缩、连接池复用、流式传输,即使大部头也流畅阅读 + +--- + +## 适合谁? + +- 📘 **外语学习者**:读英文原著时,划词查词 + AI 翻译 + TTS 朗读,三位一体的沉浸式学习 +- 📗 **重度阅读者**:高亮、笔记、书签、阅读统计,完整的阅读管理工具链 +- 📙 **AI 爱好者**:20+ 大模型自由切换,把 AI 变成你的私人读书顾问 +- 📕 **隐私敏感用户**:所有数据本地存储,不依赖任何云服务 diff --git a/README.md b/docs/Original README.md similarity index 100% rename from README.md rename to docs/Original README.md diff --git a/package/.env.example b/package/.env.example new file mode 100644 index 00000000..8981c7e1 --- /dev/null +++ b/package/.env.example @@ -0,0 +1,5 @@ +# Reader3 AI 配置 +# 填入你的 API Key 后保存,重启应用生效 +# 免费获取 Gemini Key: https://aistudio.google.com/apikey + +GEMINI_API_KEY="" diff --git a/package/pack.sh b/package/pack.sh new file mode 100755 index 00000000..7ac0d4f4 --- /dev/null +++ b/package/pack.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# 打包 Reader3 轻量发行版 +# 用法: cd package && bash pack.sh +# 产出: package/reader3-release.zip + +set -e +cd "$(dirname "$0")" +ROOT="$(cd .. && pwd)" + +OUT="reader3-release" +rm -rf "$OUT" "$OUT.zip" +mkdir -p "$OUT/templates" "$OUT/dict" "$OUT/books" "$OUT/docs" + +# 核心文件(从项目根目录拷贝) +cp "$ROOT/server.py" "$ROOT/reader3.py" "$ROOT/requirements.txt" "$OUT/" +cp setup.sh start.sh "$OUT/" +cp .env.example "$OUT/" +cp "$ROOT/templates/reader.html" "$ROOT/templates/library.html" "$OUT/templates/" + +# 空目录占位说明 +cp "$ROOT/dict/README.md" "$OUT/dict/" + +# 生成 PDF 文档(从 docs/*.md 转换到 package/*.pdf) +echo "📄 生成 PDF 文档..." +python "$ROOT/tools/_md2pdf.py" + +# 文档(md 从 docs/ 拷贝,pdf 从 package/ 拷贝) +cp "$ROOT/docs/INTRODUCTION.md" "$ROOT/docs/GUIDE.md" "$OUT/docs/" +for pdf in INTRODUCTION.pdf GUIDE.pdf; do + [ -f "$pdf" ] && cp "$pdf" "$OUT/docs/" +done + +# 示例图书(Dracula — Project Gutenberg 公共领域) +[ -f dracula.epub ] && cp dracula.epub "$OUT/books/" + +# 打包 +echo "📦 打包中..." +zip -r "$OUT.zip" "$OUT" -x "*.pyc" "*__pycache__*" + +SIZE=$(du -sh "$OUT.zip" | cut -f1) +rm -rf "$OUT" + +echo "" +echo "✅ 打包完成: $OUT.zip ($SIZE)" +echo "" +echo "使用说明:" +echo " 1. 解压 reader3-release.zip" +echo " 2. 阅读 docs/GUIDE.md 了解详细安装步骤" +echo " 3. 运行: bash setup.sh" +echo " 4. 运行: bash start.sh" +echo " 5. 浏览器打开: http://localhost:8000" +echo " 6. 内置示例图书 Dracula 可直接在 books/ 目录中找到" diff --git a/package/setup.sh b/package/setup.sh new file mode 100755 index 00000000..a9c09c8a --- /dev/null +++ b/package/setup.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Reader3 一键安装脚本 (macOS / Linux) +# 用法: bash setup.sh + +set -e +cd "$(dirname "$0")" + +echo "===============================" +echo " Reader3 📚 安装向导" +echo "===============================" +echo "" + +# 1. 检查 Python3 +if ! command -v python3 &>/dev/null; then + echo "❌ 未找到 python3,请先安装:" + echo " 打开终端输入: xcode-select --install" + echo " 或从 https://www.python.org/downloads/ 下载安装" + exit 1 +fi + +PY_VER=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") +echo "✅ Python $PY_VER" + +# 2. 创建虚拟环境 +if [ ! -d ".venv" ]; then + echo "📦 创建虚拟环境..." + python3 -m venv .venv +fi + +# 确保 pip 可用 +.venv/bin/python3 -m ensurepip --upgrade 2>/dev/null || true + +# 3. 安装依赖 +echo "📦 安装依赖包(首次可能需要几分钟)..." +.venv/bin/python3 -m pip install -q --upgrade pip +.venv/bin/python3 -m pip install -q -r requirements.txt + +# 4. 配置 .env +if [ ! -f ".env" ]; then + cp .env.example .env + echo "✅ 已从模板创建 .env(可稍后编辑添加 API Key)" +else + echo "✅ .env 已存在" +fi + +# 5. 确保必要目录存在 +mkdir -p books dict + +# 6. 预处理示例图书(如有) +if [ -f "books/dracula.epub" ] && [ ! -d "books/dracula_data" ]; then + echo "📖 导入示例图书 Dracula..." + .venv/bin/python3 reader3.py books/dracula.epub 2>/dev/null || true +fi + +echo "" +echo "===============================" +echo " ✅ 安装完成!" +echo "===============================" +echo "" +echo "启动方式: bash start.sh" +echo "然后在浏览器打开: http://localhost:8000" +echo "" +echo "💡 提示: 词典可在应用「设置」中一键下载安装" +echo "" diff --git a/package/start.sh b/package/start.sh new file mode 100755 index 00000000..656d613b --- /dev/null +++ b/package/start.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Reader3 启动脚本 +# 用法: bash start.sh + +cd "$(dirname "$0")" + +if [ ! -d ".venv" ]; then + echo "❌ 请先运行安装: bash setup.sh" + exit 1 +fi + +echo "📚 Reader3 启动中..." +echo " 浏览器打开: http://localhost:8000" +echo " 按 Ctrl+C 停止" +echo "" + +.venv/bin/python3 -m uvicorn server:app --host 0.0.0.0 --port 8000 diff --git a/pyproject.toml b/pyproject.toml index 31e61793..35172edc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,14 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "beautifulsoup4>=4.14.2", + "dashscope>=1.25.12", "ebooklib>=0.20", + "edge-tts>=7.2.7", "fastapi>=0.121.2", + "google-genai>=1.0.0", + "httpx[socks]>=0.28.1", "jinja2>=3.1.6", + "python-dotenv>=1.2.1", + "python-multipart>=0.0.22", "uvicorn>=0.38.0", ] diff --git a/reader3.png b/reader3.png deleted file mode 100644 index 45aac09f1f007282f2629abe019da127d981952a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 238611 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

u6_ak3N4k*;`=Q0q=h( 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

7j@xa}9ptl?ZdJv^WZHItc|S4>Nr1Ecw? zram2cpjh$!5b)Ab5&gupy|-2 z%jnMv4sKa24w7X3-vMLQY`4H-6c5=4MITPS+k_+U!!LsgV`sPve`=o#C0Qqm3=kg; z#Dq$|E9w0ZD2fFmB>E_DDo+9DHC&M}PH=|;rIX!*cGB&B(vO;?B{$-#d}^YDT;< z;{mr}OW1j?tAhz5mwd8#UeYEkm^EG|ftavvWJBwre@nJE5*0T$v-deTBHLFBW&Y01 zcA;YWr$kzJQpog9Uz;I56$6F{PZM=&41PR%?x0}g41o=&t35kc_ zLnw-9vE`;2Gska&4%&g+Fwb!z1}|8pz@YeAmd`56cY`}6e<>l0Z5oo8={ZBuI0J1( zi3$oS;p)t zt&b?E-sn}WKVtnL76Or^O4Cdf^_6%Q%8BZ9={Ow zy@C6^*RWlq+2fJVTp*m@&jz7A`OCq2Gh&>!maa=HYdV>zrc+f6x-QE%b0Ygxw!M&4l74=U8}WA8QEpS?0%Wngd8!}|M-23%fYS2rlU z;=d?K_hk^9B>&Ckz~YTzDajQ@_P7@1;+AE@;~iuCI^$Igy2?$w=R;}&DTU? z7h7~YlVF=JQ*3$bW3Foa z?)m*K!S6ut447PM*)x%ns#rai-$mMXzm1NvujK=am084wNK?(Y;(!NL)@5ma4x!J{ z%NG1~nU{jbgBtAs+3Gg@J+&!Ei=B5a&YUkp0AUZf=*NnzmrEzO^tiMtU5Q~0e{j#B zsc+BfV#JS6P%Ce~+xm2)>$QxP%xn>lmquQ?w=0oP~Y%2U>y&Y z<+JtGD4=gNj|``tTzP58)7@&6_w1oLd#V>mP=j$D9Z(4s_B(zUxRDaEJ=Azg);Dt) zj?OQ_bUncBQNy9l?FX*x0>g{g#=kOw2lMrDho5t8)DKN@z~P6AVO_%=B+bhL*g@1U zLQqoDw28RdQ2rI*P-}NA3ugU~(C-K6&Zegu%>th_LaSSHth1LwA&=HTG!QFpJsx!n z-P~;G_8VVaB1HIUR{*6t+Dp3?$w_M7{cBI&hz&@Mba2uOU*;~}OFsnv%BO)ViR&fn zY$aykgRE4TtOe{1E3V{9AT>RLq$^aN*yCx^3dunui)=`?uUyv?NJCHb$sfRGuJ zP;1?@3D1_7`<#pfXl3$fHxA9yyHaHp(E~S^;tOZke04-&8sqLHT+MXq{08e`7$J$-j|gey84f&$t-r!0AW!yyFMN zpFXs0tm9Jy#+yibUV|p3FpN8zz>=PmSPB8viE@;O|K?Q7`r&4>V1YGAv{x1-OB$8S z=852OS{vDaXI1i=v%1;Z`-3QU;!stGk-bEQ{;jOk=N+YOnoN6=`WOfCw27RzmYz9` z-)JZwdXO7CBrycZynwfJ&k`&iN`{}C9S34Vf_IdOlM2B0KE&H(k7xFG^IRJ1(_O44 zF{7i|wOe@)7rj<-b`l_Wo66EeFqf?}tHRR;MVH70$hq%})owm~%GqSp26yZ`SS5r7-3dSBi<_S{6GrCdW9*?V=%9(3U1Ke0N^ zH`b^Xg6Z$9h_@dRcy#yt(t{rL(w+I$?o)ckJTi~CVWCW+0JIny0LVJRj+P|gn2t_) zYnuXYZP(EXw<_3bQKy9#;ierAK>x&ef2Z3_fmaxxWeJV*9m`GnxiUGwNulW6k|vJ~ zMh&yz^z!@G<>E&+SxKr!(z(5LF;^a2^WiKji|@tWDqaUb1!qA_UnA6qi3a`r5v{p{ z0pttiCD@$9EX!RbIFzu?kAm>bYxHYxHFR2EwjZfN5$RL8&mO)Cu1slrhS|cuIC!|dnBjBREqdKWLu-V-M@q7Lt zYuG8AaC@H_c&OV2C5u&D&@S|%D9JNd22h2=RiP20*9=trG7*h$J|W(P!EMZTCwZy6 zf4ZmlQ6eRGMD3_2;X`lM$P?9aK$|~LM@55VX!qBH8M0SXGuaN9vH~$DVOf2)`%ELn zA@%}+-R6=1)8Hqx!MsvLMgh|2OJY;F?}GhZL}1)?dtN#qWm6X1{IVW{o6bx;G|Wbv2VLzT8y( zOgZy|;I-HIAU~Ep!YZ7Qmv)d%JK%Q&0^0ZGiU_9K=5VqAv?Me2txt9PwH){vLdI|~ zo`|kah`DQ%Ek(92)=s|ZDV)y8G5*r0h$6h@wPBG92!ZfqH&-=G~x<&Qi z9r2|$?u9fvS}?vu&C_|;`P-tQPlfd=?tARa=LndJ8geKjRvB6}rDz5hGOzvfv~hoj zxD6U+Uc8bWk>zZ9>yg1bWRARrNHeiP6YCS*R8JT2Uag)9l{=yY71;p5YRZgf_m58Y zYMBEn?l#PDCZOt!LjXN5FHE(Rnx0!50Wy#kI*5Ezv~~;1I`C?R;xG{Ui(>iqEM$&> zw`<;#WTLv~yWGZ~&fimaGIyGgBpfl1ncrldYsqPDbWb(2W~~myWn4CA5H`PV9 z;-T~(OFa54=}whYsP$ItKJ0^Q&AI=igAK_P$v6gZ=*n(5@tgMHr&DD^;NB(<{n@eY zz5USw6tud>RJ12wy@n%c?D5gx9fojJ?Am7T@KpJ$ta=FgdpC03_0d`6m+wN0=j`!( zeZXuaqKNT6fC}ZSxhKc{7=rk-*>QEZG_I%&?E9fcF@ECrg-yaR9;B8Xn9nleete+- z{|Or6rEMmK5me8i~9o6yS=NV%2@^~$4R9MYsv^Aat zT}kn3-2;7ByW{_>lroB!DDf|~golqo!`wC8x|!{f|6gLsI85@4ZRF!I!_?eW{niiu zc*VlAArW4AV;Ife-o)@%yJ6c>f>zPwMeR-vo^(Q9#T5mgB~iBTgMNsE_TshCCQ0Kje}XI^cMZ@|78KFFia8j({-TyI@G{r6DBo@rkQ-xMo? zrQeKmxFsAz5sEt_&nsY#1>-KiOTYpRO!Mk31$4Klw22~YX~}KqYWqs^ia#JP`|HZe zu*i|hHJ?KZN4EBSroG+9)Hy38eShp_QxpHt#n$b0ZbMU}447nD#a+?NqsPYWa{HZ+ z+jfFCyw8(gUG0+jJU=@mn_rPq)H2~uV(??o!P0E&)n~WDi}ur$uhH$vxew{X#MxhQ zHkkfL3WV^KrLU{M^^|2;p!&}v`S1Af|NB9`_8;>p+R2)|*?|M$P30agR>(45K> z&{H{qt4h5`%On?RD(Cz|&F^MTGW~;Jz*GuFc>nX4^bHVr@Z!Q|;R%^}>4dm`8fCiM z3~8HvoaKff|Btox|Nj$$*#WD{=w0xOvz>qH4@WN)hzI7^`lCEP8LFUdCAFnbXrQRw_x!drn!U<_z|XXC>^pRjNqWzAd%3tDz)|yGh0=*Y92O@ahk*w z?+HXWdcP?0u4B4WXAntQq4l+63tpWE&wr-NSPk^k?ghZIAarf$Y_E{Nnyrhg`q==gCg3xLCHK zHQCKCY31#81K1 zv``46MOkofmK!Kfik?O?xBvQ>*!gSpE7;CB&T^6!{X@ps)AI40DV0m8{HF@|eTLi2 z@a`My0=bwET4tR!%CM#YpAx#@_gN%tswogj%8J!qZXQ&vv%+VMbN2*628#?z|F1w0 zdSTqdL;#x8N6Ck=d(?W|?Rj`Ks!}LaI^F2s+InPD%9yFpVP9pgf+1V~WOPdH&y;X` z!pBo(u<(b`RI5;RU*PF>>e+55G)XQdMd1PIy?qhJro1;F1e;Zgvix;_uI> zKPGtDuj-K!mp;3h>4{pBO27I_p(}O+UoOU3r?@v^SfBBZi3UM|Nt)=H)q%J8>z-bu z!>RiH@{)$F&Lz~I)9d`wQRhme!z1r6ZKMok_&D2Bwnv_2vPrOr3~B=)1Vu-FCOPT@ zc?QLX0?LvZV!E^e8-}8f&DP1hzDNh(_XZK0|D6QX{*b)?6o7_puI*DDtz`}U(zPI}?hP~o1hVBCMG^}aekOU%(L7BM|X zwevqR#K7%o34f*rnrIg)Y*2^M=sziD2fw~vy36qwc_}0(vn=L4t!-5>U1mJHpj~cH ztZS6NA5QodccRHDj_G@m{SO@plU|bM?>Mx@*&@MajpeUxTYZjRlMW}RMCiB^{jaaE z#gn{6HV6&Yf?E=$6Y8u}Rrr!aYx-A_YBodX?LS`IjV%tXMimZ{o0zh;2SYn359xt- z0*TH9N?6^S|L$}{tH{uf)I%GncO4b$qTP+=A{Gr-*kOw@ zTQPGZU%&kHJE>&KW-QZhrlU}mQW<3vww*a_skPe1FVU$B{kzd$tFQ<(6Lh4u8euPX>*-V6(fzA)Tdq^{ivz98>CfEI`;gnS?M4_t z?7ybP|2tq_h5V1)l=BUM-!j7=;5>18kGmC_E?}p${2?n}X+6Q0 z8*Afa=K0|lj(%HzLk#DS#2bwbecA?tCH&5cJo+ppw!_cIQi`%^7|S}%ti$>0hX#WI z%^khbZT*`{;AU6GMP`TB)_p=?Iw@R+j&Cd5PG6;qxYy>`+yu^0$6x@F5;FhWecbgJCsiqdMua|p z2wW!#Bbxn~{JgGWJ44v*M{U8kMSpj)U)nu+3W^PtyzhVOFdi~g>#D;*?}#Sx%C*^~ zI;-)oH`#bJx`V4hXMG`C$g)S~Xu28BfJCs+v2h;NrY`u#qUE%;EZ;Ek{bYrcovEG% zO9nR8v)T)b9e7>7cUg*-68BD3CrBrmluTLNv~f)dFtguVb}s!;-ez7wi(95&q1JN5 z6wkJ0adt9SvI1z8>_7n*TJ5??Cd-WN9A@fkUNh^JUiS$1Krw|^{i;CMeG#o}GTgfH zIw`L5S3i!IB@=oBF29q+AG7r9%&>KjTWK zPnvBM=F+B><$k#q1F@Cn(OVL`npFN_>KeOfMZ&FmhU9 zsis#4_xd|5H0OfKzlA$hcIxunU$$uJbV`PL)BQwQxeETIi5jm^JVH4*{i*#TWA8sE zwJr(^bVGSD4y~+b-}GaQpcR(Q_Iyt@pNhj*9wzkOzD64?PhD-c%xj%V{TFu@|D&Mw z&AR(*UlJ4|g2%MYOY`8~fFnF$o9yBQkkPU!~e?g7rY-}>!y_W5%LK2Oaz)^o45t{dqD-u+nfH{&GDR?Q2}AbMvI z)b8U;I|AAW@=sWU_xqV#libfl6>@KouKA{?Mg1o}?oA0R`NVer?9cuW&lVa%;|J(5 zz+9FI2!7a$I#HU}f035q)355((?6?8!1;aSw|Q+<3vHBpJm|dqpeb7MJOX84hMxMV zhIHP={h}~qdGIbr-T#DuE&o>SA;?feFLoy%*o}?zvItsPOO?G znh7ys+$_2}gYAbP0|Pt8h4pfM8$)7pGPj~xvQKKo`W}}HyjX~CRnme{s| z;*OKf1!`+soRKaaC>ucZy4aj#HU8b#Hu+;{u2W9hBIqB1hnhdvJ2{^u{%#IGTck?oog$Q1ph3+n; z+c)Xoyw%ezBZ92Hf|WYq9Bb+w7Uq*+Hip+q_G>OLCp7{11{OVs%@;VP9Xf|tB?hbI zi(1v1rOw7vTZOgm&q&MUj_2{OjcyQ={z#sqmrGoR-3pJjc(YO?qbk_)mrnjOJ$(n| zU))#{FxhM_N#N&Glzd(F;LsJ%eUZq0C^cqigEo6U5<^ZdKE%ze zZxGAYw%TcZ&`GTz(YM0CT6N*6j*kNNV^0Jh`Y}$po=M^BbsfPiU)NYG58M+)L?~w)Uo;7Q?t6Q4`zb?cNBd{CrA+W<)MdW&*g?7CtM)I?2B1gv9lV_akbcLy zPon4<8}>2Tsa@d=kf5h`CHZ5A{oNKm`lqgY>Q8q!jdlXIYUB81PsSkRk$>MmM2HYD zm+FCX({(}vAyhCXC+1t7j#HF4f+K&5Jhecm1>5FPzq})LZWqTo%6*5ij(C&YgJu#O zYi1H)C$)1w(|I9n;QEIP2)wpAZk`^atoOxY`dn_SPVhNR7TV(p@?I9gx&&ylz*IIJ zF+ZXlWT-jBaf6o|rhgaJ*H+yGfm=|c-U!x=bkLEzQVIRy*1ZS-!I8pB&edX>Ploux z*^%>h6x6(j-ORY9vWy*=;qwrbxvM>go63{49SUGz8`P~9_0>GY0Lf5v5UNvd`1cOMk*GpBM(KAjk!w z`H~Kb^9AX_xn^)L?CkObv$7P^1{V1rg9v+vP0rSFlf$xR-1$>0!$#~JeXVd#@>lDp zmUUo8-8aR&`ITn3Pc}20Q66^0vF{+nLey7Y&jo5pRA}&cTk*Rxv~PsFvNm?XO5k;M zhq%amvj`DnPPhp8_vX1Uyg6-rg#WG}oO4JT-9@>yz;p%6xL55H!?Fuuhko=qV(u0J z2`2RRD3C|b>=DAw1){fPjl0npFI>5}!v4N2ff-S}HCVGeduN>)m8@Xk1$8xBp1Nwo z3v5cKa^)#B?pK9VV67eUQ2RO@I204wnn_8(lP(_@7JO|AoB7D1UV+(LcW|#sBZie;%P4?=L40&!3X- z|9y-9^;fsu6n&nIvs{}(4O7cc+Kzfau%`P60b_Y4Lw zKX?A~2{uIkQtXgFvq%yB$IuZjp#AR|+F{-g{m&u){Xks-{(m`nK?V`r|Ge{m+^^SP z5}z0Br?~$puR&1~uuz~KF-kkW-G6?{2)BPpe7A7Q)X4w&5|QEHM6_V5p|lEk*wAcw zBDjfcc}<9s)Sr=)Xa|#UX`4w1E0k5=t&<;QB(zc_0>FyfleMyX7P%L9u9d z;xLOm38TCi7DMaW99gOO1iR%Dm(cgj>dKOaL6z!hWOQx%SJX#=jS5`}UGBSKo`1o9 zoOIHIb>S9E8}Z%h!%aYnDEC5|@Dk!#08qr`2c;bw@Q383g?w`Zu3--{Q!~dm_>+A)c9jAoGW%tN+S$>)W*`YT?79*{9SFF@BMsZU7}T! zx2X>0jxk7QpLJNM4#Ud*fc6){hQQptxhp~U^&NXo!q}mRISt^AzH5H1%17THU&pE# zIQZ7)_o;m);ni?9b%&w7VjN3)AQ?iM%}BKWe>3lVUEC=9Jq)-RI7HR;w=6uB&n0OG zK+$fUIILQ#PFJGa6!lkVfhvV93M-|63Q8!9BQ^|&c7!^TMX$LU)jag(kmBQ)1Xft= z+&|mUcmLQt$wJ^vf35L04ele_FtjSev=5o=i%O()$}-q2Jb_MMhu__j_GrE$zG=-@ z6Rp@$OF4MSZdgfp*XFOG_KKp_baSk?uuY>rmFV77eFeRI)x#c<)lxxhz2%zxn)eaO zu1o5BwMTiWrc!L}GV`P)yYlZw!r}StD8b<$n36Wn+N-{Vey){#C|1t>?wwzcElB5K z+1@%`1VDeVUBY-Ug&^$mL?aCm@6Y{hqmj+9&ng{@dYTpXV8%My!}(8^`1TA1LJ+qJ%a%_g&gYD zxu`#!$iOhsc^-G!30Q7Wux5yc>Y?@Ac^gK=5wmQciE;;;5srA)y`__^mcwm`O-$Mv zgKHxRxvOh#p!P59dNtyywPr^jtdw7s%WSUy)YPgB7&ce~3cVkk*{v=?+`=hf${&-y zK~GRB*wVR|_>mYY49h1DUI(c9tzMxs_uh1yRz4o{WNBEP1nt;vt@3MWgA4f_*Vh1ol+%lmeEZSED`+-{}qXUgYoJ)U3v4R2AjLuYv!|e zrlG*s@;X#&KE^X$#SwAx?L>vDke_kMJ<=D^S$H4U-6pwt(k$bVXd>XfJNcl}Ct^sRu3$7qx3yM>w|vfKS{?AS;9 z-rCXwN{iu~P=6Iwp>7+E_gyIeI+|}gjVCBohhaI~4Gry1HuU+}9xd=UMUWd+C|CrO zL)yOzRhZ(Pe%e@^=vp?|`2=D3>~f{Y#@3SI@KeoDu$Q}RYr&Ce&SxYx*ss2cigL5a|x-MGtdMf-5iaW zX}*b5SlR;^R>g+1v`Oh4@RELW_S=16c=&b3+|;;FjX@9#&HEmQqBurhj&5pu&&@3i zGBvFgP24cezssVU5PAI$zFo3`GNP)6JF|UtpmmJ&*?uoAU|!Q77$p4aP*B|Zqitpu z6Qi#o%0_@;QCD5)-{zB&qAzWDH$XZ6J2EQeGoDi0MSYbiWCvc>o zoN|#nK$7iDJ^GbxDLyP@#-ob&u@(v|eazA`StKz@pzwRVGIQQmXey5L+r~OwkO%*( zF7&_Sx*g2F%(^Xaocx8J|RP(+5oCfy<$dQ-UIF(#nmfCk?+#F#iqcQL9q$7pY- zHy)MrnDUi?;FfWdUsMddZ%6>l^6__uJ`#i_G5U&#uQYaEDU(_tcLm_{McbH-KNO9W zk@sB(xSgMd)?|_G)=KR3O$lcnt5QR9sovIpw^Y6I6Eruc7Vtm_JPrdX7@1K1l86aY z*whCT&$UH!0$75l>G630u(v}3Rk3_7`$Q96`p~ow&Q})54KS{77-lKfnNUfA{zA&K z_-AOm;(>9G7azcJgUM+e5pRUd?@11KR3JqX?39nh{-;z9@JCp-sF)2#`LMzwk1ts)UGhC|JELw~ z*|-U;u7W6MhTF=Z@rf4nMo`<6%&Pzua;U#-LE1g`&oekV9rov3__)^x0 z_M4gdo1dD;%&kyiht9~w*Btp?q&Ec?$&E!DFf6_53}5L6cZSr;Pf>Abt-OaD$kIZK zjn-;>Z0i0@H9<9bHT$+J>xYH-SCzBxZ}xnnRr5VM75B!JRogM4^`)Xq4OVo1&3~G> z&A(U+T)d~#1SNh2EIO7}Wx<^ZUiky7l-aW2k$G&P)SKC>@Vbcv%0l!KRhr--O>{hZ z^H3d)IT_%t2o_Lbg54A+7rw&zv;SFdB3D~vrDe(UX?kUHs}CBC1t*= zo^G5<);;ElXOH4uPOBDbyeliH&8bQxJi1K1Op(l_A9Ne-;|aC73f8;ocBu48#k%sQ z_wP-1qK=s3Nmn07z(90HQo*ad@_UinafC^b_0u*(K>{%8z<{F=gF;O*UR9M-nl9En zZDjVIQbHy+cW{Fkc0-egJ8|*efY%*Wa}wEm#Y;el5h$PGgQF z)>s*g-pA~pwK?l(*PszWanPtT$S)!Sm6-R#5OD73`v|u&3ZHB*ex!aMSJUe?1Wisr z-82-M>MhxSo2H`CzIEGvXX}2lXmi(+XfRTAwuxnA`0g|)T|1;S;dI)9+x6aSx@91} z)Sz`P=-oBb5<-W4Z>P_I;)Ct=f~tNt6AiMUfMXr+&(-ge;bWYe=D0yIqyWDkFBdaP zZIRl!(d~;4{t2H9v+ zd-&V0!C4FSXMIpSGZTs7DRsWct3+v8H9H8+H!QmPtysZpEH*5hU(m(rJ>NpX#hn$j zf`gvCU1B8S>D@)^+fYty8i7EhjzYNULwAMeLFAu%N5G}XK_$T|!=rsS%3)|QY_?ZF zUG!eJvG5e}yMk=kGswnY{}Y z`2iMslOMN!#m*G>qAVRP*OoIO9hu~H)+VAYGwkznA5!gq?i}hj)M?-?mSo*d;YaHJ z))x*A7AkItl9OX&-&c`4jtaxv|ENprbUgTCB9W;=n-QkpP`bYuO1j6ATzRdS$}O%4 z=!S_P`s@#Ejmu=x{ZO}WLD_S7iB)=wn*Z&=EIB3fc`#apW7&a*SYA+Z`g5)MXif)G z(g=S;tY5B)CV}9!KA?rg8iP*$)9y0$muTz-)+jGG=QstxunIiL_r&m!rpm)-D#duK z+o6WC)ApBjo#ucr$=>T;x_vc2X@opws#dH61RoELd~&c#7?afYrg<@5Q=qE0955%R zK6F*=$y>HPEAlZGYo(F{lme$-TGDuMu>JkraJBRGkE9+?08gmaM;bdHeWA|$MJ?cq zA{hB(SjcfR11yGuy%HQ&9~cmJR(Z@fcW5_xOuc|nYGb-<$rGI zSUdl;tt4D0OMI77@LccKRut}mcQ^pS)tx~HosVMaa5Zrly=f& ziTLHJtIc3)ToQ>~W>|k=j9Y3LUkIa_xTU_Tt3F*mn;kX&nI{0uz`_2IV+kTDpUyUb@DT-mF!V7)?GgiL3vxX( zaaqz&^f0uqn)?}u^VtXuip4;bR~5#;s6jJM8@j5xO&rkSOd_guC_aUm)%N_1`# zBQ;j>Eh-}VekzqyHh7d?{g>rj{a9P5yd}ipv z^ndg8i~@T9riL%3w7;kL|8z{ zKCmLxCZ@j|NTHHyyJ>`~5IC6$XEKxbhM4duNTpfa?- z3J@itBN~fGwMK$95f!jWW-wMH^jjpl>u|7}u?QClae%riN$U!I!m|_x0t$IwY`1?+ zD~03AC2UEe$wg;5q#z+Hc443PF}#mg5V%cQec>tUEW?mt_azdE7!tel;vSQLWQO^5V7mY{k3lH}y zj{qh+(@;zA?iEq)Zvb=k^|k}T)sK+Answ~kv{QSG>s|iNkN(XQ^_hZ}}O&$7VxB&XI{XtzPp@XX}rd+-1~MAG)P zKTKEV6O#@biyvqoN5jq*3KK7~%iu^V^EZ<|K@brBcXpm9Q#sla!t|4q&f`8~INLwe zwobF@+HoDF)6?Q$;xpMYr8d_P!IWO|p7*K{mKCyA@VRoOVF}Cg#AB!Ms_)krhL)Ic z-K8Db99Z|fPnZ8~DwbNVPk08WZ^oRKVpO`E0LwQs0z}du3Z)AzG5x$5k9KLa4|6GCs`$B1fz z`Z^(AG1dq9Zm7HkefL&sL3i1RT*Z*Kq-5UjIkw(2uRX)CB5tMf;dAjk%dG5XA)bD5 znIys3`>in0a*56!o;iDf(XXXEV6KYj6C}-D%u|lNG$s_lMNFkiT6%tOnnS6V&(wub z=~}YYs;V(LT@SufJeV)*)C2L((4^S7D~S4txD?G4j?F|5F)8S182~2AOs&dApPl4e z_K24ay>af{y}w<79IN>N**D%NJohX{(grbn{Y&ydQsK9^w ze?i~-$4KK?q%a%6i?}cpqeWzXam&Nm`HBzr)fQRBK3i)a#u^L<5S4c_KZ<+aaKx%T zIb*g|Krt>DNySoV)-Nb}N#l>>wveOwc0J~jWo3f}-`T_5Cxt-?LpM9PAg=wAB$@A9 z0Ik>a5dF&A8yZgb+<3UN3DNL3@3HTtDZ9SGT|`xAcMNL0=D*2}&X?u(dBmVItf^fo zXuQLlFQ>S3_a4gY)_ZCe_}V8ueOy^&{?a#mPw8_8H6x|+SwCiis-y8}(VBEKuUVY- zvxDxZ#Re5=U>@(3Q@G1u&X#9xlB=r3wln*p7q!Ybs22#Y&Kn}RckkXY`qg79YKk%L zu6^r$Q`F$zR&+Mql3($SMMG>mraMsnk1Wla)KX8%>}l%$u@}nr$goHnl0~sQ{)Ks| zM%N9`3N@59-^4cJ9eoPE!o)d_(`?dgE2~?GHtnC4Es71H`u%j)rs*L9G}KvH)g!fk zh+$l~FuzVVlx=gj8zURKuNo+h$t_SX)(T)8MaVH|FFPitZ6M(Xmq+1cPkW!T`SDuv zRzIP+N&dc94UJ-#_s~og^7-jQi}vA@k=01)m0Aaz=0cf;)5Ue>BYRd9Jf!O* z2HhsxhGxrM0HC6CLikCKxf2`}%r$%8XAB9A;>_Aq+x2lX4=4Y=s@ruSYu1GTV3N< zThZS1$`L>|ok-2yvn&6o(hlch5Fd4y&zE_XlnOPnh7cj)mVa6}8bml98X%h&WVdV{ z9K2g+V|hpjk8k^Mw@TaVnut1F9wD$>qrdX=%r{1kxplAyP)6UHuTlpbmZ}O;uZgo@8*XSh?gbrpISf0Z-xmN!9WoW}-&Dl=7uS^&yL~z$DWxEUACvgZAL~|JXjmt zwidN=SOmsTDqD&=Dk6Fz+upZHy7u!V@F6tmG-DZja+zuUseaSu8e%Npdsf>#oJ!pH zE&%sIK?rT;jB>Ka{jixOXWV^v=C>Jq)3tL{{Schny2=>whpPbsv)3Z*e!lUuG#(q8 zv(DE5R9=V9iW)wyzLJxe?z5DbZjE6s;pKP(9|icQTjRX**?1)xDC4({`vyPqu#J2?7gqKE>o1QygB3#@<< z&B{0>;b*;$Rj|0wnBS*R0B^ShW+>34la!VNk2zk@FtRQzx?-MLpLyb%u-xFxK^f5B z!tQzeO*ix93qVrE&Ee^KTUDcJCh)-Y>rkUhz1Iv@x}99%t_{y`>o<)lDV(giK_%vA zcXEf^tX{6*nfNu-`5K9#chi?sAlHFFHz!bNMfQ~4`8c~^Dn^6vA=|-pMH~3iT4{B- zI<9ZnslH)ATocG;ZaDRCI%!xF^rhW&n2rTJJCrg5hj=++k>8Odt5!6kq}y1+bU<6$Xdte6SZm#Kv}5wi zWx13r=;U3M<`_y4JZrs)IZJ%qfUjK|>=hn!!l@7P^3M;Xw~Ne^_yorSH|kdSZGfF1 z3EH&^1P+3Ks-SOU;FbKU2cXd$v&A?uzj1b1X%+I$FHM8wj8eDn=N3IX2SrkPWozdsV8`hFM8mWeY9P4TD zImBfeFgN9O`^UbA{j^2^qfhHfZ6aF%zgcu*s`DO3K>j?2==<%CnbA8uTmbsS{2#26U$im?xn3)9mNTxuar&d(VwQw+g7E)h4y$`No0n}_=s@)1!_mT(|oeh zp!PG7J{?jnw6Yy+@-Nlszcx-kCOF!Xz}oxEJ+_2iX_CF9u=mTDObe(e5qM9tcvH9v zG_c1*|uF^8d|e)tePy=}YF zEbD=y^|eeB1*2An-rHVv2fI9j~ByQqlZiz$j7q^;4oAanX&MD{xXv0&Q`PT{y3AH@Y~C*fHuvJ4N$*HAxk&$Ys$H!JpDC znsqZF0Zn&ihcO0DzXEXYszX0waWek_T`RFVzd?Pp`{3p&llCop&`1XX#q_dRh>;Lc z#QtS!y83Y7@Z+;h`YE=PjEAUbI6STH!{uIs#pu(H&l2H1&#OPq4Mfr|zB}oWN!23y zT5cec7p%t1+Gh`($Xb2O!208rc*E0E)!9Oqd*2BQYw?pHkYksDruWfZleS=CF{NO& zf2}uHJakBbXSLXR*M?ebKtAP!Xb!_-^mmQkcDe7CmX|q4lJGg86Rf9x#7WcS@kv(1Cq#`?k zvR)e@FD>Ew*zWh;hy=$)E)zm`X?{@1c~hN(8|LcEUHjcyJkp!3&shBm{lMXEpd=fO zS|vsG(PT}0TFh8tfCJW#=#$uekxwCZ+wu{G(|~9VyCP z61d7V*uqM6O;o^ci6lCG_7=fuwMMeHtWdel(tt2U0)^d@h_qk`{mErGpSV%b6zkEF z&nfqFoK?_iyPh6_=;iq%utSf-Do~p#$KaW6x{)cOKA4}1n6K)xW$32ByC>KCp+564 zxog><2I(dbQ~t1+$1XRjFQ6D5_>}>zXo@?Lf{~P|MR*6XH_dS5(p^<)IP9zJ=V8kB zrr=Ab#y=9gJ5Jx+jYI`!U8nFJAwLF$dYlgn*mbxsAE|h<7-?PmDtx zUyNXJl{aJV;{VhklT}MeHp=Af6*`p&?EL;!wbcoYX_LgC$`_JZmx6K-ZhWiAFzNU# zdN4SALbi^Y&xx(PhJ|sM7&4F%USv_e8A^Ry$h&YmJn`#bs|Kr}AQLD_d2_0cSLgu- zp7iGHM-KI95=+$=NQ{^@EPTpbZ525cNbM;F&4~j;iZ@`4iRo{{Kd{uYaypRX?~GKn z%eK-7qF+VFIElV?Tw33eYS5hVeAoTKudHM{IVJpO@rnz5-y#_8J-H#pdc9S?_tgzP z@K5&>Q^sA^Sq2VVkgMnO7WzawIULptcz-h4FlvhFf|PYH+h$@}^PC`>93cz^rg$4# z$6&FC%=(Irag(W#{*mJ-NaaPt&T!%-u<+Rg>koe3x+!1z_si!?&(45L+jK+Cy^99& zgW6?$i6!qxlBJhzPn=sy<&->33^vsJ1v!#(?`ne@XGE0~-9e}Hugg_u>6f=%0eWfL zn!zq*#dPzgI4~P$W|KJ{$E2T~Q39-9JD%N=0}3x!>Lz~k5Uf0iTLykeKy{DL3jT;D z^1s?93JOFMTV}acA^p~`fI5AHPTED80Pi)No$Z_Q{?h9YXic>c<8CakJceq~&9{OZ zS>5`|DQ_XavTUiJd%JfK8PcpKGFiL|1{|RY1|6P>-m`~TZ-}6dw8)*aOm;0(LB8Nb zdVcbR(6rPUFv}M!gk5LAQlB`~GK+NYAufAAQHO8pKC3_4AlZ`m^2eU|Di~vF*Y2u5 zfuusjDl_jvwdfT{T-6i`a;L`Z6ROf2{$>z5yA?z8DE&-PMRW&NsPDZZR|K2ZYj1tV zX33^`!)KZW(6!j0DRbqlz<0N(!TYSu%!*iVZwMJ zS7-n1FMnUK{SjOgA0rst?mRr&lP>;HVU?w7(s8-zR$@9^49zGB89Nak9^S`WZ%3zM^_?O@ zL#R32N2Vetje`}JUDr8IBF=9=7RNeoZkK5|^XPK_hXpWeKl4a8(A~wxhM1RiT9QL?J^yd*cU9qOQ|fBs1CgU43BG z*bnQSleY;x2D}Lk>Z7FT8cY%jwKvx3ZoFBpwFB`>*yU;!&3QcKSU*+7Ej(c+9BXr+ zFHuVMcm$4I)Z}=mX*a*yk8{ceS1Xw$@-*o`>sxniD z=h86}UP*~qK4!k8WZKFaEjSvEGt4qH6;$Gm|D7ErATV{XgizJKVR@h++#8$NNHvsO zQ})JX3xQ@flwAnx6u4*uO(2M}@0WT6_a1PhMkVXZjY;VI)kuKL(%#oitAYB z0k|xbZ4$YEp;kmIXhup`C3G?sRkG_-&q;R%(r_QHPFO(Eo}BK&Q%oMuRELQ>T3<@2 z&2S|7hbCkg*hO{;d%mDfvEwO7*@b}lodVQc*M$Xe z(rEP2qafYy3+4-MzP%=u;x!&gu8JWQ@^Yrjmx@%i`z~L^t(1j=ENFNdKR>|65_4sq{e*B6INLOoDK$u)vaF%G-NO9Ae44P_h~x z@|IS|eV9{^jAh3ze9I9$%QU;)C+7cx>kiB7l(oOU`mUepaW-3Qhs$9NyrZwv=&(-n zOZ|q->rSLDLnObFBHHk+%!93}!7Cw02I4<-^L5b;EE|~W+5m}ckz2`uI*vXx3^MnZ zb81_I*TiTG511da8r!J?d9Obb89i@=oxA(np{+x{yPr}Ph zFYnE=GL41xGfBFQKq7pZKqT`{^I0L6{o#@b-xYX%(!|BMNTH>-7#W_!CGJsFMe%Ni zkIk+5R&V8m?(?HX;-Q9q zOC9GnvmDOcs|@YkTsI%R8X5PP9x4LFBUD7h+xgd>XKo0y6oen|)lxY;I)MBgo2@ou zMAzqcBDF#XatWVkZ05_k%wLym4rRZ7Hon1VS9$E@l4(+VluhI8(1yCh*%Kms<0fT? z_?T!gWa^3$au+*n61Y0tA|6^j#4Ab5mW6IT@o`WDgproqWC}P0WE8{?+Ei9DK(p=& z?~ouDq5I!alw=9$dYe5896JYsl6=bVPL``k%j;76d$C&e=YsbaVisNYWGgfH)LGBZ zmT6;IcFc+xX?K0X%SEOfr#~>OE03fy2pOEQ;4M@>JEY-cG5^;@>hi;Z4a)szj`m+` znG3?pdHK;vOaaP@&Dp5{)Nrfi{{4N4AlVX%I2vrT+m zdc2XhgyH;yET1i`mgNaCelTbf_>wK$aq?c5LEmk6m)&9N9i5jsU5SCvI79&cS=#91 zn`QaTQeAYrn*`ErItNwbouB!C9UmF4WO}R=F+3y|jGN&dSziXn0l47ajc9N*I0qrIf3-vYEu!y71#1(W~&N@&>2Suy6D0t$c zcY4V_2WR`PQ9|49>e%Iy>8*hAzH_Bqjc&M4^2xJyWkxFtes`G-EMASrMQ^+3HHz={ zHA6Klxfu;>KD(c+e1r;hs>U+hb4b5I5w8?;ioCW5zh4Plf?eq?wHmz(cgEtEZ%tly zlMPMh4&Uqr@UyXr`92<4JU_{@AFYj{=yl<^Kx?Qzq!UP`P8U|3)Z<0|2qugw>4*9{ z=j`EvWaNY2{J#hDe`h#1vZ1TNG)ztl>o4rA9u*Z4NAgonhnmVfJbITbs6e8ON&Ab> z@jQmTO1p4K|7O&8b=vB8KZ#e|+_6w6C%pu(BkO5BR?$OFR&Pu#%yauG$x7R+Q47e5 zYAAs%zrz1)wN0tEI6A=HE-MCzo;27c7Fc1xT0T2wCn+l;J-5gL&MD$-+tvVD{6VgT z)`sa4fQTEJm#R*37*Whg^9)!=P>76zw8Jdsggkdh;gB@ejW4smwPsf2ML%0-fM;v! zc5fbvS#}aNy-J0AG$_EjcUcBC1aiIm>pB(H z+qk2#{q9qtw!#Pa=%JY0-re`OsvB21ato*QIg2K4K9G_|`sVw^dTN7%dHoLJtHegW zKzX%DRESXDE{Pb5Q6}Yoo&tWXaDFoIFnjm_XOp;id^3EHABhj%EwyP2zGnIH6aj9d zNnGI-U3DoDXpk3i&p6^=1gGol>%4dAY&MmDrBLBV*LuN+b01adF(fTSkheSB??#i* z_fRpNnIsF^^FaS)EN$*1RH$vVR%)4bTR0VJrR>lkTy@TWue#ZH^Co@dl2#K%7;dzN zgiFC19fUn9LJwgb5pcs5J~jPGod14-JcqeOT|D=7Ij{h1O*&ibp2bhMLY3|N5?l@- zLpd=xraZeop|D7z`zRSs{iTI4c?6iqAXkBIGpz8Yk`wkbJeOv3j`^F1jd-@hb)!gi zb&en5ETtdgX_OMv9h%3{#z4Y2mEc<%-Av>Oq(iLDr$`Z*UYnybgtZkM+2er9(TE-9Z8NAvGG#!aaSp7Z}qbYawsAOG5YT3E9&iz_51x8Jw zw-auxw#Pu}bkgyE1W5n8_CO#HC=t>M-r}*OS-v3hyPr`B%=L<3 z2$i1Ka9%*@vcblCh9?iLf_Gn~Tb?QfsPofkiRW930J@&)yP&a@LSv9N(RiimJpNJL z1fRef%^b{mt7>Z*q;7W&f`~p>)%`@H?idMImOal=VU2vOviZ4kg)b4cQ%7^JGnqfx zz_w{5SwNbZx3frI(;e8Z9=aw;Am9V6tWG0{K#)3DFvOjGSg{YF_p*<3E=3vS1~ zb3_{4aqM2x`lrA0VQV9JoB!%p%(YB>qfvxN_ReUp>_23yf4)6Yz2!BBg^4;SDy8?y zH)k?CrWK!4(Zkdaut0Bb*4+Cl89tnbs3tEd)KuA%F3zV=C);%I)xQ74ei;7Z`KjkN9`q{I+iB`G3o zy-7+pU2VN_-p#BqRZecCVI=qN{w{ghB{a4JHJs)|Y84HDTuLnC?MmruPo&c(FHM5f z$PhrJ4+OocNSj#$ERFn>*m(Rjl8SPxV|83s((rCG3MNGcaB? zj3r{vZSERcI&pV8W2hrEKPdQA2bU?H`IHGL$?z6_M0a_Q0x#(B@>w^PS*zNA@$NN` zVcXWL>80(>2T+{n-Pv4QRq2ps(+Qk5>Uw$9Wxw}Lr0vUv^kS7S%Kt;xTZgsPZr$D# zTA+mjeJD-|EydlPAO(sScPP-{?k>ez+})i9cXxMpcMk+71in0bpZ)H=&v)KGb6v3R zWUZB~d5<~9Z?b`bzOXYZCbaqS*i!9!TBA$}HN;S++%{6guy_KtJ>J|uLx~KUqQ-I$ zQ)(RszM4|{6+Z$kRe&gQ80bV+OfM;>d>Y3hi&m}HF}p!Q9JBsfnVsw}xL}RXvv;zPO|j)P zBmT#v`@p8*&T)62^(Pc#!;7b$#yyrOSQ0gZb|qCAQ5G`l`GcZeaK+~HoxdF&&b`{S zSsS}aaVC-zW)XH4B9}O|ci6AmY!J=*%zm|&H>f-!*(Jj)kUMVJ_FBVM`fl{3uyyE;bFs~np@Gu!K4_$`DgDu7R z+Ls!;E97S!*N+lhh-Qc3Wvk6EPk(Oph71lCW?1VT8C2{uYmlSeK-irge!hD1)fm*} zV5*6bFgg3U3f~Y1VR@*xE&#aL{gBPK$KKo|mA56trdnKPCBvSup3}wimI&mM? zi|K;#sNgeeL+uY^>Gi@1M`N#d(%IFSu9Yjau+CO<-TG9%Dk@d&4;b^)G3d4Qw7?OC zkvlx{lzkIr?RkGRwR{*FOj-W`5Z5h#ws>lw0svi{T(-zfH-u#0 zNR|zvTsx~hN})CG`ykK?we_)%#l>*#uJ^#aY|n_2u0qvon7~jH1>r~1J+oMA@n4Xa zN$2pU9EEgJ?4ckUTt6hMepw{60GP^vA9>GmSG4_C8MqFQh*sCVKWi>ysuK)2WjvBJ znL%RqeW!RD5rZB%`DBwJfR7rn(NX7@mtRIYn8~3x|6UF|k(v-$z{Gj-P8`P2#rLp~ zBOYo=FZB?7EBGK&XSukO<}fz-)T&);g7p>aTFkBlGV&T#8{uRB+kgIF&so0?RtzN5 zx3NBia+X($)WN?4(|C7;UUvWbz-#yY{dcY0uov?z**hqk1iM_2#EIB;CA z4o4TBy!qDuxs5-0@}}-tTY`AQ>f@N-=WJnw&h;L@G_<~Rc$+w^!@x&rO;6p&dFS}B zV`4l~MGqbOU%RJ&8vXxuqPGElz|Tloypl$!jS#!XBx1}-S6T>wR@Z!!wp&RLV6jHK z4X2)uOV_&ZCr+puq1a7i(=(rMPnyrMTqw>7@d!6^bU8n!sGHHnac+v6u{Jj{-|Cm?*uTyOM{_k~YkOG(g*Ja-QeF1Sd zX@j`_=XdYl=ky={wLydaD2eQ=!9evWLY?jylMdrFn1tzw6$kc0Na1?s7fJ~P;jv4+ z2DHaGt$UB>o89>R(~^;s4Yb|4^P7LGRR7mU`{&Dk3gp8l)BNuN(|J-9hYIr4XxJa- zc31@!n1fZxY|l4Dq*A#`*MNgnf_b;E5!>waphCqxKD{MB6ttL7N<;(H>s*r)`(9%31R?fa53fEk_{|I2PI*7ig-_N=jBtPfRewElc{nGVuxJ{wuD zkz2@Sc6VYG>RNt&y}_Yw2Cj^B2EngEoxZIklhisrsj-|=E_lKU+(bOW#;+Yuw-anEQ^OyH4kSKq7b zyKITL{@TX1KEe0weZNDf-@r51@*Lud&~xGZ4u zFv88a%inhqbPWp^@&YY*0HkLx>4sks%*2^Zn@Gpg6EC)|G}=q~dt@Xq_j@zG!q`#o zyRX=ps(6J#FsSNe6KkniO~toBT z$nJIv|)_8CjYm=7new2ddg%F(mz+>4NP1jDTHKzwe zJgPuJyFcR=pH6aZ%*erRoZUL=?;t_<6^Mw$Cy2S3LN*+Wt-svB_ zTZ37+)Y{(sHv7?s30aavOu2cyK2Yd>Ju7Y(#<>gg==_2A@#gPtGJmW*g2ezW^dUhc zxailtZqwg1jo;aDz_>xKsXn-OXiPbW)Uru5UTdDp_>6iC`O*n-%Br{%?ae;q!|eip z=I^O>b+X19FLx4@ojk8>%YF67^(%x?vB^H^d*9~^J(qCFKh-boI%NUR7AnQKtXFNHmjmYMrik5lfo|JRbhgEclPrN*LUoRih2Gc80$o%-~&HJwz!S@LHdVpt;`)b*y zJ-&X8jK-)hBD%HxBpy+#YAGvs?|Tu}g1jTgS!DM39ld1wC$vQA8;2vcy>4H*>r16YFjwQSBM86U#p9VDPHoQht3&{ufe%^aEhDW zeE4?1Uh5H=j7u)-iy;5T&t4+T#uh8B_A%ZqE*4K{SUs0d;-hHz40@mNhSK9}QUE4T zX0VT?CV|l1U4*>m6`$>1-f2hz7re1h za44S-##19;NFd1YAFf^<%9wk1!F?YfJ#(K79e<`P=*KYz1$^qTE zajblzgliBd-I{Jd(jtl2)z+RjPzIwZf?s#s-FP*7x2Cxn6PYb!`MoZcYC~+!8V_=o zp!S7h(1Gq!2FW4lgDVWq2a2Aw9s3wj<5TawOcz&P{IOY9BZ6qf$G=6MeU<|fzSgRW z!ko#PBp&h^k!@eiNAAEJ1dmNVoS&p^h6Y969Z)25Sv9GYXegFhEC~N?^nY`(-4$ah z!0b})J>2X9`FMo7w$~ykQF`{z{<}Dag>xw5IXW@jeC|s*5X@ zCnoMoKIG%N*|r`A1M*xTLRDTc=VKCd(Yixk>20a?Sd5c}I}dm5L=fY)rc8~ib1oRZb10vU}KW)jDHP6A7l%TlcoD%?<(&0I3& zKB?sAN$BNGOhijVP;`D~VM%+v$K1&xbT8GbG}C6aPHsJL&pv{wFFL_Jf7ayuGJ$a_ zf0#aq*DJ?vTf)Aq&8Elik&v;wy8&_vWoh4AoBYM`7)_PJl%jr>_9!5j?VAyf3HO*0&ZCeej65Ec(k9EXC$E04qsjs3!1dM zv;ZQdkc25*-`yS}vAJxr6?@$0a~UAxpxPo8Ic?r^ePjL3?v~2yQRZZ9`Jj22nLl50 zM)4U*B>*Xv`}y;7omHZ-TzNc%+cHW)h$KD@Tlf0&hYoqUOnJ%$604kxt>M)?h4y+& zQCAu%8sXM&gZl^9vgK~iIZmT#hmOhZ-=pK7b%L#@4twkRN~ZPiIuwH;+>hiAd!zH% zBZ%*S61@4WVHKK#pkxA3@kicfm|up4J=ll6M&hzuD8lVfR{fNye**!Y+xXY0QvK^I zulVwDnLm29`!R~wr25mG(%Xk^*L;-v%!lkb%Z+&X|JCj5klVrNC{PJw&mg}2|g2TgM z;UFSZZ+M$_pUGnvld>g8@RF)6M831#%wp>H4jg7)x6%BseLo+b3`uX6rjS<5`CNa z)5!6m)$cw*NDCblVZN$WNVt>pWD*d13UNi{G*_wTdD?>gfT?vFBO5 zm|fh(^^Pay2n_70jCR~yQ|9%KDOYD}?Du9~54|@@3{@pmt+Ui5g3YQcHLEd(%D3T| z-N|S&zpb*hX$Q1o%A^fpJ5e+3#oVsDWUed~RfeT=D9YcW&m zPhj3(Ut0$wD}$ULYWKGhQx?a&TbYV~m|c)=$K49*O3T)R_xi*b0h3S53v29<^*k5P zzjM)MsfF><`}$PD&QGS`ZgixQH9nKsUsbM?hHe*u`=uHI!Yt2muir6iJ;ynP%?(0l zZpbjiD=!3E(pUV^sl$voY=G1mk+zW{ zJ$UsZO?lJACOvdKc+(0cXtyPo{e3F6Nw}kZTF=v7g9OUv-(|0eVGDd;uVa6M7fYnv zubsKeOTqGGK~;h?pbK*-ssn=;^Gv&&E}^MIo;Qcr8yPfzc|sb`xhezzrfT zD|RrqYbBe^HQG46a>nf+S01JZrT^}(FSXy5wgJQ$!x~%zb0}qsz~`G7MacY!9)D%r0nYqArxNz$}+k zK>aGxxk>i&QiuGR=mt3>pPm9VoB?8nw83Gjwm3c-hbp!eKL-E0S5wu-FNzf=lOMP4 zw&V;HSr1N;UAjnQ(4|`Ud2Vf)c%n7)JRUvTpfJ9O4}Rv}@@tl+A%kD;jg|sJ-`MYh zYDcmGQbmOl?7sUC+@b3lmt@!1pEUCksq<2Oc>@h zNmQk^cx>yh|FTK_kNx%|HpK=&uTNa%{=0zCjOrrdI7_b5u0xS^`upE5OA58?qk9bV zLEtjqFRxW65K6O5G5dG|38FJc)tZs0K&X2>O#N{sG9xx~UIFdS^hQ$9X~XxHh{VtM zDYkI26i`r9o1GbRNUqN=3pP)PU1}16%>CnnPd@H>WE07tp@nE0ez5w}3Bxy^hKR&u ziG7R!o9f9O`nN= zg^q6U@{7K2%V^K_0|MiL4YJGR4KCbYvNNgNTs83L6mNlixr7STADW+c|8atf!shxO zW8s=Zn4GQPM$e1hM75MeG54{-OotjKO~1#DZfwLc59qbTSAj%tMtqw7kcYXRU+SWEpLEpCX z9{LR&yt^`_s!DlBAG!sZK@byWvoE=5)w8Qo>=V8kKOt;!f^5`zvg3(%0mn2?I${XX zW&ER7##&^w@9H2_?U@_2ACd07e*A+#3OJMPi71a8!5%w( zsW$O*3hkz$pcP}x7tO_gik0?5z5(;?&+?7)s2obicCcY=X>@| z@}%KLJ}Mn|uUvXYs1y|5dTyeXmAS;TP3>!ZFd-sk=?vI_x4YrDZFBNx2|C(nLd1M_l*(|%^z@D1Xj=+S8^c8Mtz72k5Kq2 z!(r_y*(6`B6oa?6P6t5=Z^h6Hx_u4d*9)qTcU+^wm&!erV#ES@kPdC6UFA8+wqQc# zr6OJ*D$NZ-qNzArl)%__Zh#4B0an!fAk!z{-v~VYH7uN2YHm(}~Y)+@Ro_#nr_xnq_`+l;E&*z6Wul0lv z&AUpVmeAsqkD`7kLo+a>0(&HW$t2%-g)>P29d>Lyj2*fct!>>T0_orB9|a~i3jcgy z7LNZ*r1wKT_Z0S|d}FRSJrSp2#g1B`kE1JuMOaLe(h1E^ro2Vz16l;f=?hYFL+H7J z$3YGCw|{7mcl0>tgD7sfOhD28VPltK2toRL)d{={ZrDvMKg2XfK+WTW=SfBZCOVkB zh`&omIII^4f2=M$X(=Z*av7LapN~Z}n-&E*CX&u`)b2acC@f{vJliUugSY5-qAK+S z3_3+h4_ZpaN|f}e5Ik_>e!5FW9KB0~+*%7(g56I4a)8|Rv;tg4{g1x1@j@Y(xY;{7 zZ<4XT=%x#Q*dcvxcTU?{O`l_b;dT=S;VvrZpi%ZSMh+O_Z!>NCMshI@1W>B}nTx`X zKQx@>D6Io!^Nr@Ol^A+G4i*~U_zD}ur{H_u+?zIP$SszYsgDPsmE@G`Hmebpu(4S# zuy9HulIPd#CL=Yg&Ka~4=={cp_LXb0QJPv$N!1|mVN zZy?x*F3s}kGS~O&EKXPDag70M9?vMr+&+G>qfJ6QDJfY97w5Z0aQ%asiX*OvtlA&@pbmFeagG%GIu0LJVXPS zqY(9sI;--MB7nVQjL&M07cK0zLjM0R=brXk=ZZH>7UH!)WjABOp=n?mZ;ad zzl*#3dLhf8hPdI_K|?f7GnPMvaJ_$3Ngb)vY`@-vO=oEhTK=o$g~3@^vEFE1b7EPwWA z?D4Gpn*BIbWK!!E_lj)Sq+V#){9z@;Oz=!u4vD3wk%Pl+=m#h5H^~XU$vm>>*KpM5 zWOlg$q-not^CSh9ACLE6(!WrA7dhy^V|5+)c8pfP?wr~-9vHJxoL#M>dc9PzAIGmv zcigbM0}paDckjJKu!!Qa{n1|U@v7@O<%^4v4_2FIIb1h8K00-x7*TpT*W3)+wCmc0 zS|sY(SGHv)9=cXZvt-+y#vGSf6pf5>9lOA8A<9JKyqF1tJ}t_bvtY&R;xZT_B2^syg{Ju&AjiQ)Xra#~>^_93-8n z*12Fvf?c)CR5Ug!IjnO%|C0*`e!=#`e>O{Y@r4Z6{+Vu zdD|<5^{4S5 zRjHpQgQ7vtX#ka;U?KX;rBVR23Aow7-@#T-EJ6|m@)|F(h zT0Us2AYDeMU*X2{H=O(ASeYE7xbV)BT$@o@2rxj+z ztWgOtRW3F9-E_!NF_>vP767k)OgqnQeD`jUN+SzL3VTPF%2L=ZOC$`|EtJqjOM6DbJB<(cJM^ zE{0p3>u?cVFO_a7qlkYCvbKw?Mf9Sg0?rJ7AL6cV82Q?GSG@m*3i ziSO@R$M2(m;eCt#z_nLC@%qMt@BX0FxxtjDLr1h|)7iT*R}Q(+IL214o5T{Y@HZQi zl@_qnd_~u?Dv{oV$*onLncd51D~j7jh*Ni%fjy2$qPI=I{l!hIIf7casrg66EWN@e z>Oo!K3YVJ7Mz)433}E6syAgD`!lc`jLPbi2aYwPi%i_0{nizBE{b3kz_^91q*ZeId z)|)@@9KyWb8$2uEYjU5pWZA7^bR0Pc;3|>5B3H8<2A{tg!RcexZ|R`wx^wJsWZ_&Y z|LOHoz=^3@wm_EH=I4j4QpEzV+l*DJk$bmkK2X?}RbXSU*?MlIfF=EW52J%Ik6lT@ZE~@=NDHzutk=1fdf_%DuWlaPn!P@Pf8L%wRn8nfs!O)fgVFgQ> zcqQO8;30f~ob-uDhF@NDCKj89HLuP9pcDYLCD@IdobhN9-RxMo6=tUg0+Nd@Ff&!7 z^lRKf6tCS+e&U^u@ZFDtYb|WEtS(xpw_2^z7iT*5hqBfE;I`X~e!#H~L=q&dHjJ2W zD0d!YjJgy0ak4HORE($G2Ry@mLmg6WA|Fc<*Z3o2&#RA(rwdyvlSQ)DX$s3A2&HnD zL-q&fHN3`K5ciq~1;qXApUFJigZskZbg6dd$pbQJ4&Y*>AWTgg8i2OdTHZ(1SY9X1 z@fT2G-Acopt!l>|D|p#XZ&^n}C0$?odn=Y+=2t|RmA(5pI58TYLeu?CoNUe{T?V%D zEnRXNd-!nZ2}+4E;`aGxM~fTO-P!r-33<#T{#}q}BUsbn@LIwKj21nSU?`i-ES9v+ zSBsDg%G4iIIW5eQu^w$U8mdeuKiy!y(=(r&%QRSdR}fCZ-APD0PO&?=9Nx+&s>%T* zvtCppv%bu6giKE7_yZbN3m346ITiwF&>g)I0sq?TY&1X=*+UyShf!Kud6x*`HX>>`PP%rp|O-3CMjP@*S1YFv;YBXItX> zNOkn(>$k7bH|@+oYZ&yo2$A;o*m+MzZXkH>g|`%WML{^}LF0``D8O4zt>G~d#-P7~M$d)#^5?l0} za)}1(wWiwI$k;(-%oe2&~w-ppSm;` zTu52nvu6YR)&V_$^LwT9D&O_*X(gBg*|eTWQJ-rse;5D6;~JV=?CEX<^PnVMR3MH{ zcj2Z@Fe{+}Nf*JLWFPh&Ti+a_7Si%ud+P&qy-8Bss0aOg!)(*=x*Ic5tU9y4(Y7ew zDa6Dy^;t2G{-V<8UYXjh(hG*&<2`wYf`%QQuGl1P*|7}i=?l*!JYLAxK7nIP_T7Bo zA%q!sXNxz4ILA8lH|Rj3agH0w2@knko>ZMU^MxoJN8EjV$+ zDyaYC-ul77;pBSFYqP=NbQrH*^X2V7^_{WDVR-f({s&?s11@$ucSZ0LqLPyg$snwdcUa=m@ zEI&R~eka@_)z-)0fqU^>=pC9X$|ffyl@a!+oD(1>6X{u=EL2urZ1?xKAt?I%_J|J3 zNs>8guk+c;JDA<&xU)60)ZKligs;$x!!Ilau`vsXD)~e!IbUOzc)$P5+_|34^3WH& zBl$p7W4GP60LK>kvDxb0YQ4?6ImFX;ndVuTRg?%S@LpgDdONu);W7p87Oa z!DSJ}b)xo`=`Xd?47!7~ljMa@v;yBbmyRe~BG_ooXHM$-Kb=p3M*=KrdnNV{)~8{I zI~>lJjNwL2KMm`bYpe*Aovb~$Ur)?}hV>BvD*f4PBM2s$;XWU0jYq4VPj}hx@(w`QBpdCd`oL#WnZjgujNXt^!;3tG>PraUN7+C`s%CaMhQZ zZ<6EB(QbFUi|#iZdR(}!d^#VC9tW4-{1SXvz^x201R072zm@6ZKnRR(4-HxLmNzY? z`jtv1y(ue`GdU3?+)M??#QfuF?E11q7jc@T*Gt2N>x$#va2R!=Vk|nw@k(Q8;NCR@g@#tI?K$eW2kJj0#tbwg@94wU-Ux;id!{`0@ zvO9oJ8C%z>_8ei?=y3$ijis(Ea@-&wSb8cajr;n)bPF^dJh}*nUC+M2(InPQlY zd#wQ~!Y1=@7s-`Jm~ zWSZ)4EL}cGWCHDo`akNzBU{sF3R|Dq(I~Liu?{Y%Giq#n_luj07QAMUDq-aGXz2j;mz|sJhXuD(+a>{buW4YS9ue{+`>xpLX3#QtcIwOmCBIxYnz4I^B`vPEg+x5LA7O{Ciqz$aeD7vib1m&#RX5D8?OWEL zH^iFNKF00tuNMd{?PmjG+c4lYUYU`^c*+ibU_qC7)br zK(sZ6qI==vz>oM*_yfwB$3zX;7 z?nBiqc>dszh)yk^!_*LlUOMc64wf5PQw90byx;s4R;Wz{_KGCppNr84lZ~Y({0V0Z zND=G_0cEb)B)R9jIv7_`ecP%_iP&F*s>qK9>IeEgBl{=2@fme~bcPdivf&w%#vKOq z%wTK!F?=I$FPX!0DJpsR<=28$g4vo)@A!5$fgKS%g8cH1OjxL_D3ccUWR4&F#@Vg7 zmq?Xc=6-%VmQ{b!CjT{GyWD%Kbs9rW9og)ev7Sp_ub2;hHEr3eNX=HqcJXak&Dcdw zr_Cv@6Oe*i;f zpO^FRoH2gp$aTKl3eK#jT8^>L>3n=V@ni2ix1!_Uhp2j~Qo{2>W^ZM&H1qi~5dFIM z=o_{kY1S|80$JvBrpq+dmX~yDNvi}w;?ldpvoPe}y{h1>pT)|+Z21SB(0&-n>9!`! zBY};it22YgD#KIOT3Pjn+Ar34;rJxaq~Uy7g|+}UptRcuMl{@a|Dbz)W}**^6nID~ z6B@5!@p&SO?ZZT_oDbe&7w4CI%Ch21R615M1FAZMD%qrG{5+D}vfqSDbe1Gn&(;{{rC9U| z&f~7L%7Rlmz9AsMCj-)~J6h7FZ$);BF+Z6&k7DmB2>6W9Pb_rAh0JRIx=s1epJViX zK`hJ-$BEfXZRI5N_zNd%klOJO+uqQ0;phZ-q<~kV!D!!VB$s}!b^QQjeusF%a8DC% z(XJ16_OV2)sokY=ECH1%?jn?{_f*+^$4}v@LS6-JU~X-d41Oo4So*O+hd*=7x#r|9 zo{h2BZ?`~WsuK^$uZaNX^_z%x8vh*|x@^2#txZy-)tbbah(NzJNMCWfFmTUBGO*0v zY}A3VRD1%k<<7^kg+&7?=-SJBIC6EC7grANkLXkM}&4v^G)c;XWtcKXK*kluXlvCC(_ZzH<(Cm7c`}JRp|Lwg-K{9O49jzfq zNnDarw>sVZIT-QR=I`Hu`M=&rpg!-V@f7*)C~Lzh-a+ulVPZd!q-Ug*qw{{p7E&LCvt^BTI4J+}ubF?i8u>!eN}=|MJZ9up!H;>7A}A2Z8@Q zj+n)6!&<@2@o@lYfJw41<$L)G%u6aS>Z}u`UM%y>-ZSi4rrwHa^JK&0)>LpF=i}M68db2Q ztnH;OI9OJkxY;KYi8@v1v+8Fe$%{cu)E zgIDMZ$OrO^C(~ieTo+wu19%OZpAksoB{dUB+jHCZPx;^*sCm75j2uymzWB0&dpXIr zDIcCN`B$TsM#vM|E{K!0xM1YcAA7|_+)a1e;nvjysJgrdo*BFE#+EDs#su}rdbIN7 zyuk|3WSJb%ThL2orQz&b|8x|&J-~DKZ8(_n<3|xP_jB*+eM!94OhADznom>`F7@S#$q`3C#C_RZjS#^OG;llxG2TEQZ8&27j`h7cKF z5WLZ8`DQN@blOx)7m#X+{N9G(5KihqSEo)2G>CCja&mf!Ea>5=V4Yh(I~*?HT*V6I zLC4THf6f!gL&j7YL)42oMNEG#Kq-!o?l6)0?q0Yd*dP+1pHST9W)a#R5U6K zwo%zxKa*>^#sm}sGfnRi4KjoWdIASh<6(<@b$sxH`9pa9`PWc4qGTR88B32r)KcP% zCdFW^0xkCgn{7Iqeq^`InZKSrYBVlX%55Epspm6JA2YHfADp#fs+(ii<@V2hmA+&L zurDk&5iygj%uN78&n~j#_KPo!{SoF*0IV+(&y2I^Qppo z@47x!3fSy?%F*|Dw%6_SlkC}%&9BSg-41=WrAe?F5!7}DAAKTU;ANDgm&V&${;aoRQ z*Mn|@lUN0fRuS^v&v86s%=*GP2#b~^hmf_9->nqTDe8_e2_?;$>I6MRhj7D{r+yid zkeWwp8xfXQTTS1<;*QR&=XoOq9-io7Vz?I+;a||$G}#o@mMi>SQwa+|3URf}i{e&@ zv~kYW?BKPMFx?4m>d3+9`06;W*V=6bw|<)_^%a@0-2Su~lnfU7B-@=3kof8lhf9mP zxE^W0@xP|+sW$I6gwd)H>59dhNDtkqG0 zvZV5p{;ApVj3L*Fb5`L>`WkHlDetoP;-^C_zI&Ui7`pSiuSW^o4X{bfxMp%lbMUb1m;1vp;J@j|A))j6x9f%U4^`5Sq{L;;`1*+=o!qvt&e|uiD z;MAXyy6sHDPWZA16{(BOw-MO@b;T)_=$AnLs@dk)Q+=}jL*!+urxMz<+Xgb~!QLO) zKKQ{Y(?T4rPD9jU9tkT{F;(mUUHc&S;&d}?*_;L#-&AWf6*;HbR?Yv}BM~SdqvAV;A2_gw%wM=h+h&4 zE1DseOiGFolVHgMW|QJbEPGzqBqZk%NiG^r}JV%Vv1m zLRBtEP}70f*Tm*OnO>kDo%4-b*NaA#kz|Omci)c$;0dqV&;E%V=}@a7-9R}S(J&@^ zIJaew7}!FkztQmz5}~rqwt7K+HL0oZF-|+$>R$UNIGO^$uC=k9^--*Q>%J<0+3FRN zQ8*GV5Xrk^x}Q>~ql?|wj;SM%X}j|Awa|*FcsfDY-y-qN>z+RR}&y&wdB@=OOu)X<|!)R^^DjO`Bk08 zG8ZP9z?Y+2DgPfX>kj3NO|4jNg56t!5(gDkUaQxwBT0l9CQIb=W31W61|wbBR?A!o zdxd`@6Fc zy~E1QvSvsnA1y!sCW1!xbyt$hEH06t#PfH9T8a^pgyxHvKId_%#6>7baPAGatTp7g zZNu4Y7#U$+T?LD(w|we6K~1Ia+F#!OC@CH{Oayln+@?jc`; zaBvRn&h8V`@B9?U&87>@EMY=AYv9Cdk9-hMUH{^0_br)Rau6#x+`aR(hzx0t8e3M^9Ly_g=qiUd|JCPq40W-F_(m)JtJ#&U8(zOHIk{jeisHT=T!{CKHM2f>`0p)70N zV(<{umFz{X__ir(yx1r)ikmm4C9Y1}YJh9;hwrBMGCESzm_x}Y!iM$ zHkdt%95rOeE!2VSy-}_)H6@psUpK21fL$*-bLt`bhO+40i}!({2}@|DiXI4-2 zUR}1OQwNHa7I&xPAV41HN26a{B3pEf&cA*L(_@m{ zd@Z8Xs~P|i#JT?@-oFJtn$D9|#r7J;_C_V5gg*OWW7SmW1m@oediBZDYg_vZcmGT; zCvcRHr>{AhD>cEdPP9-|K0C12syVY3$P@sDjA67JgY;-!gZ?JWZc%g*%?3L3TNvir>fr> zRg$FgW_;1%?kjIqhe_2_UV!y}@u)r4<7oX_ji-n1Ci<``V+vE_&R@&nVSBIqh9s8-P#A0HE0Ym4Y%+#sB*~NtP~4GM?{rs_c;W{+jqCih84>rvpMb z!#!3h+9gdz+U$GnEkaLMD))WJv4*W5MCp_QwPAW76oCqx{H{UW#d+Bu+!Bl$E zT7wKFNG2ImKWerg1X@S~)-kxZN8EN&n|7a%S{tCspemQ}QMqFKWbEmuU!;`d`3+Jr z#rOlJ1yKbll2~QahUcf$co<4cr!;nOP>-=^(IC^OY=(T3a>HwjNr#9=27$(2IJ;(9 zy=$bPwSG}0Hu{S>UFYSHyU(T{y|c$o8b7s?WY*fag!QHhAAZ)}ca@FYwXRu|eTF(W3cjeX4*tEc#Nzmw{g%XzYdA)fp(&7fNxgn@~CRdF2lY zE$8@c@1o%c6QhOxzkfZGU9LDktRBn1+xGd3zfA44Xmd29#>LCobIRTJ)!gwQPIa=B zR!xtF?QiA3z|PlNgXO$5)NK5L7#2$8n$U-AV}&rYl8>RJF_l6FR+YhS)EzAlqE&MD`x6|H-`--`-WCE0aCvaeIN2`v zV@gq(d2BPPWPkf0c`>L?XTiGvCoDmx%d6I{_wtx?pA?my9AsyR5?=vON3W=pKPa<1 z6-4oU5;s%F*H(8&=#w}L3w!v8JGlE8_rK~H5|>_@r~U|I6fpa--~Np@N6FVdBV>D^ zOwu)AZAH?=QqA2_k9JZBo&5VDZO#s5Pzk8=Jx25+$G0Hi_dZ+1AVIs``Vbrj>Bp?d z2hVx?yxKfKc$xAb8{FE4CsqJcKlwrds3b9P_Y+z(T>mN<3&(akUPX+AVxC$my6X+L zxxpwVVCaCFcOLL*6`3Z{7)xcGPSj~Q8Zt$nR%Q(@$3F=QdcVIqAAx2Ka61sL`rc&% zv1pE}y}1azti4T>0L_4%`_kOoo!*5~bNnpW-I!`C z62Ew55(R%;y2EzV0q%*5eEp?ux+RbY(6@6@4wmZi7ibXT^$2z{ZLRE)rE^X9p0}5r z&}l$C-XH1FAq_0u#zE?UH!ye0TU6cJn1~9L+#W|&$;-%Ncc7AThkW8zej3o!`gE%= z?LMNRtoy%y(xEHS4dYTr=`~J-k9;qVdWo5q`wdf40mB8Eg;n4m;0!Pa&F^H7s1>&O0 z)koD(p!wb+c+JMCIWThgQb=I{#7v7%@|fd9JGO(Hw9A`D{&Y3KYV{5M=`rU9HzU_c zZQ>7yT5Ah^6QGX^X^H-jMuZ9=6m*PT!$N3$AG{^@W%x!~4uc~;h+*U$R4Q+Kmk>+= z*N?Ma6B{h;fxPZdX2lVqhsXIsa@fTklXSrOVIjz6$ENrdP zcm6S2;_{p3lUhi{>?q&nDP3&zLJ}`c{~;{Gsix09E;wumY2Vbz*#+WV>qSEfl9cU3;;u`z>6LG|AX=5jZTEB!?R*h!W*q9$oqg!3 zob(($X64GGR&V`*4&Q}iBS!}3n@|HOapt2ak;~7Ak1IKdXB$f^v^ekzG->U&za@Gu z+vM15XP-3lTnbc$HXE_!rS^9bhWJW<_GA)WmpQ{2gdd$;JPJ?fm?uBsfb3sL zVTKgow7PXz{mc2*u0LxdZ0-=941K(NE5maZzRJ30fvCsYrmvWM^;F~ga>f| z&66rVB0Z|@K2D@rTx4`_3W>XWS{KA!eG?T@01KP^>HX5t%iyLU0Jee06^+;c;bb4v zzW>7=$?eWW{9bQdYg2z3>{X~~KDYV(DBkIcTHBjE>KM_0q*7rj<_TQ+%>b?5 z89Vhd(rR9C`bg((xbF5_Am<@8A9kGdFAo+yxxFDtuy(v7vcla`+SCM)Ob4v2WHex| zs}Pg*jEK8E6USE2Eg_%veaNCyEYz#)Bu5JNS^OTz$4Q1ti>bN{gZj9u#SWbws3Kvm zn>)>nMlYA?>RoA%cQz#LUWOEOOr4L2A|H_mfsNBszTX2*Om^n3hhVSvaVs5oLlbOE zJbAaUZ7O~yuEKAMmT(vR0QdE2O6h|w0r7>mb^SGOB#i_J5K+Xm&F2gwWXJR&*RCfb zfuX@l9@hxh<@NC1bH3~&6CZdUup-LeX?g!6RmHP*GD62FD!|#Bz(*>LxDAPB0N#eQ zLHn%r8+sCzIIxex3WvJTQz#bNLbKo3v#&&l%0%P-Js5sz1E)`IHGDLBl8I7(?ypC^ z_?&t&iEV$iYBmXqd5{}BiSgo8VdB)yk}r>Cz%L#(?ZwifN+Ak1(XEc0qtKaXCVI-} zu9wJ?o0F$bg)y8r zd%_6m#+3R~=wB7_(xvkYiS8rcrR6YsUguUhbI*TPI~0W1j-J%~=1CYB}s* z>crR*nGYos-Y18wQLi3uT|4G_TAh#G50-!v67EOyT@>XVHc8_maCX{+|i?-=|6b-#5PT6Ln+w+37IyivC}J|L;GMxU3`Ar8&@U#2ct# z5va@mzZWh?|<#Rk80j5x-w#+ZO_v=5;+^x>JlIvcr65XLAuFl|yY#@$> zMX_}z5>ZHIs3MT!Wm$ifv9gynUstbUSNzlCTT1BUOUN2IA+&c5zPf;1&d3ssirbQo zYU{);S9kJ%dd1c2^h88-gI0M^mSSNvJ=ak7NVgp2)Jxm{NSk?NRdCgIP*&1kt(Sm6 zbN7PQmr6KBaz`;OMjxY1R=s*Y26JdSvUGioG5;cO6I|c(L^nb(Y4N}W5a&C3w|bOy zn=$xwHC|WBqw4)v^+92mXf)(-^0Assa{L!jgJ>kQ?#R9rpsMER-J0Vn2I*d!uxQMI zuckNm03WGw9^=v4-^c4Rn%W^3p0~B=Gp3Cs*eMWB4NSKH?Rwi;^lao%pAhW!F{1>S*Xd;*Sz!BmXa7@N|znNu*&4HWiEZu7#}#5ufjHP z=s=3S9wrd`4V9~(MZbjfq zqZ<(AofB~y*5P^|R`wAw1xY1J9mAplc$x*Ocz~y7wwLd9IJC-5MkYbfyFSG@78<&7 zttR(U^#%|SMp*L$c2M3%RNf-Q>vN6t*cg{3CcHeD;ipYcq?5k@`1PNkj0mp-#a=lQG7357)DQ1b};2;KH^&C^L2F|eaEC`+s83vew} zv|4gSoc$@P@3z7h>2*+CsDwl3s&0(}8Q?4wsTJ&^1k{O92v?#D-3#qnRS{spl_2Kjr$QFSZGWMqsAYE7bGTW2G_mIRMKguW{QR&cZn>{xsZd z1r7EvZeU_rJDI=nPtk3)cD}LLK6b+G%8u%w>W?+Qt_WX7BR03$?FTlvFZ4_)1@i*( zi0K)T;vt*<3`SeL&*8w8u@%p|rm0Qf0~{ra*J(R&Wb&KkG*w7KqPgi-__qFrN}#bR zMCtWN`-k}wGyV}#-$ciU%vX)^nOXfDr6VFjiKQV-q=*x@=m^DE4)lFCtM>IJo(Pej zWLSHAi`h|LYbuu*%}T!^B(o*@ahpt+af*~=RC}j2Q`vnP|7~xF(nKHcC-=k^PtuOb zRTdv|h^P<~A`{ov>ftF93X?njYEriXsLnaphTB=+E0qjV!Vp;9iN&@fZv81)q!M8C z2j~K(7O1Ie!vwT&mH-*>%Od=oDrX6@`#%@0e*Z`*mP8z zcNNQ7dwfLsxo4rd=RDga zo}ybZ0ru(c@^f53lt9xCX6x`~Zc`pqVccQvyMV6_g0Yi(A7KHwzntqj?W3cxRL%gx z^QO}19h-hGMoC8rf7TROtb5?cr*Tt)dicPPO+v0$Jl51vl_BFrnteQ$hx1w$C3{TL zIwG2}Ox$pA_S%F;ffZ`m34eHIZ@V}Rf1x}uXP-yc1COnL#Y!FJvDj%I9r;{%)zzip z$=`qD@ty$}6MLD$VPal};h#ph$KGYl0~|b=kuJQr5c~FXAn|V~Wn&S*1od@eUEZh` zKnq|SOGpwE6z}9xeh&5NG8~gd)?Xb^1WP@D^!_<(|Al(cMlH4Fs9n1(YXG3!UvfcU zSZRGHyr4c2w8QGMY5K$|f1uYd9tAOrBu~kz~g0Pk67}@6wK*g{o}aNb^{YV}^Ln8*hA0V5g#< zE~Z=cx^LmJ?;WlvAP0(Gk5Cy1IoeLXc4xSn{Cb$6i<*MJ3DLqubEwQMl}?Esm5Gnd z4^G<9^El>yj%DiUa;sl$6KWDvXFDV2O{}O>NoC%2l7;!ln(wu-NS1tUh4txV#nlk^ zh@{mhq*GqIA63X0Xsi8xfvf-Kl-rPLsO9MqsJ4N;T0+QVF%jWw7i$qIgGn4>baT_k zBmrDLmovI`BwQY@REnp?L3v65-126jfll83judW`jIjJGnRE$H88Ssh#YAjEy0JDM zE}thPu{{56f#%z%lP!pf7>i0eJMC)$TfMR6bkE103ez$O=bH9=-RBH8yiH~u+3NYO zY(Yf~>o33=kYl-^+X1m$?8}4ToX>PNPMOGoO11A_G+)2u*6BVY<{Tf0>p0Y&Xaf&? zA^R&(sLLA9q2DRSMLvXLw7p&C4kTRjEhZ|`D>N$l0@e}eFb8Y47$m4a&YO1@|I!)F zXd5SoDbVXn(7LYs1MedAb;|Cy7uB|JW#<*du>@<*UH7tX6Lw8Ix7emSn{V+;M%5o_ zy6?M)#izk)#u#lLKRM1aDEWo7#2y=@aWGd9Bc5wFatlW+K&|t9mG~#qP9>Nrp6Pei zUDRGa_3hd|=ki$m&gOc9RYk8i1l7SgJV?Eo$O(x*l2^x0pKU8x=#596>8rGv8R*uA zHTvlfuw&l~S+unYAxgq%z}iFweF%89U}ID~-Xo;fWp3!e5N22{iY;j@iX~8U~SJ13Gzs z3))PI2snCv{ro8!;0J(x%#sP}v?-{=*EL>XeQVaYcFIprTmE=GImDH?4pnO=NS!U& zi*m5rP=CO6lDw<{hY;($_s_Ehrx=FamL}2F%j72`P=}ll;J|zPRq}gU z_y=DK9O7NxCO_6XTMHmnF?vCtlfrFS8&qMf0o_~^ipJzQ zrkS;Ckr<3Az}mCYS@hK}5juG-@F1T2dn%-NxD@GtpC_pMQ|2Y{ak{%eM4Pvmk9dn} z9Mqe#b;?PuEoXxks-yNStOiSsDcSO++DoWF1gt0W%2e)r^LyGTd^4!+Ia`f{g7E!QSl4ryXp2UJ!L69suw3`omWuc6X<8>G6hRccWNA$1^)Lo@G1%l>D)WfNUVHv1oyy^%CQDUSaHFOFxXZepV1 zF3|dVnx@cKQHDsMyZusd@x4jjqv(PqE?JN4&7SZh(RvTV2N}(g$mlqd;_vnRvTcSA zMDKM=pHrAR{|m&o`r|&NG3kI#ypC!ip^d_6b2Yg2MX5A&HX%mvD2!eGOQ%Rk>&3U@ zPud}vFPRQb34ApA*%<;uCpXtr3(kkza)74+y@fOaubHv3ogUIeqy@@%Vg}T_<~gSZ zm}P|AK99E71&HwVf7=k}tSE2X!7lPDA+mBkdfDi9WJe%gIfDhPH};s4Dg8=$KHN~D zYAf6P#5~jW#UTEe?L$Xi{VIdrFyFfh5@oImC_zAo6e){pGN};qteBHoBLMiK@9Xt4 zy;blph2OX%$pX(c^G9&!jG&s}`!tU(vd;m%Mvs4-Hy&I@pZA236OE(@5~)cbe~|kC zg2igpa1)Kd)wyF$fxVH`*0J;NXk)zFcs91@I_Yh-X8!6Bp#W4w=M)?Q%dZ9*$dXfi ze8Jzg%zFvhvA#%6@2VE+OGZH5zUXE5@Uh#q{k)P1imK?jS|SBAf~-*{mtHei@ei_h zDND7K%id&%FbSrt-sQb8RI&O^fKVj!{tI>|0H;Hx+SO zavR(t^^W~{SgurrVQto@%APX#w)zQzirtdYo#|7^&t#;GC17M^y-Ynl#plA4TDO;d zit0743(j~pzXMtJIXad~4lIB-OiYoxquw>xQcs)VpgWsH>j{8D0Q5^<+l5A6cY(~t zVRd6Lla?CEv#eyDiuEkuaWhty?}}K~6ujW`jBxZ!0k4{rMH6)QLPQBJNZ3h*icX0*%7{;74Uo=|zZ7&CuL^u8c68StFe2_xH61fK3oR^MX;;*l;g zI^|q+pO|YmE@YRA@ZhmGES0BLk+L*E9?Pv7(7 zhEo@xW^srHbms+b3O^<>@u;=01jl)<6Ra=fEj73QJCjyPsq+Mrmis{cMI;nax}Y=m z!d(HVYa=gj;|AErfip>)y%_m{$H?KJl3e@J(K)1`PwGWf!aBnxv|z?57tn#j(}#Ao z%)}X;JXtrcD?Paj4-x^QX?k`M z({2J{&{vYu0!P;A$h0dDq}!ae$wOXH0Y}TRrx^I-8mAdKpj)m%w?Ne%-zih5)%v^r zVk#|)NN2U@BIlUmvY8acFS$0pcYFD8)avc*I7?BYAu(~14q$q7h6%Z#QAm7G@mH&u zifAUb-skpgfj3qBj#Y3SWkh*~Z`gRd?)8k^!UX=cLHTzPD+kUa@au|`8}BysHac8{ z)AC5hc*bakYn!!ECn@V7CNV<~%t?ZKfM-DL72z%-Y78biI-EXH(TtfM1+^mQ7H_n# z$3}}-Pzj$+tZ|XDTFnLxMa~7MCaWXaLkElOm*zXK`xu0SI}Ipx|AK1Bk9umQM_#o) z0dq#xj>tYMeqH`6k9CAWp>C1c@#VvHg`A+wkL%Tp0_^ipx4Of|>ZP$L$6MP=`wo4^ zr;ZH?&Wj~CDdG5cCL9F+%Aq1M_}khHrFh%Lv-~4JWQnBS5j4Oomz-E0*p6uC-z>OK zCdh~HjM&XRSaGhO+f3*g{0MbyyMKMy_T+0|mPj0cIHr@2pr&bbKenFq5~HFg9@WZDNLXr1qD(F@4kL|5`MZTx)L#;zilc7CKyvB(U_T2pie|NOyu! z1Wwf!%iXG^NNF0dszgKwG4nK>|NfpEb(1ymoXc^1hvezM5ZFn9=(;@~aL*_WVmWD= zubgy}KRveF3=kHGmp%zh7VI+o@`{#Qdq9>*lsVOPGj!H!QU`8DBIYbbPsmzRw6B=i zx8NdBh~oFr&nhbz@xSr{sV+*q%GFUa^g8bI-eG-GJ+CKJ++K%=s9m(I%pJKzxTfKIM^#>1(T$tzrDKCr**~)*3u|ixW$z-@^M@QdG zJr5X*sBbTCv-P)PTPo;*JRz@p#*$ck{1T<_FaT6yxA>AL-~LnYB=R1;+taBsZr@xG z`;KK;Vdg)0b#wx^${5;_*1+3NN!CHPt$Z`ba)d&s*6<~v$~zV)%(53hKA8DG{Jjah zVvw;s!z@oDR^LDXtW#5XUGlBMBXyplOF1g7> z)JJtxr{sdHaB{#jj-PXuZ}ZuH@g(ho0nE;1rr1JARo|~adN(AL|5l%4qz6S+o!ytX zsZjAlVi7F*)Z8(_?mKYkXxWucj?EKcVEfH(&=cgEMQ|mz=!5s`G5V<8H`}ljN1^#L z#&c1>8!7HRXkXgK;`cJ5Y0r%A>YT^k5;$nhoWN$~^L0X&Zfg%+d@gl+?P7>!ff>wNV>i*ch;2LR1N{s8ASd8x!!e># zLjo|Wd^4kV1Tz9Evh#e(bch;pw`3$21{&{N|uQw`7_e z#-Rl-&|~H+$U2%*bK*de!2!IjlVh>$Z&k_(@^g@VPB_vGx_VykDP zyefCJH>#+{ZkC|(X@x`%?Jvmta+V00gG5grxc6i8u_(j`T70{80q5tipX}24Ky@@& zy$6*;dJIf%chcHK{|#EC1Re7B=`jcyBc&{U%sQ7Y)(Y&dl&oytRC_i46^Bqto1Xj@ zde>!gE3@k3Y0r@Z$d;g+R65HWh9}G0Yv^ z(p%}JCOxK9_dJ{rTMeyeYlk$DV2#Awc2%DW`yfU{;lAhk|a`s(dr75xpiVs zbf|vh!Tj4y?lNXhOFOcX+3P>7eFl&m$SZCWi5Z2-E0NxIk&!>(>>UJ1z2Asox5J}jBb#HT>WZ-ZlU8koGn6Du$kbB0H9qh3ei5Tqyo0fr-bQ6Z z?>Y$`mG+#tHTE3C#aL9xU=QVJ6o1og3wP7_GT17Sg7H}G`BRTBO>SLUJWHwmxs#rl zRj^pgZvz2(`%WC@Ux0_3YB%z3Hp6_^cPKJD`PEu@a7M`&e+1kry;`-UvvRv+LetY% z9ftPFIZZ4rHX1;eELiqEfX4Gm{vw=3&6U{&@SxmBwK_3+T9#k;__%y46#D`9#}x)V z%YjHOTRKR(Fjh06N##HXcrm|gZB5L79~ZS8N?(1yB`yk2*249hvi6(5Gp?O zkbbOjn0RO8G;;}TiIPHdJP3-1pnLL1&JyBcTkm<>M@-(L@CksFWRwa*dD zwv40+3?$8R38Ww}inl?E3C;kamYBqznaa?c4;F4jRX(K;t&5 zv#ru#8_rj0TWtUA1FqOuLUhernGzJF&+7#^_DEBD$#bxC>_wkCeXgnherp-Nn zGo9517aEWUtLHge;1#oe(*Bpr3D#4e1dv`Q=$ceNyv}6i3irQ1_Lly!j8RAVZDSxC zO*-zcd|I$js2z0C2AIuKFZg{{-K%M7Gc1&rjO9_(_gPHhyXH&*dT%?|y=?xPLRAm4 z5y~gbJ*(a3b4$vi^IW;Gmz9c=?0-2#;E9wW$Bp;tpwqmT;TwAF^5l$VDT$kP#_+K} z&VQe!5fjr`uBAp>bWAX)f9rfHtRzy`kN;F| zWa)2rf1;a{?3V)^W$;fxh5}*?zivN0w<~YmNd^sUw{ZHRr&U|&dR^|%=!B3wxDFTM zD?f~i9So1>8G682@jnjUYn(aa#ISKcS#; zSr%opjqW!Whc4VTiVzDFSVz*nfK!%wmtn<|+8RG?v{k}eWtzb2T z&+i?fQsr?XsliyhQ+jIrBwZe>fm+A1=}ubOlR$S_Z_oXr0@`O z#oGHb4IMwFO)jE9Ks_;^zVyyI;otVWWXR%-&FmxGFbePpP9dJe9tXe87~gI0%<$@w zdAUSg)Y&LYV2(hxwl%=PdFjG<$Qy^#_yM zeWv%YjcAFl7#VT~1t25#5b|Y&(uqpT?Phpw9h-cr`_ZDUPil|c3Q%6p%a;>tCbYVmiCrk8rP@#8dh$X_3YT%Cl2yYC`<06dyMy?`D zlYQkQXDpV@+i5!CU5vY#4F23L&cxbpUVPZs2A3Il)X-n}w~)25nulr>Lw9 z4NknGZf;Lb6v!auExg($@y0N6dk!L}x8F8m$9aDS9%AYl4St*4vI)&$Pmclb{G*7F z-|xa1(xs%I%zwyg2}cF4l(-Eco}iQQ?!<%H6ozID6XFM=~<_$M(}3Zh!k0 zIUb1^QXc6grLaujtqPuw%gP=Z#;KntqJOxb&7NzuNF8V$ILK9uBU_)~YWin`VW##3 zcN+B!EiV5z3n0@hP*Jm{7-_BDaW$#l$#uO09Qo?Uqos#9UQiZ`%5@YeFpM$h)7)x zDLH33quiqp4V!+1Vn-&k)KJzsa>^VoIY~ZtQ{tB^{=5DDwZOS~YPGiOyRQM-Vm(Xn z6>`3CixQux57U?P6PZD`p&sWKP7aH8=|z=f!FshD9>|@m_uM~i{Nf;03={DTHsW5K z-6VI_BSY~p^607-R7(}y9r3acQ^XAOYzc<~ME63j z2s8LFcQHM2$^{@_I{LpK2Rvs%aXuX!772wOt#P=WjJEgjCNbV++!4~S)`Z)y)p&wb zW*T-uavASu`ch-VVD@GxQTvCfTe#XvCQzgXEc|j2QjdsAi$f*}xslG9WE>LPP`p-H z4XiQXI4p^14XJvzn6di$b&ku`D6wZAC+6t))s3FFn^OS+*r!S3!4Y{y#%lLFyT{(g zD$}KFJm?U=KoxR%@!_kZ3x!h?mm@cwz9b4X13P_M8|j65g5*Wvqe0-!iNf#^O|3@e zO3@KnRqe*=ZyfO9rAmrY=--*|Z;uD9-%mDv+S@A)3L)pVN}pC-XyO6%Mhn@i)iy>3 zT<4@SbjGRHsMz$aEK3=*EP_s4ly6~V!g65R5h#0%NorId{}}<@g+=^YUgD+lr}a1# zfbg;vE1JJCEvaE6IE5pE;Ealnls$jb8vVP&D#L-EYPQ)16Pc9ic>X|ijWs9HHdg5) zV*5IBF+K1{X#R)+OXlS-CGL7KDVm{#V+zc~i!I>_lbE1}XL_XWNOdW@nfazl%CEgE zm{2YLowh@GBP;Mn&!w2MC%H(r-q-$^5Q4VzsoPy-cp&qR+KLxnPWtC})(y-L0suVN zQHtjtsv5xoaWO@dCZHfveZO!8H%|>zJGPf_oY-fsMQk+&T@e!}7Dr=_N?t#7V^Nr- zKIitY+czO?n>tDtUhz@dtXp!pR@+!GL=G`s9eFBwTEY^Kq%bli{#=~+Azgw9zi9A} zT6~i0rj0wR<}Z_GN%&Sk$CfFk8`oZm$?rT(c`D9^^_A6QwHXTt#~B*QTuNMgK_&Us zXb*DRlXao-b^@$9QNa|uY|LeFadI-=?ycJ;HIjvZs;&77fylgcAd6CH({X)6HgKT$7tk5vEEBom*4 zi8%2krZa4SH@5Pzh%7|mTc0}G#6)|nd9S%YQLFa7p%3uaPY5Fo7e{8BuR1fUnxb|~Vq3;1lHDC25EOkSx#7JK9QgOApTh%+F8_|(& z%=y<#1d8;bm?>$KtDgdVX=(V|kN2C<`-Y-CC5>O)Y=pg9Je_z!Oh>_?)ev*~3k9VW zKzC0aH;|4)(+2q&cOZ#4DChOjmv3I}pP*fwtEI0`Wa2PXIE5L@s7!MVb5C} zhU~<@H4`E74}g;&;9SpbZAc#8c|#Eyw{fE~ z_U0Wt0$dJ)UCt#1YkaWb#GsYCy)58XKG=R+wB2^HDdJL)>KA8NPadW7fktP8l**(y zhtBA(;2k%Z^+;SRqh?P?(A$w=;#ZbxtnNPlx*OpL4P&))U=*skD9`=Zgj86t9K3jRXU-Sn&|MQcz<(KeF<~v2M(2Tu;{@HQ$p=}4w4qp~v#MoH z8}j0ly5dO6V}uAy>#`ajOhNJ=B6BUslHL~ON{CmOJ-y7&iX1-KWlf>OoaQN zm8~?=v|OMX4FLcG-*pplOCUArSN$^NFJaM_FCynzGr2LDx2jkbG%B31p&Hi|3Nx$@ z0;PqPV!e5hB6J;6hJTM)txceQJy}vba|K+Ul4oZFajc>a&L4LQfAy3S8stVWl$%;> zDmFN}!OqfKR4|{{dau((Lgb=3=$K77i=uuP z`8A3P@Eb}J_U|m%0L~F*ZVK;E?Nk!#6M#-&zn-nCpXP>*C)w6meX`x#w)34(HrZBC5uok zb3EAnq5EWSk2l}_!pOF$>oqIbojddd8xXB+UlW`NTTIFq>5li0) za@>DKD7wkZdw;t*;VD|Hc?A_NoRSm7hT=FDx<16?c|^Jc>QzJz9@5<%qWFRUxNO-j zGDp7(&i3|oPuP3D)x z?av0hCfCqgDQocj7Dd!D`d_R~0r@(gO|~J8nxFwgol_d${O*AaSee}4HeojW{^oqX zaEq27?pj@i6KbD$#1=-!yp{>Hid>OPtfFoL8W||Tu}C0?Pvr|(I4JN%M(I+F!LleI zaCjVqO(hN9T(|ulL4!Suc-u$VDp|t)x4pY*Djf83UE*&5EL&}b8Z01rSWnf`IGBX1G_tL#Est9L))Jz^v+4fW1N?&Q&ueE5Egvxh)RoE z(4LyBNQ?hzi$rXOp2zbQu2tCR6;&=%;EnQ!XEIP6$2ew4cu|^G+)m+$Qn>STxr?+w zz@85xbh`Ss`)RG92TrCo+zfS^KKcg*=W8pTUcMqBMolJ6 zOL~RI_s{TUAgaL{;^WI5ulXffDb0_uz^%j9r@>GY6^rVD`AYLj0yDZxyfaKmX46Jp-f7DG;EG&QE?AiyE3%j z0F~f9^Jb~rH(UXB_`ATgD>2)rF~D^MU5X#h%IF@cYN7Gi==oThFt^q_(~ixo(Wmvy zj*a-bf*@qgK$oXg`~4g8uZx$&+k?L=y*x6>2Nqjae;`H`$dMxk>CECB=dNXGcQqb= zSAN7bCrN8T*w$<0cfEFEuVhzSqR$F&Hkw7*_t)LUO3Zc9rhCvQom$V1nrDRgGR|_M zA31CN&QI8U$k`PEV52|n;z-xDyU0@2xkN_N6Ccl({+|xS&w9~p5%v}>=`dSZI#j+F zqrA39z}L|DVQ39LOVeR5wW#<*7!9`SH$`8%G_GWVZd!8J`BIp|RW!ON(TI1xdhS)$ zdKQ-K%3^J2_I5F@t;Fsg$?k%_f-%_PdKx361~4^&X&0s)SCLSxs{bHv+8o^x-AX_^ zw|C|5b|CdTMzY*)Mxe@T@N_ZGRXJPhUzjCBplgRe8cR~*H%q>%_(3w?i!O;AG~|kIVO8~YNV~WsU28V_#c0=V(Vr&d-fHjbZaqTbyT%f`POr()2nHGI5rcQuC zpUSN!QU%sHm6n%?e>C89fOB+ULY0#*;1Uqlv4ObyTCwQ6k-6urXxx#lws~h+YhC6e z^W?|XrqHfKMo@qZ-o~zw|M}QHB}!#7_b7@tZ^!^2bTl|m`IS6a9% zO(@_>)1k-YV^aFT-L?fcHq%jpIh~2Wp%E0nIbF2gxoF8tXyu8{b4r0vi^Q&;d zub}&S#OOaX|JJY3l2bh}}tQkHM6(=r? zobJ*$*RH4LcIbK{POblf$(1n~8rDDwqK(aI+dUcQ`ypE^V|Q$=lE$V26x=2RyVHST z*9eRE*q>~$ezGcml|Z)ejn+@Lp+~${=1OC@<#V6Ydk|&J#A%qNIN_^_M%f763IC+( zL=p24^!bJ6>*Eh%iK6WUXO&F>(y7i)J}L_;UZoki z4Z25Dzfy$oq0!=C_>2yiIGCc$xJPN)!bUw~2bFqd=5C3JBAyG` zEE);XG%ksZ)f%AnwxEp$QgDE+-x)Q4KG`-Pzq-|Y3e6e*jRpKfA@*la{y@@K>R+Gr zt6hhQ)`K@HiO;u(U}<8zs;wQcn!70si-*jk$xIEhYPP_*UBvf=i3OJ@*)PFulw3c%co2?B{*7n@8cq`)(U#kjPe{IiPIJTytra(gClHEb z^^J9pa)~aNj&}{+=;nhcW-g9t*1&<(B>^b)D)tn10R&ZjnW4_ozUS96eQqFTotN%u zDO}8IZHQr191QuPpg>g5-y=7J?FX^A_V?Y=Pt%ZZ!aPdML&Z$jY9i!V+`5-=E<+#l zmHMVeRHbjti`0@sA#@@6g69246T;COp*JwBq;O^@?`Hc zre8beg<{ga5vTLJ#M#fGYmW3Rk8>q`s zre4h`x~^b0?g}A3Afe@3d@NUuQ}M@wB24%iI3J&qRTQosXFn0Mn5 zmuJjs&EB^a`RIDAuQNH{(3^Q<-HYJbur@!09fbiTTz}eN`&918R_6en0>i69Y&-=dXzt-Bv zv5)#W3vbhr4#ZEWn?*kUowV<0?1>}*Yiwf-Nv|`X(jm=`YfSKP zPI2+QHW3w_8FF5JP2MZoCA}93CwaZ0!!~=&Fqzy@YL)E@l>0mWYjr~PLI8_@jmNrC#B=h0z?YkO(wLGqj5+f4JV-0VqY(f=s$;Fy5Gar0 zfeYT<7&T+}yG5a*zNQ4D{$_N;az~;}t1KK=1CL>)mL#O~ zLOKveTe;n_RwL|a{HM{lRKb?V(}g{IO=)JF=CTEakxk}ON)V?(-Rv1rD^z37PTguPo|A7%;S4{lGYFQ!KJ>@O!uMDs3vCdtyQBzAAOA0H0 ziqRw7<+tGs3T!M23h1Tz%6W=dYza5ZEZDy%ZScH(gBuF+#Y%lQhc%_IIG26y>Mw^@ zK$eI{Vh^n-m_T)wWXYf8>?aD>E9hkhap)^>=|LRjrj`!z=UX_a;775Bse|LG$Ui^M z4cDWgVyfKPkYxI8tSiB1$V!JUz1sIn-}+O0xox(U;S12lAD=OZt<* z3(s-8bD)z0^(-AEZN<06R`U-f>$ZPowCI=TFIT@N0sH za}f#QzVO%e)?R3%dFke}rQ7m=pzp8KXNY4W>gB4vd_G!!JfD65HVbBPZFytaBV%+d zC+FnjW6aDjs(7Oroidf3z91?TJ2T9&0#&>eY7>?BF{#42anG$?bIW|_CSck#Omji3 zq-Tdam@2PKzm*_0L=_RPsK?Z0N%J*bI0L76G~2r~+@=cEVC3?!)sqO|9g1H(DStPa zTJ;uYsmIUOHlVF#^YV3IRTTv)$(og@^H-{qN~#|qf+UynxS5#tE?R=gn#c+Bg=8HiHmnKpNpRJcQP{Tfh4^$EGVdEp4`d>qbuMv*1%u@$_PY6fTO#S;S zUZ>t;WRYtUj_**ZwdxbYk`AMGnKwdm)N&d*#~!i|poIcG#g)qe*VS$9q6^0{P3IzB zlN6)kulzR}WDj-p%L@IC)TfEd0`QMnm0SjkW)BG8UA(>^KlU4jHgcAI&EG*yv`E+# zlYzj-AvK7hE10D!Rj}l45l8rqO=wK?w@pb=-l*=tUp`nKkxs&9gU?2WXBLrZV_NUn zWv>C`<_#d62mjxM+)xR&s-liloPa+TaICQ;V)eCJ)@;S^{?HweeLOLfb+FwULjb%A|9<#EK2E`+| z0jzV%5Q6h&YN)FynYDi);1m1{E&DT9x!Gr#{ulxqG*xB|HB}RFXpQBV28TjBiBCo- zdvD}pGB;c%$YHG1l9P*s^*)Uo5(mMEb71wC3toUMV4<44`pilg``xtlM!%Jtt?Ujz z2ZBihGR@S28Mj=E<(P%rs?Dvm0%g8y4rHoIOy9 ze~oSKNm&sa=Z-t3yRcc2RZ<9>>#&#O6vF$hEb8*@xcio`!knnB&yF%C2OEa8yQ4oG ztGnLpAv)Faf1UbOM-yi)OXAN=)s@lhMQvLa_}f?ygK5CnG~Y%)2ZN@|yy<~^(%8_C zGRI12=PF7^n1gnO2#>!1rwrk*=i~wT@*HjEVV*HA;y6^>$rA@w4rVk~su)0< zr1!8acim$F-@&rs!nHn%WvIzi<5B&*(pTGWF&42k*0ry>`_^OLhu(M{v;dgd9Tv+C z#V1x9;;>{hhn2Bmr8ribowimmDy{5AvABb_-n)JWa75G>+wc53Fdsm^hyVNNWwW2Q z!@S_bQC&&gw`o?3-ZU_p4JuP`%X|)^hws7oj& zES`UeBXpBs-Mu%yR^YOxa`dkOPu0`Ly=_)oUWwl@8!`TQKKaLB(alGtFAUv+MuvDD#n;j<)%JrvLF@8Mtb=i- zAO4LlZi+e<|EHIoH?my+<+YuyI3n&FIJwF6)HBJx``bo2dWCJ}A}Na6tGaNPdE;f4 z(|A+v2cNT&6PGsnu_xUirhr?GnFQyW)9`pliBVA+(Nr{5wc7=PoK)$SEY@&$-JjJ+0R?OzMNQe`q z5cRI!OYZ(nSTe3+Jp0z{A!~FZrtvDF{<>COP5t4!1DwG1S%{V(;c``Jp=qMqqk1R$ zWcAn0amCnNi}R|+a{q$|YM9cQvIa}S%v_*Ga&2!M1X1QAHfMe+qp&py@lKW#GH8(d zXP&J=v157Fmp0%+!~3UpKjF8~eg5g7gwy%;!^*ws66Y;=HMsq47nyi5$s=CgMrrV? z5Po||02ifxi7Cvg+N4u8XIPPx^q#2nZe&Ig4=HjNc(eq3_7yVHlcqadbjWrbY|v!-lypyz_b9W^M$UHTk7GP3 zJUw355F-4(q0Bz$g^J?CRoS_9BE;vCL20A**BLzePk1;?pq|9NeSWgEuHcFa=q0nW z`C#>;j*IKp%ifjee9p3naR|an0UDZ5kM$gh@6FV`=}$O=XF_{_%6ZgJBLusXsb@$& zC*mEzR8-_?*x5v|1nA%f(e%g-VK+NQAt<4Mp`BynGPX6GGG5<`>?gv-y?&l~+$uOZ zhFs6zmUEb!aLc{9+46FK4xF_3UJGaq^KiQW$jmx4#L?#*^_AQSpci z7=QSj4*{lZTnO+^74a{0?x}JdVf`L}TM+GaMJaF0RpV!F`es`6zu5qn$GW)v(~F(E4$8*vFZ=(!?j66s}6|)5@O169uVJd_S@U{ zeJ!$I4LI)nY(k*le?zxhccLJEE&J!tI$kKH2Gk*lzwCt4OfMR8(!{&7OCC&vD(LQT zG-4vprlI>a8TABlPm-t>i<>=cSVNwu2sF0_!*Jx6tqxy9cHP0=Q$HJe4h4yRM z%}Q#*eE6RoXIRu#vwYj6v-VRslq+-&zas_rmh73j>T9>(4bLYFZ+_SZQ$SD_Cf43Q z`X!i9$=5O$UTj+m2Jg0*-#kzo{Te~+j?-_s^4(&W($?xYhI7UQ@}f7|UTFi%nE?5>_$+noYW>LT z?%GH(Vcrx}N9?W0jA*u`g*#GnOlaV+!e65BThHCk=Y$=qeaE^Pj37@T{YgPS@E=uJ zYvwj#>Nl`BoyFrWC^j8ppUIr(r~5^K-^Haq2M?F@#;!?JMNsdMaW8b+jPY#u56DI; zAQ*ATlT)1Sz?lsS!i7F7cS9OuYUd7Tm0kY+jHm<>_D4#I8Wq5Z25Qa0gg!p;9~s)eWGJ0in( zm#GA#oU}*w3$?lT_q8%kTH?5$3Jy+`HU(Anp zODk)dcd^M0783iz&ZMTvWJiX?~ z9mXr=9Nqc#1!|}ztC?%D*`O$dD ze4i*%p#@bj!!bE_mle(m{GHK~m909PWOih3c$3bK*r7T}-VX9;d9+sAwGRuV!%W<% zGF(~t5Yo(Pd?n*8An>^fyNHU~>bq7>wF)ebd4tfYU)v|OIL7y}#rrpxy6#WI(==oT z@>C6;*mOWYK+fVH{tb_?OY3mlYMj{e=~Q9uCzIN0EehrE#Wv%vKPB{DXdZqj&9=cP#NyQ7Lpm#3}pH36?_8)Brd~hqrm2?DIs4 zd->{`U?xyOcLlSLv?9(5{RYp`aiTI|-EA0sIq`$O;rCrSX4{R$L@<1eCIJ15At0Lp zeNE)X!yZi*$qN*48RP5tm3|_w$e&J@DOL#Ugtx7)=U?-ZHAa{|fbWh>pe$N+X6vq1{lYJsdP#9rwQuS|Xq z0^g7W)@#EcDVD_h<{;;4LCsUt?5S=<+4G*5zLSnu=Tu2=lMl}Urx3pU}A05*|1D?!46>*rZ)@PRS8&iqK<#WRnTJQXE-+KBoQN;`$&N2hf~40 zdhcFS9%=%6(a_1Eu3Km(FaP{$lUui3qCh`;q~X~y#im-JZvoTUB;NX(8|fL^mm^iQ&u`mgfW|jh4dZjvn6%$JE^;9tqqex-9rb(g)w!+B&g(@{IQ;2s z$eUyApR)(UpSc_De)X7A7WES&my04%@lLAE?MIc`p6F z^>0bTQq4AzjaN(>LH@_i{OT=1kM1wW6F)XRbqPqb<>BNj0bvoQxdtLz$E@h!ALrFC6Kcm8QKOPu|r z{2}G*X#k@EEgMX><_)c1CxONojCS}e=kO4)1i1pf58p)L_Oj*sj*uLc8Pp!2a1A-K zE7s1$qey`|*{|Rz|3sp+v6gbw*!e#$EVF6i>n4ybCsHGR%RLX1t!v}6D0uUa3;U$qp-t{bf?BR$n zq<8J4QI&_ha-vJD*6lg$$LC)1g*Z@uC>Gb~_gx(x_})DqnSjZ-ohr^LqVrk<0?CU6 znT6iwY>l1xVqgmsclkc(MJ!`-?eu7S;)J#b{wx}>C$r@~ZjUDnXpn;R(rV1xdw|-2 zY*%Y(!+HyOJ1)a&HL&q^;v*NBZ3N|&=htMqz@6nCWbK+amckbIn`YvX#|pUk(u> z3?7jkDY`fMzP;lwz~O(`a$wQ_CX>kq6eXEZE?>J{5Vt{ouH!4|p2$&P5k0IREe`XA zW%sk8_DFZ{HsE5h*(CYZc^w-=pvZrqbR9t{`0`5y9*<=-3*w*eSgajkv4PwaFCxE9 zA32X{&VpzHjZmNN|3^!K#q8ZmA4d}4kWuLSp0nUQ)`Z3|L)W~#-_BgbbM<2uY{hn5QSoy;|Z1#kT_g&NX<45CO?Ec&AlXO^Q!!>xu z&0vw%@B3kr%J&yqF8rQSym}(XCi#9JjZj=KR{lI(WeW;WhBD)a;51<~z=bcMyL`2= zV1n-ImV|_+|Ldmym)_$)4B@=eQ}kVnmqXgEb?a>e*5`>B23>gs=K9O*O;?maXQjgLtW=}IQCQv#dEv8Q6U3eOCQJeG1;hN1g>P%OKT@G$w6rTH?%BVu|{2#c8; z8D&e!IAU9((Ea=Jf8dY*pCH4I_6YnSReYSO7vNrx%|WI=x+SdXjTL`ve`_C)qAl+9 zmcyd`P3bgcH~x<18LSH#h<1iz`x&GXclhv&H)4$){ zSMl%qKoyGDqZ{W9$XUy{*K`rVX|^PX zEWm<+mGw+%E1)e}yzg&{NB>yYYF8;>M*k?EeHS)s(?-sI4&lNPxD5IpyprX;6((Jq z60RIgA9-KxO@FLQuf<`e{NQQ9OXkqm3X1QjNwpuna{5pOg_7daUAX4Yej;N0g@;E% zqFPjd_b%@Vo8Qv1_i!2_>QJv%jX}M* z;3esl-<+%$cddMSqtwB$G!7H*PRF4kje)Jp#uCH_iVi zOT8V<=?)sGv+$kl3plBjKk{g6xYjeX(X8!IiqzYH<%euqPu{E+7P-|mXiy6;z;deo zMkT20w_pAl7$n5`a;y1n&-TZ!I~|pX`^EeBc95<)B(ypd?s5$l3k_R${eu~Qm0Ca$ z%!O#6Q2VW;eYRq$lrkY3`Ie49wG*_e^Th7!H%#KhK-G^wD8c`qN4}rRI?G3n^T6T3 znX&AV6DcmY9M25?AkGTmK>^F-A(dd|U^;%iLho7cp=Ub&6TCLt*q!DR{6lIkm2ld) zJ;u0$S@{p8E%67lT*XJQiQ&s(yuT$ynU5~{-pj#`R|hsDa}~fB)i?1c?IM;v;X4zy z|9p_E$G0$Fi~U*m9p!I!rekOk)>HvA!d&*}fj$4tCH;SKMgI#_`rkjWeZ`kH#8FUv zjk-rh5+FD9{2Y1iY{iy}=FJj}uK3(a-u>>{wn*r5Fqp9QX15I4Kj!+Xbq$UfzCW^- z-ETTOYEhdtNXJ>RmI4tYqP8d$aCfwage&I>H1`5N`=Em2BUxObvgj)FAB$uiium@; zUcvbf!>tmJ=XyCFj$8%G!qB^W7BdS_?vrir-8xsL=Fiq%F!NC2^Ts9jkK1Q2L1Kj4 zy*OaaMK;yTQFvaLvAuv>Ovq>3dR23#zo7qcSgRJ@gWWS-$w!l^2xw*I)n6PK@OVNs`!MpIXtKI|Jx;-IDc!y)9XP8YA1=KLqk{&ooz_z}Cc3Xk>y^TxG z<6Q(>Znw4U-phjR}K_iDZAI{Z6L`BxJe_v2`Aa4 zgxYQ0XSnu+1wz_LBH73LxBq2Gndzes{vEHys~s@D3_(NFkgfL1nv~(uj~W0411Z9l zSz9WSppVfw0wi)LAGAsiDoK68L(VhQn9~8#67*KBc)i0knfH>9SJU2QMwqMDKYqbr zFPfX)7&eZUOz>Mkm9m)Fx%cao_wX}U3NoxWqdJ55lg)rOa zhRG;w%T+;so^pFLBSuZ!VH(i zcTZD)7~z2;PN!XFrLn|45LUJ%{zw4k2)JIS-EV1y`JP1(-cFH^v`!k?p4EE@t@-Ca zMT5r9nn%xeZtJjz#Gb3^Jwz*Bu5oui64~n)0eqYE5F~W>9Cl^8tX=Z1yDBI@qTxNr zU<+eLm7P}GPWth3j(@+|c0;>Tv)pZ*I7=}*fB=7nI1u>h17NFcUU6#IT4@J(`C=%z zKZ!qFVviQAo@*1!j7(apj8yS&dvrP97_mrp4DA=O=qfnwa)iLY>)8wokr{c@X5dFlzJF*D9;p>ICdBoUE z{rPyQQxt#_Y3?iPt{rb_@Mv9inLUOIW>eEE4Tc0|;J0IL9Q8{ip`W@*9CHG4Fajv6 z;PdnF`j+O1h8(sR1_ljIX(n3Gfa}lEP>H)QO#+_p32zqp?DGz|=Vk7>JMg(>N3qVT z-u={o*S=?3<0@@J=AFXZUuGtyXq9^^+M^|p-(ks8Wz{@VY@MXrZt0jersynR9qxYn zJ9}(*Cp7Wnytk=q1)4q5_c6M*q1`ex@~!uo44lQu>JAOC>mE5cp#D zJfn?oSOkW^Tal3ooEL?1q<1x@XaB}Hd@PON7lNwVZue$PVf>+s zHeG<&pCrpFT{m#b1zPZO>-XWscs)tkaP~!Lc%cT%d>CO*?z?!y`@2P#DXU!-*^D5C zXq#LUC|6l0{1v9M^>o~=5q#E{5vNn`Q2)aHG;wtI7ry#I`3z&p!$?fd@H zRyduW3GKHXW&{%6euJ;sJMwC`erEaM5Tz4c1?+}dFwPmJw6K5b#j2i{XF88T-~l&aMkF6XMc9Ay4=h0#f=g*`r;X3!wRm z!dibr;lRlWtN8Yr+hBEXxDqAk$>2SbXdu2j7aN!q3XnAKSpM10c}ElkgMoJbZQZj& z1QL05bfVxg@PmSe2imt1&PwNt3i(s2pi1@o>%jX%lfmsM7;;0 zLNcy5Vwl9bux>J26`i@JGzrIsq1(I@Y^mhBdGy5AT^dA1k{z(SGkRTDA*q!un2yUq z89~m%E(F{DRI9P!>zlE}D*RQArTy}TWqN3Bhp%i9kt|@xA$}lLEPnMVV5u5R(`o1UNhJ! z{WeaO@%ocHA67A0M9w}cj~h0^ljFxXA87yM^YNOe4`_?|VkILQ$Le4@@|*;tK}e9%UkSi9{`!Jj{Lj> z%k+Qjw;!)zx91a0>@8yj$b3EDmD41MUu=?RC<`D?N?d2x3`| zgkRRH`|OY1@2JkETZK&u+e;OW$W!KpMu%^-VJX-0hcm)*qjzoE?WT*`ua>2W+UTMt z*J)taXIqq8S^k=+jjbH`M62nG7P$&gRs<-Sr>}?x%ibLjYMrEK#{Mh2-nY+`#V8Y> zN6jh~n1(cZ%hC;(J-OxA(9Z |PWBSvSn0O&J0*;rQr`TGeAUlD?Bvzi<7};B-;uDvro+S0 z+|~*%j7d+FY@sv59r{cd8FG6>x>1kSn{^TyVa8x@NSkujx<>5XK&v=u_Jd*@M;0h) z%_TOXqu@2omY?nHk)AMr)+M0pr*VwIar61lXn2NetD~YkCT-`$sE{BS3SIoIoCo#X zduIeAlM|xYEwCHQL&DC6N#PL?I(i|))ZvkNL-G5k5l`8;R3NnMf$L+(7stX~?mU@~ zTENo4588LO_0io6A3hZRgb!FXvwA0TqZe{`qTlR)(f#wG54CEc84bz@9DA_)p?R_c z`cdbD6Iv0C%f3A8DVO{!NNn30v6dXB_~!Z<&EvO-!l_K@DX@S| z7HkaIgGIx?;~GOD!GeC&k#0dWBWEXp3i^}FJ6As0OKU!W9hehLmP&Ks`Yfnw(N1zL z7)}WqlsrH*f)M$bTwxytamtYyxQ`z@$Wg$Wb-`lb6;FO8>HYshIP)zvBg&tR7;ueIR~a)Q>Da? z&gDTU?d9?bVizS4!?H(h9Dyd6rO$FJvgk)Z3AzjHk>00~6zhcp!tDa*d-wMGEuQx2 zwh5l3^J_P8rWHrj%g?|t`=S_9Xf(y?Q()c60l#32T{~7r{6o^dS3l_VQmO6l2FKU& z5SAb`qjv-u6O9XWukD5RWu04pNl~Q7h&=y?V(k#SpkP7dAYZ#3^HxajS|6ahN$Nb> zV%+am$Mae3g{Z<>ZX_$sC2yA4 z?ug`=e4>u*zp`NWBLDN%uE*fZH#kz&KZu!<&%S{|qdKI5#dScd)^sVTv=cXf;|E}z zm-|)lc^4X~(tw&%u?IKq#+B_D8vwdW$$8u>ikpB!vsW{3vGe5P^m@&A?mPBgxbG^% zMSNQVyOhJF*Y3V!S4Gk9n9G5_;Qz1y?3W7VS{DGoBVPF~wmkcj_`(aoSW%I+p2O77 z=G@n@yM-ikWd;+T;-}v>Q|QP|3kRFVJk^T>Z2NxLjuWx3q91dT8@6T1o3POM8=XH1 zj}F_d-<^}4mr3@u9VO-g4Ovrl(gOovnBMd86w~IH?AJlVU|& z<>V)bZ`f;ZdQ5S=OuRfhWkSVItM|!B6b~!Mw||-+GN#Mn5_%>j8+hR7U0Zq*- zg)w>PqbzZ8S~4Z$_gs3BE?xQ8?Cw_Y9BtztEoU{T%cbqeEuiZ=!m7^(p*7vw%ij->%Py+%0SrxeZ3r5MyvI zKGfQf8$JHa?vu8tA$PULW;`0+a>r!rDUozDoTWvKZJaSG=0z^gKXYf3gFGrq5iEUy z{)$Dh@=U~ndb3uJL@J_J_X ziGy|7J?_>K&>(ERb}(lQ?PT%zO&yW#8*g!PYO%$gzb9({`BSC|vvvO4+Eu^r;U5c&0j@_}Qx9 zWnyx9V5@2EGtU(X;;PU%-l|J41BX{c1MQp5t66?6BLvWSB< zOoX@)&Dc?{U!6C$>Ptn@&burm>SWaX`L{-!ZTq%eXTU*b%CrmR#sfKF4=Z$s(XmFF zjNpe7QzKa=&~rjGMzD2OGs|t5De&y*ymlgj-bDF;?B?3Ge+Yyrj_g|Nj!v09eBb$n zAi;}Y!WBWP_-eCpb}!u%N6Y80)tS`PMPihtV%y{o5IqqIJvQMFWUIqH`&e{b{gZof zJ*f}h`#q@kX~EC7GQ?A7A9%#YG=)7;2X-lBT$znX!IkpV(RHilt#U&?xrnC2eVJC9 z7na{0tR0>H+!qkEA}y=EFNeLgaRh<$@VvC=U^Z}II2j%k!x&1$iX-<-Fxq_d5y9%{ z{l8F5qp4Rl6j8Q3xDI$x6Z0J|M}irbze9ugQ0GUV`AAQYw6ci=^()Hvp2rOe=;Z>% zXdq7xz_OC_-lZ>)lH*q-=4;g&+FQ=lWtYT_HSJ{K0220DSa9S+gIn^~Qa0 z53(a(EC?K-1?5 zYADsT-hnO}AJ}X)nJF{4zA?hgUt&owU-L&x=~vFjQavU!w+Z=eTGo8c#`jaW$9tz{ zHwVI?s;W0v}H_)}){?wO1X$9IBS~JzO@ke@_C^bU_fjemds{4zDu7U|b6ERD2 z5+OpE85asTzrar35pS7~N^FDu@(xk9WY5?y!P5g8td&|ftT6ELWp`?Lr?22MeJW-P zS?2YB4_7!yk_3)8V3zU!wOoRoC|;nr#lLsI#_`_wLrWDmM*Togatz+z9gU41EcwG{ zMbZ;ys-cic&Ucj}wm^GLIESV|^ zV%(By63C|a4&93!+ZOyMSdW62PgO4$vWs+>G?ES6L`+_1#LYVbyn&f?H!hU~Q;zfe%K>Z(6nIBHVsM zI98>$;$(IoqNJE@0-l!s+bn6}k0s{d^HmgOfm^uMe7m^#j_CBv9J>ZEw^E{)@hn&c z1Ls92R`)W)?PJO_dV@95Y#VP_=bhkZ04UjRl$$mqjJoRV-+Rhcm>SyM?$_cIemvL7qJIOS@~<-+*l*_R0g+ zyj)Po$a{uD2cDlECHo!K+b8D}TH&Rhgo$qsfmcfmhz4icKOZYOGjG!cgU^JU05Q63 zRgW)P_7y$lH>`*kn>DCbG7lz3&S_$8!@1%B??}n4%Ydfdcd@zNM>Az6WGwOvi0)aD zeCOVbC_WX#>_k85h*X^^V6nB@L7Ze`Ti2^3G2}5datmw7CQjo2SR=34Z2l}DwGjMy ztUD#55ljE)?z){?9^oFm`UA={j9EDy>%Hp=m${8LuU`(lHOR=qB5D_+;b+1y#^G&* zGA-)ubRz(Sp2eDHuzX;BQhEP~n=Tc3V>SrZBfyk33GB7o=1{Mid%2!m_>tzplFwQB zs6j*SH@;=;2VvxY;rHwC4l-RfhwNrB`+jpN-2m9ai~-g?ViDY3COPsPjKVyBFdzKp zN)WwaA$zV(A_B%$+5xHTRn@WM)3NLDE?P2N=D_>vM=X*M>wy?N}az?tB#rOQhN}~~|)6Pha@b_9ErGsnP`QMP+18=Lz z+ow}6-Sbbbx6H^Wcm8#Urz}v#0XJ1?E0B$6tjNP>^%AIh0!}tkkk~U+T{TH3wJB}n zy_Q!k_U*{geYVdu$qg7KTq!=mN+%d7WQsL-bzxP%T^DcM=2#qUqOH%}SBEmC`8{QR zJKD|Vl?XmC?+vRacK)f^%Vz7G73eeF%n3=k(0ttCW@E@H@=*zbo8} zwPsV=W{)YWSWXIm99hSA3nTY`^lhobOV{O3nv$NxopHWh4m$ z>7CfeuN?n=)iF%GS#pZrFRlJbpk3)Hb^{^mo283!)*n${PKnhZe$?o;O z_c4@{w)nA}$7$iFZF_b2ZOT|4u^J>X9CFiRonl7q*M1o>_bZ=#xl+1s3`^rT#5Q8b zCA}Od3J9(DnKM)|`j^qB#Uy`&vNV#j-oM$*&_O1?mzhVcdj0@IiAw(Rtlp;*xwCwo z$8lD;l%z2v(6IZIHo6YVrF{Kqp~*c87oYH{CpW){&#}4xDm&26Q!ydo@99Q6H@&cd z!x1z36DnvB7lDu6Mkz)yU(;Z18)?laN8=Y{Pn*NQm_aR$wZ8+Euun%f!91sQ&of3T z;+s`sSnb^<(j*&)Thni`q$e!_{VHu|7vH=C>I0grIM5j4`47Jfzu{H8t=@6<9Qnls zn&d%^4DKLe&duq=>Sl=(iL0;FeR8?VUmC*SywLi$+uEwO{l?B6eiIg^eZ~R`g#Au> zkviM%Ui%ljmN>WsW(Og|!@xtdHGVFE=_^jB`x8a+$nE9!_KU2!+O5>vGLK=6t7lA% zZ~tkh{>}#}-jA`#q#M}^4IRjRA?)&(KZ^+uLLpT)lc^HEci0}KQyHXkgEq6JJqnv! zmjYSm3~$?!JsD(?F|VGjoah|Hzftw)a-SSyqoHxJ zqUSVNG93ZBp42H>rb@U=HO8Q}e#RqGWk%OnxD zrr>Z$Dg@JqSm}I`oGm=~5tSgDlf0QiA1*L#MDw$+sVspF(yPT`^m2oc z284Ai;(~^jN3%};_^S`G3*v#oJfD7A&5${Im zkx1hMUVbRO>LAb=Ax;`UzI!AZXzTve%9*BjFIH~g|!AS#ut1?_U`GYsQ%d}hB-Y-{a>K*Ob^?}-; zb;zcHEzgSD89v2Z*!8p#vEV}LJ@lx}*2fNuRr89wcz+{~a%nvPM{{xZF60E>lr%H- z);T(330|ius>c3{7ZF@2L(;s_nn#~se?p^(qYS-Rp56o=TgbR_%vUs>{`pI- z!k)FcruQ7Nt%3u|U#spV*6aM6Dq>mQmP@;UbuQHc(wr5d)9E$+@tBJR7Xo{;E{8{0 zTw(Ue7|8J}6_Y^?GXlm8#z;+>W)DSDzs(|o&(ZnHA3ffKg0~Lt-bZ&ya{JykZRe@& z%Zj-y_)H;Pql*0HEq$Jv8{JsIBhrk%v^3XKS-B8*Z-UXAqa)8!Op{ljYsXl;p-7l0 z&N1DRUGC#W&Fh8+K-I_9rP~%B-Ew2=H5vE%*`EM4XU!@5U&L%XtgOYZY6Ym?Z((5`=tvC+sx0OEp2(A&8r! z!dt;sVel^LRe0rLU@TU3*XoQZ7Q>HpV5of=dINrxixn6a+Aw_qJO5sda^U0(c8U4t ztuZT7URnF4_=79)3Z-^iz|Y~=vCMPBczjj+eCGkXZw}rKR|C@33%upDuaY`g*bh&B zh2Q0wSa*C>nPVbCG`wLphbt^i%@{ipl7g_&Xt$M@MDrE0P|kEWgM}zsp1Fwui@1N3 zvh3^8eB3=g(e%$9WJ~uel7;o;>ttIXc4M&SVaIIhCI2nKgStU7$WoA$=af+5+WvP@ z!pq;_>)>y6d#J-MRFlr7!CXkHw|tyGuZwti9vcPkA<%CNME);Y0}Vv@1g zo8c-7qhO0`Xz6X=2qB7ic9x(*J$ttm;6OU{oNuhwW~kv((qfQzz9eoFerv~6C9~6X zsTrEOalj1gYwBaO(6M#-;P^bU^y_Ju3QaoPB9i@+;o~VUFg-x`b^8l+#O{TN zD6gu~%k)|pi2$e zAKkLs)T!vWM)1y|FOeZQXjn%=mZp^Hapyj}wbitjhlx6kxX<6!x<=`MBN4=7@ggyXJgh*Vz9xwb= zj%waCtA{5GJ~gITvnTMEQ#Y*KRabN5uXUK$`&v>N6U`-1LCn zk>z?)z9f|QS6jeVR%7Jt;Z9*w-=1`alq3D6>-d7W@Yg-QX>2EuczpmOzLB=5{`1D$ zQ@E4?uipEj%8lfHj}H>*;>WCXpKpkqsjw6{Vbdpx0_|GnM6mzTc`d6zh5*kH$~dyk zIDir0R@vxQt9PBOeD`=wXuv;<@`FtQx2dv^1FLz9e^Xyew=8=8uGb>{itga=Cuslt z#+n^oTwH*kX+&hG=Ft~#nC0`?cK)K9L*v=@tbb*zdTjQZOlE!CeQs}gR!D}ZY~?l& zjvO9G@HB-aDDL~T#sQ+=34ShjCX7j_WA{5kH)hTzD{wI@n?iQ2qVJhEj12Bla$Q1iuU=oU8r(`|V|zX0Rl|tIEwV23EFQlfjS6?FS7;ij_s|8doQ+ zzc=sz8k@5(0~_Rw=3bJ}BryG_5%pe(2&FGNXCpeN;Ly?Wp=2zN+vg;lA?#23I@wKt z8*f>ZF?)%pXxiBDuWtTd-ocd5Ip)(PO6SAaK*Jkz9!j6Tbj#AFgo6ZU3}%cinz=0$ z{A%*NC((Kp`qp(+J+H`7Z&e=tqGcZ0=liHtVv3JQswvW1trGcuJ|l<+rE55=*v#dK zFJ;3!TOH*2f9QG(r?&d=+c!Y5VlA||7H!c2MT1+>Kq>CU-66O`aVW0A-MzTGdvSLO zPSBh8p81_QGxwf9AelXpy`S&1*7_`e#ZX+5iMQ@6vn$;hhxCY_H$ZE$-#_ARwDKJF z#!t~30+o(uZE}mloAtYm-swBkSjMvBV!z|^N4L0Jb8M8C(V2w9z9y_hB~wGl-51C0 z@CY&k*6p`Y!7rrfzkc-c4tv7>;DK_r`a%d;KIF1s>*p^~sj(a^(+a>`UmoIaMPH`S z<3TcQCs8AicHd3VQ)n9hYsC*S=Pqsk?-n_Oy~&PEfTMGlTOH$H-M=->ehABMLD}`} zVaD0!S6ta6=7mQqP}?7OKGPL?95fFXwXg19S?{yRmgI;1ng#bqv+=r+1reueDjK=k zsxueL{)3>#=&0ahZn-E8&x+Mu4GI|a!Wl_~~SHZBCN+}VtfXUMeuXxU2A>3$T-XLh#r>8kyehCj)hy^7c z+A{U#@BD+-Mj-=O*s5aeeeOI2{@R2t^#$r3{pOhT4(5_Em7mANm@*UG}-mf$D5;X8V?2z0;mjVU@4G z$EY|1iN({6q_90hl9j>`WkWPS`tza@z>{=$b%qjiy8TorD6XL%eC8Am(thLMo5ojn zl`&4x0d)K0MKE`>Jc;dm%dkEp|jT|>$EFI5~Yjz73)#x26{zIFI*EGTtB4M)&u~pCST0_$XqGE>c{>3`YGnPzN6eG?AZ}sxo#mu@F(49cYr2DH zTznac9cG=W@dR!~PE`Xi1tVM^^V@Wct>wdhf%?IyR_G^62Itp}0Db|K&!3+ADfOJb zK790JM_f5Ux<|x0U$b>vpQ6+SEKXT46>$HrDP{R9>eKcpO(>aCvB*-2WChcf7<}tP zzr4%aG$*1OwvrkHAooaXuly)?Es_0Gkzt+qv)ck3{(Nt%AW1) zpE;pBwR0Rbfgkb~f+QhX$uS)k#_qVn+K|!nbap`F2+Ln4)1OyFaIStv7p?Cdr4Ar=CxlTj(t;$KEeg0tiM90T%*v!cc!p zrwRUZ(w;Qfwy0u(5FRCfuZX{J;)#ZV>K$&s5YS%4eOo-t-SD!GiW#i{mX13s9qvn( z0d!Bfn0TB1YEGpc`@Pot=0QGimXlmQY?WsN{(!eI{lRhZL-44Z-XpRO$>-5p?P%qJ zJ{79=_QcGv12G*sV$c67O`or{9kiWzR!nz7f~E9BwR7fyKhEXJjDD@4)$1K96>=vJ zQA;X;u3fv8mJ^y7%|<4b-c8VLzlxgf`@FZ7-QIC@ zkv(wiN`>XL`ymrpX-3-)IN3O~6}IBL5SyU4>zJz-kF5Tg3Qzbu^wy7rm`4jET3o%E zY&(ez{4zdZ24tOyJ&nTb3HCAO!c_k)`Z!2`vWD_*az(vx+pZk<@LA^j@N>wE49tvz zMX_ww&7{-Z;3C&(Xa_q7!ys=68%QR32&g3SF-e$TN?IZ`xR%(F*5e6n5IbOs=@3r7 zdIsiBxNX~e#rjr{9etZ)$tW1T9g6a17ovF2l^E8_MoENhix-933NP@4BW|N$1JWMI z^2{I64p}4pFS}3+2i&$mpF}6z>pw+ruRz-vfcR-Ayb<-|h5j{oZ8(u-K z_@5D$^i(&DO)&w3NDW_6qz+7qpjK?l- zf8@%S*%J$tZ&O9xJ&WaIIV+ANf~^rV>5dLteG3@g5kP~G^z1et&zDeNBBKj}GLhFO zs`|*MZLRE^#0@Q~eQyDT&+{&EHWV}S`S=Z4J;!6q*|aMn-dv~k{Ag2OQ~tLj)yu(G z8bBfD0_1l&(H{T|C$XC10e*hVe=5zG6*18c$Nd?zd+t>-r*M9I6>H4?eDF%nRj;~r zAJXny*31v}t5oY~K>u5GcDh~{L=^nV)=Xa%i5wrn_lRBQO;ZW*oRGnNcTUZ1?o7m? z8i8@%7sRgkyxy{uX+EcnA$K~{#1@I-N>DeV(2ggbO0S- zpz_q%sXj*s@Ab5UQ;Xz~(*IYlYqe;3BD^V#e_&X3KyCr-N;djkE`})%TW{sZQ zhiNp0V_*x);k?AT{BcXi^av>!jVyhB7C7RvP<5@tRzD*Oyj*2ow^8L5yeO>!ITSiC z&6)?l_4!%g=U%*Oq_*j-;zAc3ls}BqL7EviF)$gHc80>RaK%Ot2@M4`az zR%zlw=l~hXq;@9*1W8Jd?WZJ+oxYf`PwZO?nGYrbB7FTW3157inQ)E;86ExhMu;YB zwc)xnXsl`(J{RS}fA*si3t7Y?HQTf!MP{H`tD4-6vp8SYHPyxG4RYy(?IFgy6?g-q z#x+QUq6OfMec$s_U%kOd>*`^Z0aqa=36X zlN#O#3oyrhZCBIvmTu`RADvikn6b<(reIA5a`sQ*0FlZvm6$&j4cB8K)jzaAgt_Snbfh!MNh~zeU zolc%R2q5h%|7`_8@FnICYvl$ExgJDaspS$3BQp~o-^$yyU2Dv4+oX890mjPP`HN~p zx%&a1@e|%=zbvN*l0Ph#B@q!=*CJ0-;s{LwZ|oSar`VNM17H6$<*DJ}+CKH7bJ6W9 zFtd9=p!6tet_$~vHbOc6T&V%9z+6$t$NOJ?6yZ5ulgwol5=SGC`O)-*dplZ31HPSO z@8A8K`5x_-N#VJ#j$u8$>|xSz?QWm>F50!-l!*_Ji4R;;<5ifx%o4MDx(Rx>b$k9Q z4c|^krsCVd7Wdb^?vp;o&%9^cFeKALw0f_N5lNVI!^NdTjEZeGk3jnMjptR@NcYy4 zjdg|xl5_8;oQS$)Uy;-fK>Q2=p^GdTYK<@1EC?aphBfx?MH{g6;;ql7khdhgNkjCd>)h`K+uGl|l(^)585 zPKCmcFDW8lo?rtpBw6U1OLl)MqGZxPQ9B2A!1lD*1sg_(U4=&i#z2Tg5pmBJhxcyq zQp7QeZJU}qeU!Q8b)SwP!`bt*_zP~#z{63OrDWXzNqJnjGo8!uR}Ijbr!#+*OjdT; zk44{QSbwXy&{?{bayuf94%F}v*ni9Zr$_>oqXuqf%H6!{PqWqJ1N)_2dcMFm-k@x- z#Jp#YxIoy+*5UUkTp_flX@8|~2dBLW-lc)k8g;EaK|BP_4@R)Wj+mO}LaeltV_Bzv z{gf%G3l;Qw&?JtKF{qM>y9{($8ME#5^-HWIN$7s}=_X8wrwEiaAcjM~YSA%cI{N)U zyN&4OQ-q87iezLOy%T09=9^5X;o>c+8r`;2C50D+VS9&t<(V^$S`!j%ZZp&g=NW$( z-1s=Z6oF4T`u&re!Jzz?+0f0NxZrh}#AOaD1d>^NNzw>$i`dWh^Z)kgP`qCL)`!QG z{@(08AUfSQ-=t#cC*X(_nnuaJ5D{oVAi3ewS00t(Ay>R<%oht!xGOhlAp{p9al zJ-o+@kizZ$ZNXzsVr>kFzQ8oh=nXK@Z+OroA^OpWvmI7*1OYP}|5j?G865lvLPs|V zZ7Z3r;+d_S0lDutlC&DOddZ@w5IuTY9eoaPBHSR2Wv?Q3AV~7-9ijmp(0%MFUFP5n z#*W?mYl#~z6w8?<&Ew`q-aOX3rJLn_b;($%`8e&L4ZOgTQiI?RM*PV+xR27OhZ!OnQm^%9u19*o+G+amUqvUnsS^cES}%ixV_-Y=wm|YktveA>p zF!@-&z+{yd(RV}_BrZmDly4(Yj^E4tBuP|91_aw}EyZ7GU=mN?t_m}AUI`!xPTi4m zdls}iA4mON4lj(mv0jYTr>A@QB8U0E=9{XI@>tz2WA92E%X*K*^i2yCy=FH|?60ZV z64v2_D7K|yfer;D6I^(94O2>K`EqEEJXDO_uqg&wA(GVoV1)(Xaz)ZBoNY_(rtmBQ|#o9K9|t=#+9x}xk?VI1R` z(b$rUZc}1Z?Sd~|gUPFYdW9~dX4!Ad*82H5&!JU0`HBuAiog?+O+7$b~bVwG9Co!|-wYI)VCk5Z_aMl%GRa;zOqqhjw z!m2ygtp;)iQm7;-v(z_Z2{Bm2TrqSK@3%?ry#_)VFWGv75LjI*r(E<;w#uyXWfCF= zYMroCb!f)df1sXR+9yz}Ny{+&$p|D?bv+@4E$$gl(#%(s2Y^n(l%idoTLxU#Slh0* zpaBPx9%9NL4{3}pv!fcKh}d06jCS zb2uF+5{ai-wM-Ct=gIlv3pssU7cCas|H|!U-#R`rLy$!hkf0qjnA}eoqW-&?|CHFS zISX>E71{{tM~M(&=lTIMQ=cnVRzq1gA!dfVSUi_O`_Bk_`0(7tk`Qu|oMQgk{3GD7+xc|f z+Q{fd{6)2G3b!A65fd(R`F4oc;UWQojn!FjozvxJ-y(lgU0kL9GwME)b8rFvfa9e4 z=8;6*pCXT?e`iPg9DN=Sv|fI;B%IHmG9zNR)Ptoo+m zi1}H#;C;eB8r%AOlVQvxNgdj>IAs3EqEPdGDj@`-sWThfuwYB#&0&o_^Kj}4eniA2!O~x1+{g&eY4#pML+O7j&^|j@Wf#e?A ztdDmyaU>ANS_(aRs*z2~5CU@%G~|0~H_cZh$bV1%@R0W~v9+enXNG*RoU-vGao{O- z)abfu-%@PU@;V>yC~Hq`90x5NrP1a+{J_yURun6o(qX9fEr)Bi+E0zv~KLB_|>H=Xh7jB&rtL=+VeJj zM5BK3o<&l1=&Aq4CQLLsY3i_R;^N-XkbFo7Yg)XAl;R~XIx%UL$fY;GpZR#G(dN@r z00B^m4|;X2koNd~q3f_$9ykr=>n2^9u;;~g#f#=SJ3oX+1;`g5fo@MlkeLk_UWi>L z`8wp}ZDVzRU4 z)8@@Z5`h3a@x#5i^ZWc|sxHMI7+nV5Jp@a_g43L)TA_xX2?W*tBI)+ISIPIw~q(Z0QxHk~e~PxSFcj)BIMq-S9`X9}mp z1#d+Wh55?*%(jcrmOmaP#B8Z-OTBj%Wa8PHYyOkk!IrPxok;rld}qjqE}6{#C;tVH z#>J;NY^ceDg!%UB@xX=KUjd53zQbW@pSz|Mo@{#Jy>sXnV#&;ZQ)KdDz?D2?uB85L z^*GFWpOTH!zhA{rEod1vuA}%4|vrNmDEg1qSdA`f;Q^?|58+5xlp%|eP3Ok z`}4zjt5isT!|w=TI`%v8$R;Oh4&wV3;@9;cpUP1Ny%pAnl^P5=9?%ym?5d3r{azoYCtD*vPXiJ0^nVA%+*h*Z_Exy> z_!}^=C!>mUhnuq^SQ22l1R{zIkE&oRHkwyYI zGg3Bh9eFE2LL{D7C$^35A_}M!ICV_aKcHUJV7QWM1bL``AUIF47tk&6;nY#(1UH)j zeO$Gtc+R9q@1h^vmV_Qh!z2fGVSo8k&*uY;6V{%eQpm_sVBn|p$(sB+ji3N-8QJUA ztJ}h$&F?n|x@4xR*Amaak2lO$$b|mmg8UyB^*_5hsH^=3h#AntR*SI^`F(M(8HFHI zvHsGIAWLqr%eQ;CHL_x^dW<5T8Z4|;g}whI*aH!Aen`z9#qD_+NpsQbWBoX>zn^32x7N;G)t zjsY8E62#8@B(vw&v2R(dyh}&^s(mg3jt_xrOPOo-7uVNkA^aNnM|^IHtcMEIa==UI z6R!LEyT8)|+y3P2t`)&u5$e z0)Wg&D)=MO2+Zjd{^9li_k)1{5^Q%mRVfgJ%|rkrX{HNRy4}-D^_H|ey9(-e~@v{AN}xlOf5lO z6xPB*TF^v@D{H{x`-vwB91sWuj<@j{5c-l8XOg7jV7o=oC(Xx-14I&1lSY`N*scC= z(?&6D?P_1j;_#Uj-Yb*06k>HhS#Vik=fUH)h3b7Nc7OYnLDxMP^2+f0muyJK6*aMf zz$oN309t`Hl2nNa2kO+gBS2XBVxnC4c@H_R{IR#(1bb1NP)UXGxx62l&8^^q6%k<-@W&_TfH;r z%<3xUbYlXY^$S3K_rhxVJfQ|1|KUmgLz7fF?3Pq5+rYXl(1R>#JHJ^q>D{|Lj6#nq zJBW2JgZBDl6A#GO-LplWpSykiAR%s7dm;YKAw=@Q38kt*aMXo54`;QRZ)b28%>EVB zBeOa;dge3db&D;ZdO~3`n$FxD64ZO%5tJhlo+{ib2ct^B|NMi8(|m;iljOzu9&RA* zR?y^OfCoFx<}QU9(=bZWGDZ@aF`fev7i*x5g}1<)2b6@C3Pv^jJ^S zDl@0JTvh_vPD(4~)m5+1Xu}9FjECdVALlFTrAdMbX4*3<+*$RV!(v*U4nb5MM=^Xv z0`6r;&I!Xm6~mD_LSIQlSH14A9)GVZdBeT4m=`yU7o3056kAlsCaXlev^Z0~I+_py zcVy8wsNz}GRISeexqjD7C7#bWgKTcYKewgA@#V#KWfH75gCv4@*S!eZNpBPC7dCV1 zC2uVJ9s~D_$j+3S>y{Q}EFYQFnKW8yW{Z?`cwEur=;Wmr`eLQ((8KYWxpe&;S08S& zZwx{LX@yw6tw_8*uehy5#xy5*dKzJsT%J0OLW6tAT9{ut#19QdFtuUM*EuGzF7n`y8FXLO7af0H9D*DlUM zk1m9`ixsKXm!R)Ts~l?^tTsDuR?|b!A)b7~{(3)J0CUIF9`V@uvI%H~nGT{1Dm6xC zOW;@Nt%n!WIhclZwKIX!Dp+yr zQf2gQ=gUH29XGcg25RUw@UM#q#aoaDll&n+NF@=vepys2dcLsEsc>(@D(b%KWf?;6 z(PkvNvWENpebY$dzn%k+O52XA^Y(ht++eKkcbo370nuU^eD(50rRBf}?^z+tXNRUY zd(92%{%pe)Y~ZA)d&8!!R1&%G=Zl*McC)e8J#hDRPzZ(BG(P4RRVE4rp&J^!rT1q4 zcu^*}x1PT+tDjJjI}82|XKt;!cUbyNpQ`BY8@Rlw7yXuupY)^P(Ve;3e4agnPK~%P z=;N1og7Ox0~DE&nksWkx9^2y;VNVnEP&g z>)U@Gw#=$0c@oLdqK%K_ob9eBlY*RU9R@siCCfc_%xL##T)W66YL&z$qsc^T;9~(s zF}yj`vDCR9=>ZD3*f>T!*G*LD%GJtTKiDIxz#Ifh<{7f4+1dL}^=1ZTuX|xh;NF194*Y(SXCKZW333m6|6e=73pkthh>G%Lz_`MYRJSsz8rRMsO? z7IvZi4|(AK!;pe<+fyz?a<4jBD%epEW+y(wjqV|ttgYmk-epn@8ac_Qt<(ps+I*~> zw(~qc|M>EH^f%I_bKA^PmL>S=eQ#+mYN~vEUYNY8Q?}wxLIKkvEkzKgqP6O}k?YZtCC*k5(+vSHzMI{UX zEyg*quGjOb-Io18@?>aMl%iUR(S>4#TrpJX6=Czlb@xaYxH-Z49d z6Freh2V2)kK zUlWNQ_bf<3<=5cIpw|#_I!(Z;+Iy6JB=|@;HilzHH|CEdZhOXN@l7_b zr&z6g=DGO?V11wYoVNAuL|({y`ZgJb3v!^!%^qO0&H;a8KIDLt`p zv47KjJujM7>P!ah&I2>uI^4QcV}XgPYd>hi!ZS-YCj)Tao<`Z&I@P4AlUY)F1BPUT`h*xG~mKGl>;SZ-v&23FAPx}`U=}tMG3ovT7hfuhD z{}20yPazphyk)p8iB+Gh#B4SB>7!G2Ukoeg%r1Qi`4)}mPmI*5;o`yP&2Gnux**c0 z$>DqNwFtX4UQI5s%9T6S+qMAG7n&Kr6-~PqXfg0-f58HxK|E6kCpa z6ocG?MdFRqvO8jF@cId6KmRI%Jvr}5_4S`1H@Qgnv+2g&Y?JtsQ26%5PFftNpo!#2 zdYn^=eGa&xk=TIhtDAO&o8=nYCmrzXm!ymeBaeR# z#xJFH)}IA9ZAbW)et0iCHZ4$d+F)dgUsyAy^KP!M&tbO#|81`S04tcivi3>KDRcMz?MLcpWz}@PsN7f5`3@vgSLq08v^Gv>}JunO4bgvOF#N6;j#GXgga^ z=;c1d2U{T1yU*p&v3Qb^Mn5(x7m>b(vEn-3CuO*wFyhMS`5 zFbakGWYhk*mYB-vkL_R(qw3M`K(K#{#8pAI_TQQYs6V9~c?muvxp*uI|xsw`vaeVnPIHWJm)Lo!5 z7x!@)aimg{)UG|&9Ye^=jh))5PF(;`?TD~d_>Fmq;Ut_tQP3V+v;Z9@u@!Uw94*&X(`$V(X z%Aoy46ac1#Vmh0%JCADjN3q`=aX1GrOQ!>ol!Lst?{oVD5&0$}JBJir7wz%tIrJvC z6c5p=)1UR^r2mHnV7wpURhr1$?aCMMoXLrR9p~kHt&sa>TNHM+bas03&#_x9TEKb$ zz(Wx|xr4H~I3_~78uD!#Pq#?_g$-_LubuaP&cF%WCqK67rcOXBH{%?I+1L=~)!wGR z3TCA3GLfj-`FWBG7j9X?h5gjVxHNL#w*e z^$6k>Vp1iFGX}5h+op98P7wR_;SxNRfd<$VKvpR1^R2$UEtu$Juufcl{Ax0;4Fp zxl5^^s~>b8p0-VJh|tg7kJBy-;5oPxWJ%rcxbFcY$S)fI%leAeOqk|?7_TIOC-i5X zf|YvR$j*)_ye4j79bp#l`bFnHW6wGk$8)e^oWt#d^P?y)SI7%Ysw|R?lgPj2)%VvM@xrwltQJ)d340*_!OT# z+Lm2AxqsT^X&B*AjcMToScaaaMq#(I6tdu#Ili#Hdg^v%Z;F}5IoFY8WXIKYV~rvfX@WQ&Y9AvF zwF724wys~3Qhone6dX252(X&U(J=eG?OJc4k_4#JCqJf` zBfoxu-G=DVllUo`eqv&dO*ioor7u}rWc)7n1uxKUBKjViWc`nzAeu*$PMx_Q&~)Mnk{6Y#Rs8&%?mWHOFv(`HB|bwVte*V!HiT0rs_Qse1R+!LwSg^miy0i( zD*u8D0R#V{=ZeDE*{2srt#CW^pOI#8f>O!wf?sZD=U1*X?QJ zI=W7C=6jaE*A7|{)S3H-<}ZhZNeJ@w%V$m0q~OWynilrOL1@*9nZL{Mez(xYcaQPE z`yp{4Uit7-42M8^{wLmx{gzEbLxru^e>}^dC=PnRL*bPW^-Zm#_{p174(2h&i3r?m zU}%ip&9{YAdGa+spPDhSF50s#oceWi+d2vN0E}{B)vDOsEbx;q?u_s#juYY!jo z&b?N}QikVx1nySSK7{o#?w|L6;PuG0?LPjPiga^yqkS-0t<3Wh7-wbG?IY{OZTfcy zs9JZ{cIwbuy}Wv7Cdb>m=fFE~yZ=jaU^)Gl{&uj_lsh5cbvU#)4wS-0YaAN9`{6u_ zd}1U*>Ckm;?)`3@0{ViEPcD&Ap_Na87wryh^6PbCCxfq>g_1tj)X2>gMj@ z*#Uw~w%l2&Le7$ufhiZQOi(xo)^GEYu>wT?<%`U9wS&kVu$?Jw6joj^&fqwmj;mdYKHSr@fWCzCAdRp0ru{#r|= zU!AK5vZ^&$G58tO{p#y83mo71W>HiMN@ef+xq)H1f42c2Pw6w}yO@soO*NR{md{Ly zz<|1B_<-P{=}pHOMiqSFrBaz8A(z8)$00jFd|b6wSKK*bf-^Q z;MBe@SYrTbDTtRHaX@2lRI)1;0TV#(v1PtF2d4@`>h%5I^UO}h1>ig6Gt2`DL<`WR zu>++w<03;*lGyl>Q5!1^`Pak?{Op5CtwV$OI>uuIS2Vj)Ywrn`r|8Ddi*bM#Tqf1A zIB87yF_cJ`=-dEj68;RP9ZOR*-R*NM!RX8W$fQvc%j5O%9Es@GsuY>sJ%lQ%SE8GDJsS-h!zS!`O8 zh^%C&bF=vdcR2hg-ThCxym$H859?vKZP0M%z|c4T9CUN~I=u+d#JD}cX>neJswDkZ zJWd6HDnCpdN6%z~vbBp4BnDjZlO|#|d7o z^A5{ua5Icz^~G$pcbca4zT5H8o{ytkBI1cjx3xE7ZmSQxAiX4ibGOV2TsS6zS&dFy z(dp7DxZp5g5yj zNEu)H<9l%UN29*noL-$#+<%a#=!0k^j)MR78I!a5ym+=SBWsdI3*{b?7KU7mzO8xN z=;?jm705h6AGSPW6g2S+?0|JU!c*`exlw2DqR#Bf(22wGS#Qm5owD6$i)_2UEv$cF zX}{00yJBukEg*Og6dE9sPuGF7vDO{`jE{uxZAsH-5dOym5Inq75^iCByFtT(_*rea zyH*a~HEn~QW1oAT$#T@l>p2D1$R8`#`vAHY;Am@co32b|?Tq^`aDElHx0!EBjs?pT zK&S)^7{gO9j}nlS&CHiBa-5Vb_w!ZPLo|~JT7oLXrkbRQK{qsNrU!r9ZIGihb8x$TB*7GZUHI?>3V9c_z z|vB`MI%V+ImgZYv77MjVt|Z* z_z-+#7%iA{o6wuCBOrNUJPlBoKFzfq3nECjZH%cdPDvo$HLvXm|Z9DhN)U;Xxq(yYbfStIvgjl^qX1YWn&k zt+tF3=$ob7gbL4_hM^D4uLJ@@BHi=s%LxjiD(7VJW=&D|B&oZcmrX5g%k zek7XJ9Cj~6^ucF$D^#cG_*(uj6$X{wvi+Wdr-v8^X6gl0{JEQeI*fe^aeoX0NbP0m zjlB$mPt@BcE)-hy0P92)tu+JG~X8*--^(!TWA8i0ej)BcU+{wJn&-^ zlwuR$)%*JShda~nR;ixD#}>bKh3%fr7|gta@>4pt)}h5OHbs1%q2 z?!eK~{A|#|)EJ%g_pL0}xV0HLSZiiAk1DF=S(*sodgU~DP3@)1lZ7DcTU2GL(i5~i zGT`9?o!7Drxti$sZg|?Cv0kEi|0W&3Ut_H;BG5YOE|A|yieppm51dC!&<-VysgWoev{H89D%2gC27|;o=_;7zkdu6~ z`d%aCDKv;d$ZwPW4`xsGPN%2 zDM_F(2Y>F7r3nC+>j%!g16%a=X12wjFT(w=Fv>N!-lLs9cf-~cx3rt$0w~ULZ)1xZhrH5yT}$ zQJG<(;h#h)lN&&1g%&|9pgMn)6zmQ0EFn8YVaMg+ib!J{B77V&MfzrL_A4BTlL<=Z zKG?Mxl%$JApzG~*;67|$P|jx4E#89t4(n~iLfcZURMTn2S%adIbnx@WHw-$DBQvBD zUpXVw37DYgbOMhKXNb1biMx&(D9j4JY!#AmCO7;8lSEn9f^xbW% z6mJ=60o+#V>lJk*+y*tAUzYMC$$zLh;e$Ek1`tigA6Pu|O5fwm7`Yv?VtOgy2x0nzz?6#X>gVEj;3_AR{J}STUxrNj(+YyfI{<+%VAxHt&hzpl zwY9B#fIRb>8$VDZudf{y7K@^f+pX|M)2c6=pz-UF5Ofk9%dDfCZP+!?e&otHC(^gi znsD;)Fe^bq%qE4ZqD72?%+@o>7FLSH#Rp>=rnws|D zPBMisFX8eg|6vgc{xa`$PzNYQsU_r9qRvbXSZQm=G~?=-ALs~{CDxa>Sm-~F@+^`C zq>0Yr7}E+)jIyiu4uarJyKe?%pP0M+&4IcKAPg*~?#37$qIL|kEm&TfJo>%gJb@FK zxMsr)p>ZuMXpr;`Wjgzhj(C!_tH^9!0X=YoIwEaDeCt|{r$ONdpY?YLrm9tRb5cbl zQM|*VLeHfbef#x}lnt3iveWw-DA_5hF1ei&XoFtx z&>UxDmn>3*>rJbCH;5}i<~U8kb67!h{QNA{L#@;>%A!-~k`hz9scQh}za3Opeb9tN z7OqT8&a`CMGjvD~8b9Nxv~Q8yptUvY8b@@S(&KI4-!Rs$V<_g`dJ4jxR!*7~03A30 z^9o-Gg~Cd_?o&KjOLvR;)}$R3%nI7gO994(3Wo!_-i1AoJk(YQ5A#*tdqfDlOgJ_3 zTJPi7!Oe9izkyxjsUVWB{i-tO)0%-D8u}T7lw%=cVsc_(IYZc4}5Yv%Xxej#7WRZ_>z8 zt5>+gvb_8bhPN&2qT4S=Q|>utQF>0W=GMRaLO7sbCIVxPB@02j4z(ZmYGG!8jSH|~ zSzA}v?$hg-uC%mS>K;EwZJdAFEo&#SU7I5LxTKR7xe|dQ-ea9U)tY!adgtC|HXLZ? z*>Cu=>E<+=)vA>F2;O_S78#am$!Aq$v7tn2ya@X$?2I+B$!c%Du1h|>4dOARa6W{^ ztsTF?;%Ps>w4~w-VhV9Ro73r}*!}<{XJj0rJXLcjrEUtlv6X*~*_7z9T;PnagoXnY zckL#Xt%u+!%`-Xus!Jb`6*c)s-+@!-)HfQPEUuX;yGD$}I1dw8j6=6BH(7*p39mHe zqQ*B{Ab*%Yq6VDbP3Ga~=7v_uxOGn8_JuF~dl*utv!qnF@?4VQKQxnMJIsHsy6+i? zNB>ecGk;I{zF!nc3Q4tunLXBE`tWqfZ5LEY0cA4}=2~K;aH*7f@?y)9`}pN&6`$xc z)+yHXuU&{=fJkUV2xM6x%LZ1&AsjB9dDHQb*Umuy{Bg+)0(T3rCkWcM%W+7EiX$3ed+=Bd#}W(tsNZRf-k*s2Jw2Ti zmgoB;2%LfrMpJ6*t6Z%aD$qxjK{r3szLfRNZZ7Q>m9PE{FI!j2g4bWKJxulB+}sP@ zjw7Az*F0`7T-E)Y{uw{~eQ=GD9an~iX2^`t+}u1lpIKTWzh($?swPc*h`~crStN!A}p!rDR|o<{&= za5{I9Xxe-@wmO+e;o!yu@4V;#%%V~P*AgA#S4$iQPTQ=XJ+7-oG}9V`Pc(f5r-;K# zL}NcmoH#v%(xax;EeGJNRN4*~8r(xS4c167x^EVA@e~NQ3BXEnUFJ2W01nr_umpqx z+Vz=m78}daGf!ktY@j;7;csjru5QfvD8h#(%yiZIZy!B5gfzk-e>9gS4SH_c7S$GN zRcBHiib7pjbeiQH>%@iQ@x3OrY$mM$ll|3 z_xZy2ki&!!FNJR+x=m{hwKPo?ZAUeB zeIJvS?>06OkW?hGZO15?(~aZT%!)TO#5VV*BSD-jLy4GWf%YU4CnoPiL=(D3zqlgg%PLi z9h^^IZp{Y8_{fxXem;cDQO-Ej7giuUCW3UY+2$^P;CYkK%>2D^wrCU%qF=*&m&D%Ui4WuFLxWB~ENlW8z&Fw)vYW;u6ddsM`y69gwNRi@DthlDdrMQGbTigo8U5i60 z?nP4^id&K5?(XjH5FmJP*KqUx&pG$raqqXCFUiQr-YaX(`J2zWM! zp-;kbK^CQ`pu*(ln+0Lh(x$KGR1sm>XF-gZgD>k|C)Gl7OB*;(RcHY%ys)_$;q}hs z-UyGpKrK`9Xwv~jjFJU=3}swTL4?MhcPYfo)Ra?v*NW7-eW`|q(f|Po6uB~M-LiNljzxLy|SdES1=gGb@}d1 zSS*90Vy@SMt}VQpnkUOhy+;oqaOBly5OrISmC2Z}d)bpG3;hY;2VFJYo;d2BuJrI_ zR45Vlb9P+(E}I}5%lP17y+41=Tob(O!H&p=W6!&B`?WvS@3q%V1ZG2-%DR1gPUhZL zLp^0v$#rf?+nWZ}voigBJsZn3-RhcB$EF7AnA^%-Nu0<{CL@}3nBfa0bQ}X zrB-+03n&P;mPpT2v|CLTdJsm0S73mfnOX2Sko9NNCF0S`)6X-bjdJ=-sU0lp+RNwB zH*(`PF_8s=KRGjg1B9n7(`!fd_Q3Y;FB!#NX1kpB?U+Cgp$pvC zDLUvA6zTYhuk#krS6bUB5BE;t5e2yhMLTf6W~(%Q=h#MAthj3|KXfXNNj?YBrufX+ z@$}=oS&Nq-QUls<$(g&a!?*Z>^#jb{%pU&CLFNzWxzx`IO!yY$iU+aEOm=DYr3^FA5PUIHNC%GB*OY~6^Y^m zu!~?X?uV;t1-(nyWc}4_11HRoNYU zdXuc1HYGzpa>HQ(4qkIJG;|Kfv^{ZKm%Zxp7}Nb!(fUvvtf)dk*})TKdrlS_LH-m_ zL%|ni24Eu$99xwN!LD;0Mrkd(B5gD&jrINK*x!i0JCWt!>(E-&xyq_p&U1?x&mKMY ziIj@?SGcZiDXnSXQWq?1H?#E{0R9d%A6(R*ZI>DdbCS{_pQ{n1Ph^jTrc~}_%8xc! zA*N)FpST=O8lg(gp!@BO{n47YhiMs$(ypyX7lq~}{3P{=0PaY`MbX^#vj>xKlMH6$ z6nsduE~UZ!DU6&9hb%@*vsJ7%xJlS--!*+IL;&JFb3xo?cF^Jqvp0>t^T!U>zGFIG zmNrRWY{3w~%C*~`c!1grAk;k!1m_oz9ALlYcgTIy_RF>J3F*F{(j&8H1!EDp;2YbV zn2#iCyVt#~qwjz1-1IH2epkTyN{!#o`+25rmfU5jz2&d|Y+ySnks1tQIj=cy$j%Wa zmso^2C6*ExwLx2-+vQ$X4`^=3eKtTA;gd*+v-)Bs6FJV)A9_?r8Sl z_x`WMGI}ceB~RM^{2jGgGoiRIBbudL0(R(On#wksr|WK7K1+UF4UcA{RP&ad3%2v0 zBv_~tqsiEINoGb@%~{noAnoSnhUE_Uc8m3PX?^G1#!(SjjWjkm=SYWrEw^KcV0*4( zXDmM)v*SLoeCqBT%qJF=_N_!ix7ncKhB+1KxTIV>K|QF+VWE-c!@iT3rBC}^oGUnN znLBeOPn$6?m;9u+XK#A8NR9n;li`cbqqp9JXU~H|M&F;hpj#6La|qw<7OgmWPled0%DiOF1jiDpK0?Cv9h>2WkNb&&Im=ZHg{G z=ct&PmsPek2&Qqn;#E*p!K4|~B?53J;$sANu$FIwXWu6|`P2iJv)Qhr^D1Z{$bhW#kh zMNWOUrExxWHYX(zOr!~>a}1o5P5^5hpSC>mwje!xbgqRAa0v@@4S!NfbJ z=^|Svd?F#HG;@Bst^2t7OW9-2C(P(a=(;5&NRamK;zE>z34u`j0C3{L>hZIbaBsz> z77)_-+Rq2yU=WWg3cMi{*_gfDB(LyW1wCi>cN%~LB80yEmGBi=gFSZk@Q22vZWSvT zMG9W^{geBLQ}chv5$_6##K@i!%FU3y=axTp4e=Pfl!pdldcRVNdv zp&2k+tl16ga<3!y&5*-#9=MyMKE4R|cxCAzfNAiq|IK8PM!l4?jh#YRwN0RD*Q*QC zO3S8da5KYBe`=ST#kx(!zPfEr#s*Q*=kQ(Q#mO!mho%R1x*xwmHW<^wG6r;VDI#th ztvIWWwyd(El+L45H__L3W4iTPjr?3lMX&8rv6H-sgbk*bm9n_niIW~arjxo$0V8Ar zGD>D<)ELhJ>F?5B0lp~3Ryx09AHqD;lwNq$DAnpx+DSaj+gU17`^n-y+)OO^O0b$ zllBp%e2hy6z-bFs1m1G=Fnt_x*Z~6}j_BQ4Pj&CwH~3@Py9+0eDBc$YpI+b%%g<`1 zbKs&ZYo^BqW8N#GX*cwgkIOteEN|g4yOU^|ccQCGI5rDKHeb2I1iV1yx(xity<%hp$P%z3-1?{hSU(9pFz~uA|>&f#v)JF36m=F2;=q`4lcc9)4#$ zZSLJWbw|2*6QCfyr`-h>98w9pat$@hb2Mqi@i27rXZ%Gjq^X|;8@>4YNVR)=NVPrS znB7%o%6@8nm}Vo=M|+9L_*^a4Wkro>E@UW4RnTLx2A&THOS~AXzd?8?D`fe9G1#Lz zdA*K+GO;eCFw!|!8Fj_8iJP|N=jjTH{Z#e7_=hvvCrbW2LG+#((9_c?gy4pH7kz`N z#Y+~uX)s(%LAF=BXsgN6h3VQxl*+^9ban?{8FMC(?R-!jT zj3}433iQt*rWaN4VEc*~-?Ve~vxti=ux{n+uA4!Ph4F@e=1^D`xA`*6}6MUsDcZQE~6ulttIkt zfX2$W-C-2^rWW6}*uXoqSfk9uegaPx)DM?^rS!T_E^(&Fn@H^+LSKMqYoAm9%hSze z%+xWM@vRL~T(^0i96YoAW8*!?+Jdxzx{OYcgZSm2Q>dbTIjr(jY4d^W+q5?(7=kZ%;Dr=(JKkvr@FzO^rd@CgAe1Xf? z^bAnsjeEEO)u2eb))Cv<>CR^z>vOMj1Q9doS-QvVm9hca#OUzQC}(Cr#Epcr#Pcb% zL!;~E>=L=UFbn;()H^zU$=PjWgiYMnO2%{}%O9yhC^Xl-c`Gm;FF)ngJfuv~ZU?z3_j&L8OQvOrm1OdU4tS z2J#$s;>F79094xFk@ypf-#)6;SoJY?`4028uSgyr{!JgCsQDF%>G62|F*N+U{I5gj z2lDGNBVylr)mh$L83qHtyF;lN7iP-XWx{_W;bflg(;2oTfnKgI1ZiHK3nh2Un-r;y zG)_JlhHiy$2&A3}efr`<&{8O0s@F7pguef2=upD* z;t|_tE&_*z!sx-a=+lF0i&TOXUp*X}Du+S;DFdn3jOv{L@0n*y8|rYb(pBH5v4m`? zP#vBuRxUfU{H0kM?l*gY&2#_$t{A$Wh-Cjol*WI^{@h4^U+>3Enon44P273tdn>SLb$rbO-t#$xqU|ySiQa@5DJiDil>X3JQJMLPNzHF@N>Q`0-Hznai;nCO#SmqaxI|xLy94@f z?=jA@r7~;NK6w5-waz)|&DFmmw_$Y-d{Xt$W}G7}cM1NETsa5O@-7>RVjT!?2)rE_T_-&+ zp{|0o3{WTTDc66D>c{S8mt_P8Wet=d-N%C5f9-$n&ERvbQdgipa&vIMn)@&%E#lvar27CqeXv7Dnt8AbS`cP?m?RpN|Wu{)d~ zA|pJq#pOr&guk%%a<71mdSnAAMOsbn`PluVXiHX-{?B_(G~MA<*GK-++`?T#a30!v?t{FUBh#*V`|I~e)LYcp{OC|LQix1%3d zCf4yt0x5!bOtJPpFQRclBceG3}cH`)}`5IvgvM%&K(1s*c8qP+Tiz<~=aa3G{B? z!#G9e7e>y21ux(RYXjNx9vh_SkV&sL36t?vpmjQP*f*N+ zIKg_ebxuJ_%?PY8lbR6!|MJx0l*J8cOCoB{|4I)3UwOedABYHM+_1 ztBkL?HAkp<(02wL_xZcW=TEXu_GS@I39ps%+@E^AL6f@y1L)|u2I3(kNQiJ+^qw*e z#ro}^9$_ri4%2pUb1cY7pj4Zn9OnIR2|yP)p?%SJe9vu(MIt)HNN?bC&9^k}^k)~g}Iem@&o1$gasNwq=PK(sEwD>JI+pRz2*X4REJ6G}L z{UKTIhlirhp}Wn2bfbrZJn1B#65D=+%4hn>6Csr8iMwkRQP!OKk*d7*?1iB1=6;^X zi*W8-fht?te>}_5sZkPa>o3Zm;jAN)^$`8T*yy~cKt=|{dmvrqHPDhC965*X*snHn zZzF`}GE8(=ab~^9WtM!_vobft=gG=&F*CMUboi5(p$dupu*ujtcO;EM_<;Ef6buYa zrlPVf-5EGDO6Nmg5t^0qaGzCQCb_CsH>ag#yb z6PEQu)ZPae|9Im_yhS^!OR5im@%)pQT`md__DFM@q~;t!zTL=*sJN!q>+#c?BNIhz z9cUv1KA_!bU{_`vQ9*;sc3zG3l<{o1ZpVgFborBQq!N!D)ko1k>?6Zm;xNuRsf@vx ziR#OcZk~*NN@myuZaTFo_AscFOcS{g%jfUOr%X|+9PG77KG2F$o_A24fWj|_DE&VI zaM6Qa(V=*2LKO?RB=E%1b93v20--3}#SGv%olA-{fwbSBMW#C}x3V4NhGaI};2v|d zEzZ8Ct=-DG#MdljpiG5MnvvW4tX|4IYn<(DsFOWCGM~gqRqJ{xIU_w02;9!xd3#)@ zyrhvAv=|uje@u4-w0C_fef*pemrnl$DNUP^&cNNiIAhd9c(w!JwinX~41w$m42S?l z0U?_JP@m+7CnNIYc>7lSoRsgzvq|l`wCBDjd~RO?vi!e}_n=T$HPcHzNOU~*ND*3P z6%*-8coyatBN-G1LPxVU;{3wo4@sj<$@kHFs)SrTVeX+rGkH!ujyrQ=KM#{DHE_l? z4{K=>emC(@V_qFD^&HAm0hE;?naO)v*B^6~F>;S|>a3FsdA|4|E%!gq6e_$CW1@TA zj$j92K?nAbL*5`c#0#0&M-PyFcj@x5FpCk4l2-?&^4ny-i7sHrg_^J8R%y=aaA|~b zONYt$+q?;g{`vR|13O;EN3ok$&{m>m$u8m+p- z__=4UTRgKUw*ia$_VLtDf__jj@^7yoCB8$b$Ns<`lF4-EenJ1@rDukPRG8DSUZu>r z8H9@iz_~E;LHXJJQIDtRG1)Q86yAHvm64yhawW>Z?|!pzN>zB=N-&= zT;=1vUFotx%gZ7y%sG%{fY-g%iGu~W?&so$3L4P(St^*akzdD%`#i2#30sjRMOgLI z+8biwgQX*pz+ujPj$Y@%mVrmE`<|!0>0f>Y3wu1`bDkoG7LC_Si!3B?@@Y40sGSnp z!q}cM;$YJK&3(i!>7RI&j0^jqqlw%NH&h%o+>(gd4K3OkehZM`R8pbSe|Q>;giQWE zr%<_9y`X|Fy@tJrFE1pK20vmOnh^{2;_ z!~UZb&gJX*Q6~Q?yvlaub=M_yZnW^dy=ybQ?IGRCWqI%Z&W>uD49c2mJ3NX7wj{Z= zjGMI!mvomfNGo)Sq{M z1hm^HL=F{S9>`q$l;x8vn(mA%Pa{1lLR_gTIzMyQyH)O0 z7-Gnc62W4aD5b5boNYc*0h({0xGy&vu0lr4JMaD)OPZk-d6*?CTQ`qxZIGUVrt@}> z@ADp7L^As?e-}7`wg#Y{o_@m8lWTBBpSROwGNwkSeE?_jcWT1fJ=*_%5F_x=3uo!h zJyvtY%$T?kvl0AM?Qv?ll7&u$5sfzXjNL-abF>jW{oK0NTTYj`7bfv)@|jhu(x@}; z?6cj9oVRB(_^et)a{ z@S!gN*cwc;&D3**V|P3R2WuS1Ox8Q4x1MgX*%d~h)6h0}MK&~ZO&~pV$d|T$)hgGL`)`!P@or5VLOoP&azo{QmOfkZaFxLpynDo?jLe zPVL0)h^>8y_5A38YnGI>9p%{(`B$C$(JTKnCi$eGc{`5k8Bx$!0GZ#-(q8FU`mVBvMKM8L`fTH% zaPw4~LkGj(n#j*KCl=DP6d{05=X?u#b`RtGi_STM_D}8yGdH{kuQ7ffN$+4btos(& ztGH{pXuWN}d#Z8le*aZKbw!1*OVZ6$qz|VV$)qr&n9dVh&=hTzP6#>I^sbVw`t2Iz zfeb_=0$0qBa5Bx}p;|*!e6_{%AI@NVn_Kdn30jx*um+p*mM;Dn4(2F(p<-gw0bnxu zpI{l@Vv>rEo9eUP{F}CIK|9BJ?ah7BxYQ#3g;Hzh>enCp*+~(ZHeNmqdrexJWX|UY zkeN#gm+>!diN&D)0nyPn{xe?5HW6xM0*mqhPD2}N#gvPPu)X$d%ZU?`g>&frAC)G& zn^WM{31X6L+hh~+NW(;M@@N;;H5iM;(Q}IEG1<22dwkn-lwUZJ^`dM)Ku^jPpq~fW zZPrE+iD67`2BMQs(Mvh$8MikUA`6JeM*yt#US zZe(~mhIczR7a=FJ&QYjaG^%1s{6LjN$J*d}hbdhOne%BZ>p6D4Yos;oCzw{aBKF_; z@w5BDl1vAnaZT497OkT&LA}<^fPy~w`_YU+QoAKz!B!|C^^aIHsAF_(cWqfuc@ zqW3>_{L1xlJ<~B{GDE|6qArmApw)O~+@-1pJ(mvF4-D= ztO}OD5SL9qmTNdhka&c)BdUZ7%IajmF%J0zZ$krSXjXqNRBa}XjR+~p|vpQUDO+Ot&zra$l4Tt1sUKolzh6F9jMoF z`03>fxV2)E_)rZQ_=wUgGmr{`NG@-<3kmN{C1%#8+Vm7GX(_Cl^pt7ve4rpUneP(Qd!iJb4O#?Cyycw`UQTvF5@h^BUzn3N?C$j0~|pQY6*G@Q0Jw=|osCnYmvv zY|a!q#XC;R0?7j%yM6^T+jBw_A8shkAMR=~ZJzfxE~!wySVvU(8J&p0lSMc?wl04( z`^3La{{z{Y5kKrJV~6<;ZV}GA*EIOu0hLygQhinQ~%6Oek z*z2Rnh-?scLrlDqHk`ZYWW&__%?v5Q;c4JVLvIysj2kH$CI&q6Om)2j;vvYh-lwQq z$KIGjT5Ji@7}1+CLA$@+pLq87QPevG3t!7R4LtMqerd(t>Ki%#Ih)q1RhpqDtChoH z9lg#w5?KEgh!_9L-BgdV^2IflVIU!icmT&PN3?u77ZsH|P~0pWrGEhmz7`-46rYxD^a(^$|zxxN!el$W5X> zjapdY59==?(`UEF0Plhdq^HNT=MEi08xm764jBarw*UR^YbqI%D1>x=*&?7WQ4w1+ zo+_T#^1GvJ;8`TUTXm}g5UG?V%jc$uzrAC5H-t!5n{h$d!~Y}&{xQfMMYqH*w`>M1 z#}o4^_Q2-d_2N73!K!82%KGdy%o*bZCEKY!v$^*`lwS^DE~(ylMaB2zoZT;FpBRD*`rd7#GjEN9`?;G< z>MNAk-)A{aSDx>w8)qv)30y)q{l~qkO!{z=Z0$Sg=*wJ`Kx-Jj)8}}&vVrG9*fvg* z3A(~@HVb8ZQ`#8$)#k&m@hDtAFYn1+rtu$0ho)K+9T+ZHTo4m*+cuXRHVZSd-f`fg z`S)DI1j1ze2EzP$=dz}(uuHt&cdv=%U79aYBDTHk;kYpl@04UW@#Eqi`EOp z<`7!`G@66r4`HopB^vduo#4{K@MJF`S_3TO@%~XI^0`btVa2Qh;oP+0_jtX2b!y6W ztgP&r)0aNm&4mtpQBBi$&uWg!pj?OY6_xJcxe~=&^0`1!j|(zmJ_Bn`8>Xcnheh8M zK6vKyC+a1vsL-E~sY?l@Iq;^vAP`+kw;A;Wp^ah7&gjAmJ@*!5R)M6S6}kcM49oT2 zl{(d!Uy|_7YZ^dF*Ph({x*R>eV3YlH{qK;uORGuYKsbXG0yy6J^n?xGl){Kl^mYAf z$ZVrDu0lZ$j0olA6X%FsLoeTvV(}Le@*1~=5$#$T+pSI&?TPV$+-fkTC37vt-nuFl zLQ{Vj7Y5AWF@AIWy}Y3Q-2csj>w&hNEDD{nvSYZ=bd;omwbvl4{Wf&P{@7gj$RIKF zGAP-woswA%m$x{qOO_F-vdRA#{MJO_&pYkza#T}lqr2z_E;L{GJy4`=Km*f@yK}^U z>ZZD%>?<-p459g+Oc$I@Mvg!L)WBT*XGH}c<4`P6eLV9Re`wK=i{RIlCPhe}&~P#` zj|41Lll#VgLaNY{9e%s%q+%=)n%au=fS*46y1D#Un$6{#HJ9C^+qZ7pBM=cyU} z5Zysb49uw+wEx2bC`hm7Ew?;;L;j5Et&=pRaOKKYJ%jo?63yJSfw+KPSUB zqFf#@%8()&T6Mw;i3%W#heke|snU7Wv}8jt%c4Ko)RH1bKd5P2*V%GJ);SJw?TsZn zd;1d6JrJ^QoB-J>RnNUEA=K~MM*~W(+qEB=o zd*ePsWb}xxdVSq5&jA>^TF7(A z8JL!O>c0Q?e1EyaCna`jw@!~D4Lc63J6>^+bYhoK2x}l=Cut`nx#ZqgYn&XC6;++@ zL80NBszaoOf`Fk>*gmIi43q^QhjP%5N)*w}xdR&PpGVn-F1iNXqj)vga(oLxK0QTU zatHNi#%HphRvSn;tA@sNLIe#|=bZkXja%Ww$B6g-0Ak^JRBoI(?eqlhv)+xAc-4|W zAxaZ+t8Sc1LOzm0tHi&Zi1^E2G5$_c4u5{T8H6JOMdF|-5S@+1Aq3z~>YE3I?C+uA zMct{?IL0`OxPPA4&NhIrtym&05443Iu+e}Q7U!OJsH1gB;$0NO87iM*0xnjTXLZW zJ(X)M-BMBB#U3S7N?huYATuKd)g6I)o~4I>7Yr5Sxi;9Ji@FKeiTY~Xo9!qs&sM6t ziN3tth-KdT1&T1n7=8WW&=P&SR;Js^aqc#;cl#u|nw7}`j|lC9K%HD~sZudLEi3`7 zU@||A6odKki||nM>y!ICUh0Wwc$B4v>F!I3xL@tq%lcf@7CW7l zO6hs!YYs%{=6~4x@tu3kO$UlD!nZ8&+|5+tHkh!ZsGIo{SQ8tF{825PLE0nw)Wzz> zMkEfKs~@o?y`=jqDWRa{RKN3!eLMZgqhlFh}RqHL{< z%Oe!DOF;OaZ>*YcfbL+Y?*(Fy-ruL1GZLi9XCra9%{(*0UVh-LB)ccn$+~P}e)QD2 z#N7-&;EfFMuPP?7TL$OWy{9zkh0{B&4F^Yq9u~vQG4#VW)k;2KO(=McV^|CbnbRR_ ze$y-DqYqJjW9;)MnS6hi8&rfBqP$Vs#i*6%nZ&Bu|LJ?PYscJjLZ{oWOZoP^%~@&= z^Spe)!J+tdBLOyD}K7%5CQj#M5~##xWNlKT57CP({wz5dCaRc zC_fA8>3gkma6F=De5psNmi^?`EcmM~QX3i0l^F1O5{=Zrf+TX2xueC~yirrwT<;%J z&*tbQXTtupcTK(aWi9W~8;9$|y6Fr%YjfvcQZ%aK&k|nK|I-HW;zZNh+wxC)5j;P2 z5@nkk6YlH?ZCP1Be_D&abzlE;e1GpH+Flad_v{|s(q=pDwQj&~k9W((84ARoz`B(M z5@VS5D--s(mz8Cu6Dq~D^9f+SVRI)`wH>?hh90!(h5tF1YBI2FeadKrXB~#Fz$hkW zpdMuR9`Ig<^wS^KqO1Oomsi!$`+>`9)Q5BLHU+nV%gf5rVenpGOA_?@{E8(1*v|U7 zb<|ySs~_sIblxh4YwXn8`z%JK41Yf|!a%*@v30s}@U>^l!(IFw!9P%*h<|Z^UQ9#0 zsx_#GDKIRZ&uJR22!39@9m>u{UXVjx$5oEk))!@iUwQsZgdKokE;se=+DBc0U8S3& z&=k+HWj>g~TU5V^v|B-UT``*O)`+{7f?KZWQ~0^48f^C(SYF@^QkH&udnr!a4yEVQ z7v)^PF1T8`j4rt5X%&Mj%K9o066Lt zm=*dM?=8kL;hyfUGXNggOi7`!+HzXOIX|^-J|R_(&ZY5qdCznx`$}l%1?^?iR=3h47yzH;wI4o?@a(t?LHsnNO=6Y3=bko&%HL zF|HCLVZS@aX;>0DZC=7swoBX-!CtOMljF>9oIo#ItYG4CtPzcJz9??s0OYr+S6;8{ zTbZK*g#_X3u9WL;OpmCV=?SYv+N;CcAVKH4Pi)l2!k@R*au(ljmalk)oGXvj%^3GS zakB;8%`@Pw7ZBYM^_;~jA9#jacGrS)nG|EVGJhraKhk)=nG6^DXf+;_sg+SP&KKLw z^G^=7ywzj3G@sBojTJMvH$VIWe{C;Mw6#OsB&USwqho|3mr_>=0vcFu?_?pUO(D3P z#p$rFQsYZBqu2sZEym_z8Txq9;$g0+{PvZBI;R=O2uhF1}f`VlX3zvpsu^6!Y=K&B{E- zOQEGwxDN$T$SB2v08BRQJ-+)9SA6G@?6;!94h|DL+IXcQ@0wEPArelgTN z!)b+`$6cvuLVKN?e3-9#vZw4&*z%lmNaI$}f6kS8&gFM&sd4^wrVjz%ZRn zPO<@Dcq0*e+pEAWoaR}sWdngNvD^RQsgmrjc*TdqCjdDVMvfL!e+}do>?~Qo< zI)G$fM(=|TRPj1-{?1&kO<+<`Q@P z@?@FFe4NzZ`ND$cnu4^EMyP94x69BRWgtQW2vb*D`MkU=+mXTM_mYc^#=bE0<1z-8 zoZEwff)@NmcysSf#)oa_s*d`r!9Y>JC#J5h>xtJ+bZj1(-#YBc#65G0-Wa{BmJK1~19Ml5xw=bk6{NI8bgyMkhQ{{gHc%Z&QzF^*~E z=YrF>cpizL&p*}-^&8KI=!ea{RXo&4$!KW&F07JiM`F`+vN)u~0mcKnqMs6(4QpmR zheO`2->st$wK$1U3kFxIw7RPjT}r0{KtMQnKtv%bHu?s$V4;}qI{05cE9AI;Jg$2X z7=oI}M}&<#jFvd)s7OqHD1i!6Za>`oJ+d51$O4s?eeF*Hliz&njq((i2nTRqgS zn=1GV@aM12c%5}UE?rCGKLb1;*FzhJ0{v)M?~ISH%w+@qkfLnlmy4Or76z<&;81f9 zeqLJDj8J7^`~Hh_R-~}tR^&d9?!V6GG})bDr+-ROvVw6MLJ; z8MrLfL zrorE3*Q2yhprFCk6qg~F*q0ogh))TD|xCDiKG4uQxc2 zDI2@%KHv6wKFyMnl0LmNrH)Bj@H^l7mrwn1{j(e;#coH{z-iTl_M=UhDLPbv=~*4L$Z}%&fmYFnOpe$;k08` zb-u8&2C0v06^)XK`*bF{wboY6H@!fy`$~7`ozQBzR;f2IDv(kp=H8X}Wg`0xE|TvP zriWzT%%N;CZ4yDdZPuvP82r_nQy&KA_b4`(Ve<@1<8`Y0;a#7!8npY-nWiDRFvZQZ zQ{PFS9B3KaA!#=Z4=C77-RX_nIcx5G6-`H5qxAU(mH!`j_>Pi z+v6D{7x|mDuoCu_pF8qXD$NKUZv9W$nqt2zRm||`5X$Gj!v24d?XK|HjiW5rF?;aW zBgkvIONBULSI_B=1~bk*wN6sqrn$foM+jTI3@!DwrLit2)UV?(Ll?Vq3hUFSKV@qR(F{kyxP%L%3_C$PQ?5<_rpp#q?z zXw6rE(~j0pT{yqwWTU-H`}$`~R|ZmD&;VLA7fMJi4Ih5eyA4^9Ey2ps8PNav-DuIg zk8k&0j1XI_j@lujnXm-aFiq_@GNK9D&`MU!|rqP zg!gW2Zf1%P za#i;evo%03C5csBi(sQI%9Vkphb*f_Fz?T|)aNBa4 z%C%9|waI!=63z*9#hJXXcP#L!SP(LDf z1$f!VM)@C{d391HfByF?Mo}2P@8xJ7jSv?aAg;^s1zixt*wTBvGvU)sc8v0ySd1y%%NNFVpJKJkJnqz7T%|GgwwW*6 z;~o98kADe;rKrf%37}3q76-`|%lqjZ$eaz}-JSx5An-KcyU^r-fzuBLdT+ZN&rAlE!?e4FFJh1y33;vo#g1D#StqfKJO@#SSVif^d@zYztuN zsm>4LHaF>x(pjOBMOy_jOq*oAnBDz~r@=zbhPa&L>8uRieLtxnPH_=}Y_+xv>~C0N z%hoELQ$iQ0BJ*hH4FOJ5jr0 z=AuE;#=A#NbV-izq4~1SqAKWEDu^FlrHzcw;@9#^y}jO`X$TEaDV6YTmOdnpK4#Wk z`Ri*vpy2mTlx&5j*Cwl!=Ge^3xN1x|ew=8XLN{c-st_(*M~816zChEA@zU;S?g7Dk z$iaZ2Sqlovp9AqnuSZsWo&OSr|1IGE6D|DT7cGGSenVlar_>FS5momuAAt?4C~5ct zNw2)f|4RL&l=8U0*dgY==Op@QSRryuM~%U1SYAqi7oNmcr*>%Q+qLB+=|9 zG(^Z?)jkd{Y{v47s4BCahjQZc-k4vHqjKg3pWDf*hNe0j{(XVN&59eSrRZ<1E&DdU zQY?mq9P-yx_`4Fx@RhtdP zr9xKA&ubzKs4dQ4s)zsaD*Zp}!~eZdATC}I%Xu&0jBxRe!8Pl3Okes%519lXt?vLM z{TV(%*t6xUJRp0Ji-rH9NG~t_nUka6;VWZFp;^w@tYi`s8GLjTraIi-t~=_8$kD@o z;-B->;QUkU?=%wk_%eSE{C!0bBTW$>Cd6-BGyZ0(?)_wN6;rfOz6hZO+8loNQCrDe zjw(GoWx>26;$xz@_^gj48X)h_Lw80n1jLOAE0{5!L!ymJlBbM|TOK)sR}S_rAZlAQ zEKE+cG!M%#0zAa%=`|VP)C#TBFnaGN=Kuc&w!(h$Gui+Z-Dk`q|2h@XKTV?Dsh19# z40PN$hyX~g;psbye1>G2g$it)zvPDPjCNiQ%mZ8;9sO4Gq)h-B-cJEEJ0Tmr89Jem zlC-@HoqY53bEwD0b_Rd{%7%vu(7#z~Fn1Jnl2I9-UvmsP)eU+;xqfT~UYrfEHYD?>M;^c%yTMJBGSH zpIz1>P>y(;a_y+z}B9 zXAX!Lhf8U=3=TtPo()o{qb)T&JTfps!}?i#k5dbRvwY!wN;Bs#Vqpsm@RNgT7Ll69 zJn*CCH(%?_bzIJPpwrd?^fYK-TAy$=vm=^-9=ku{@MF6&Et(Pko(nyhaDboy!3f(I)Wxl+3muWzhaR1xtFY03-VSe&!9dG%Xr4J^W;C+TKn9#1# zw1gT$YGPO-Tl9O@PPUFFdRff}MkY%8|J+(8%95Y+yd1Ui+Vns+Z{2F&*Zf~Ry@gxT zf4sOoLCwS>F%zP5~CXijP7QP`tAFibDqCopX=JY zUUlD3|H}QJY_pyg?;evMYXoPntNOr(3Ab+AM_qKcE;-sJ0g0zq-Q#b>_XGa>P2?pS zfdY-Dl?sW87e{GVo*9I~t`SNH`jVwy8e-$)Yr~24>DwK3F4`fFY?*xi8V1_QHOzNK zNFIMI4*AU7lY-=Ox~OQ-=w`Lt`E&MPr7*j}09aHJjN)3=A4=scy77=1P5lRS4}D=? z+qAMM;RHCS-1i|4Uuo>dM)k%rXI6&kl(e7hQ(z%42bbF*?#fQT5fhK z)~u?u<8uGYeG@8hS{@5xs*B9GN}_1$b9!+4JcXpCkd$hQ@@F~rVk$hBU)(X@i;g#6 zWY|=a^NWf8XPZ;Z5&IaL;Qv~1)gynk`6cZpy5x#AvI}>w+IsdtwH~Q1xKK`g)MPQ! zr}KX@R?Im6Ub6L6Or8#~zqy0M;=sWXDq-8DmdH!#!?J)~ImqC7wM6oL%m5@ho%i^r z>R`6)See*cPtSo6JAIe$Mn&j6Shdh)zsu~%zlwZ#kG;z{0t7?jf9jy3_JjY4Nk}f! zuu|m5#^J9NvvrRrQQDzBM0zJ^q%~s+i}--ktNpN%vP(T zBz0xXRM(@ku+Dq`o2IL0ayfsEyXWVQN6Q9=Y?c4JXS=DwPz0zRPO)nvVVQw?Ek8nt zN$N31>T7aDEi3J`+*6<@p^pZSUoL~H%eh}%tOw!Zy01Ci;}&@i1axL>4 z4UbeEI-&)fXB>o;YuAa?w^o~bNC%foi;2~F^?}7>BzU7vjY}Ze{j1tq)}WK8{Ocuc z?VKVBR;-_$cb;Rt_pQXO$hO%(;gy+GNXUnqE8j-nwXjEU<&@nId($>H#Pz%(^FsF} zZ2y!hWpP@?#nAVO^;Zuh5a*A}Vx;j3{Cwl6 zjyzb7nyk32CY7;nSzA~;q~*EMp)rJayqHkA3@UTUMXL`pHjuP9`6>~22!UeVdXzxQ zr_8riyzrd${A#ZgCz{WNjrS%tQpA0CoIDWT){DsTFQ*lMBsgc|V0=ClNi%qTEaHN! zWBSvyKlh-ggrxSkLUBIA@%%0@+i`Bo60`l68SoU47c9B+90MhTu!zb2etW{RFjWprV=f14-x4bhWPC;-Eal{hA(fyuA z+n*%QpTGS=7ALzt7>YRk->Q+cAU(sQCy#^gu<~=A*G5W{K4+Oxx*yeS_}G=#QKoUW z1S*fQ&mQlqKROM9H#74`H(PTD!P)e3v8rVnN?3^7t|szMM#BJgiZpwVt~=cU9B<`IUQB6Cp2mr;xawi$WU%@9-ll z6uLW_PB`x1^e3Cb8-di>hNK#LGG-avyOfotMg`TbjJ55;tU9=;1o{2MR^CJ4PR|}| zCxeOpcu_?o2dmS+2By7NJhU`6(vyi)wG z64PHEyMhkHxNmrYxJd+%`H|>B1z*cC59gkCN2T3yx(9AxNr4)@Trd{WC(HWx9e-(v z`DZAmr-~TR<#0{?@K@y=u>rucap3ameF^%yeH_dxov(fluj%hYp`eR&}J`byoNgME!wo^A6wvNgzbiXddflX9|C>7cXsc0wW4G-P@ zKP9{M` z&HRXB@MT$wVt~YzX>^26$;VbL2h~dKwE1Otk$Jgea@FpxlT#v|D~eD4L-xQ(K}4bq z-_gRU0TtwwAKlQ$PiHDhjP@;!5uU_DHRVHxN;jox!jBFP$AjyQ{nI|$Q5cMg1DA?K z!co>|(^Jp?c_-6>13PQroEQ@2zC&o${ijMQN3)&d!Zge4$$)#Mz?4EYCC>8!W#b&Cg2f2Y1mbCeuoHf+_b^{?UC=yh!-_{|tn}vDGb~PGQRfp?vv2ukA*DpY6^T=^ z+(E0XIQv%?hf?l>SB%r~RAzF_5{mA{)a;uW*>j%^>t8B$r+|>SStHSbO z(|F~dqxsJ$4e!P97++V78m1jd+{*O^zc(JAfTqVp$=pdS3I+)ofIKId4{-iILK?lu zo}!z^F2nB$@S4H`ZQL(cGHL8kjT{atT=b{^C+#9mf%*hosWvuE6F6QxJ9dYLesti* zwbN~SZJDDD1wFwSa}cdpyJZ?BZ}5XC#Vu?X>i0V)$}F2y>tu0kXsH))$OSXxk0FQv zYPavPJU?dC|NQ8`X!Jxr;+TFiD+VH|8hdZIc?sz5gJomhh0@g$1%OV_PyO)^W`y;b zdk%ZISRpT!!4TmhwPLM5AJW9VmD7ZsHH6N7quO2m9P%sq!gxI6F}hH*5ril4TEDVu zPI*c;&X4VPn6^UZ{Ut+0e6iW)M%T=Cw)u7oL>p)Mm%%0KMIj+rQ0$^_ zRaE~{I89yiK6%J+5*oij=&)|inWBBfW`TKSL zhIq$!e8)NItX^+B+spc!%^s2!Y^%Oj=dH;;{btWHM_D1)`^2+jc&?}GnTV^k*iJ}| zIdSWH$)-wIhdrWA&e z)zv5Rz$ekaTEjdB+baG}vO=19bAwcwq7)hQZ3oW@`xX+yW@!bVZ?IPFTUe7)a;h8- zkZ7<9A46i^?WXw0^`PGW&=($OZc}LfhAlsmT}qh`ww(PB@djko_RC)ERVwGa|Hxdi zCl$&6kk~Hpg}Y|(LD9E5B{;)=q`NBApEGm_Id>9|X7GRZ@CJuFDVq!5`TDLM8c!9l{wcre zp!V_zAh_n#H85hfE(o0Pl!a|t#q>;>vj-z-5JD8bSCfC5m7jZ15J$($hVR&txT$k; z_=HDO3M5$xyrWx?+%O7@Q9ohqJ%2+My51k73r^Ojd_Www!?%cocwS3oFskBTq)Svj z*fYxRWKtYy!Ca|+_Pb|1--EA|I*p0_t%%|uKUAnkLi8>bUx&v$z6}{md8b;bH~3Pf zSk%1}`HPz47^pkcJV>Bhxw!m(5|{ob)2E7RNm%E%5#l$8_7t_|qTQQLCLkR*TG0KG zGx>9f>ax{R-;o#o2GPDk9Hr>mS&f$%Aw#H}N!JwK=5|?ia!TUw?xKYF% zlvLDWV)gc(2H%$(EBehj!z54Et}Uje{7*Z^dRM<6-bNC!2Sa9=3Hj}j2h3Y8>*ax+ z7j9TuhDL>7OTMyl_*^Q9&K}R!g{6HB%N!$`6HNOl94GS0O;aZ%KUU<+%kz2Tw7JR{ z!~n$)dACKBMGE`FS@pih^TwUn37yYg%8|(JYJQn?Z5lW*rfoTg6$23-bnJa6e7tD+ z?O9N%H0t{CC-H6=l6{!&`>vA!TB^j)$1PY6C6)co@wHS{V8!v#wXtyUe%uLr^5J@Z z`Gukgviax?YobfI-rksZ)AM@1+87KbF`23@)F}C~cz~uBQ-B989?NW6AT`5%pNcX- z>nm<~@;M__Zqc3e+288el?K-|L0H0KrP(oDdDVIQpPeaHgg~eIw{KL?BFjSM1_w-d ze{3IH@<@AdPrribC=&z8cdRL`I2^Cu|Jpnxmh?{M8p`JKP-9pIK{#x@SJEe0a-s8v zxt7p9KRo2DwF8XTl=bNF zSa0n)e&|fR_NDmS;%_9>q+pu()5)icrv1;)AQxI2OmjajJ;;nZfQWKHsFc@7<#Q4X zmk-~(`;&gmJ!a`LsPUv}@(E4!RZbDiCC=~Z45p%nkcbJoB9)Ir8R(9ac%;~uej6bl zc_kImXBEdFKw)J{W`#=FfyA-h^mTln%#$atMXrsie3eE&i&LG4sWUv}2pkGNEe5-o z_393tv&8cQ)=r9)nDfZqx3ATXz{|{w(HVL2V0lF8PD%n>)Md`Y5G+BCDyE=~$|?vy znN6McS7$-YXF4l&Bd0rpNvreahEq~2Q=2EjRiCIkHR}H$OCRU;yt8AzRV50v{CFm| zGBtJ@MxU^lSy>9s)XnsEhE6r;*6I<^?=a`}Z*t7({TWYWQfU-2kxaXf6+Qh__-A~S zT{4pitH@5WiAp@b?X*R3s*vZ+A9G{6C3-tWdIGXZT?fwh5)q`jeq)d>8td8CijJWB z8rXm?97o?ix}L1T{cJ?w2980q72~`fx9|9h7z|bY`ugrh&&%Qs0Fw z@koWtgzsUWVfJ5@W|!30#t-J;s_@q04dIx4Bw2%TP|-xsEUM48TCD2CM`>pBCsv|e z|MZ~nHF}kanbyi>MiY;qE77FsDR;+JZHR)z@=8_vMPv&Uxq+Q?zvQ`G9o0b=y`Ryk zBkMh5l-XP&TgVKNe6L8ZR`{2O=hVGlZpZoFC&#Ayf0vA$`Z#!+1d4ZvX^DI)H{&+GBSntTXLju8VVm2x`thUkn#Om`5}3)w5+P;Oes@9+pAR;BAOt+P0{d3gzksmNT9 zi!INo&2jCY$c-jsgWV{+#1KgW_g;JO4OSumR-)54U0v_n&efIjwgA%f9gAZ?C^nh3 zJ%Q}bO=}{FDVsxJuiA))UAYonS{^exUJZQShSMZFv#bNvwM4(j{wiyL@bE*~oZVJa z*Vt!sLZw^~cvBAv?VLQAwYlmN`R9JYs)lUa=EQ|l@)-T@H>Tnl7*Y=v&re@UJmV_) zBaazqrb6nIl zS%k^?CgUXL9hTfJhM`SE_RrsB;!@(1WIvjJ>DZi5<$$a44L80x?W*3JD)KqUjv5JVZUa z)Pn7TLU$P+sl4_!iW3EPL|*X@jJnQ4CF>b@a3hmBGF=3}FAO0F2THqw8VV3Y_G<}5DXgIp$X#@w7i-{PtH7Ja9F1IQM1z|aB#cM z-EY(GOc*OaTx`=Ae;6*6CM+o_Xq!Yz0`K9V!G#T?6e>as2X)Qz$OEg)53XujP3C zZ5;m^#oAn2At#RrO_E2hs335h#=I7TXF}jtaczBO*3gajQBmX{k{*1-2Tvb} zIo-7`8I4Vxv{D^03+dwpzUh~3cNLv@#4hb_TA>i2W_jAxdLC>*`0PNcrmQU^hz%`$T_j6k46z#S+ zP!^T750e-`64*qM3Un{J)sm@$0#bE&kI7@WNUmxrDd?c3e;11CBW+dYe?P_x8(ePl zIKR10{=NL3)FjT_XHW~(SWkPwu1PKiVn^&-anr-0%_vKB2gwm%?(GoYSqSoY=z&C+!fi%Fe_CoTA zZ{~JFoK}G^?v53_ez(gcQhQwupG^cU+u@jfUHtm8egi9Pga_&-AFlGnl0e`1s+eFS zL5{k?=wil?sf46c9EUDD!o{2+hvNh5*90w(mdJ0tIPZmKRf$_s{=IPdX{=)Rb+mZ> zd~g>Cw6zOnoD$aeYGT$Fl2}q)Y8$^kj=*3z`xdI)rK~+#f%EAivPVXruI=EU;KeIN zC8P8{a%Kr$&aahUdt^SwNj$+F{k!}uh%&L!uzmVu3<=Kvs&t44)1Gif&=6J(;ymcm z<=iAS7YOVc&mJ^voPlw%OfJ&)uB*D9-t>t96Sk24#SkR7e~Bmxi}6F;o?F7^Uk-7J za2#^8b$sclfO+uYX7g_l?w)8qp4aa_Ny-(WWzai`Vq%|m&+O>t4r99FzDMoWMY@;2 z=;{n&!|9OYFsUIYXmfrK+?{Z`4Oc_LTWX+Bv5bzK4zSNW`YpG{jD5^+hdRqtwqB$- z{juODfJXKxKq=hzjWgN=0h)&VEC4^j)MV{BWsi#uhna+fbWXvAY$$P8$orSuSz=Hx z=zw}1y<~{SVuW;i>61gyD~ku8T%bTV`2lJGY-y0t&rpHyxf+bSJsaQ!06Ew_eNNKI z^u2smWupMFp(Qd)B$Bd!>Pgq{BpB)0zFPPL!wSj^cka=|0DDd;4Sw0$gz)2&|F7 zk248&*s$qdpU>$n+vCAlA}&z%V3f0fVG}U$Gcsj5d&~D(GPOiwy+y zC8UMs?Ti>1&_`=;;{1IHYdv21#56bzCTr?Z*>^NFeN|W*^1m0^5|WzFHD5j7gJl6D zmtF{2`dqy1yQyOi=S4tRu_U*WN zSdvX~LbioIsIO`Uc9L*V$WhRr*NG=Asy&8l-TZlF$0p=HR-JC3#4Uw&TF4mf~iYWeGA6UnZIpIU{VdQOLed`}X% zV8XIAPQDAr?ki-iYd`7;WJ{b}r|@5=7QgdaaS7p&H6&5S=lu=-CWvo`4&UDGLsKXs zOu=g{4Iun-J`}5)O_TnV-^U3~)^vHjXh!?+ZQta@O!j?V+1C;ia>WyNf9=*=X*3Dr zM%*D&(QbT<#ye+;L0*r%<3kN@yrvb84uVl0#%_Yv^n&RKZb2oQ*C7|ilE89)Xzb+U3C9iXol#g=`E5|Jf%(7yv%$qziPfA3+E2GO z5a{EFu1}Mp&AiIGfV9aBN{eb&`zs;ZE$6DJPg?HB&7iwOPTxKJmfn(k``<49V+XXw5}HJy@6LQKsMvj%b#=i*&^X9U0fW&3RRn z?Nt=+C#Nc>hLGvQtqu4k1Tn(97T5Q;65lkrU)7wGj>^;I?NrYokGB|XeW%(PGD6UkxT(r#k=(=uaCaz*&Fkw+Ppm}C%N>SG#NMyZz&aS60LvE& zFKGB2oQ#p>4v*&iTRSf%TXc`L`;$zg+FtAzE4l7BZ=0RN_gPs}c%MU*y7H5 zIgjyYk2PZ6h{Qsi+xtV*z8RSA7G~Nr@RASmXi!o#ovf+nMyxHW+oV=%{$H`T+aL@j zVk&g4Y4u%B=KHnipkrpbS3*}4eZ#cW9vo3^nLH9?y*&8Hp{KF#fhA0thoP`UQvs9! z|DngA5ji;Guo72u#Dg+(7W|eZALMKa!O(Z;;7#IC>zy(_!(1=GJ5!qHBin053g4G0 zT4Z`VmwZr@Flz>f0q227VUBdp;dXq&li%ka9%#h3w+b`HuwTBjyX2c$JmgHU5Ebv; zn?Qn3(kA=t^&M))VjV&khlJ&Cfk|8`hRi(T$(C;PW>G{SR;&6WqgfOo1`9pxCK+~5 zhC|6UgScnYQ?Yfw@~-D+E*v5pUV8gQZ%YRDNCt0>g7070tgMT&e+ejssFp;_zfK5$ zUt9A19nPl$(DVGI&xv!JkDuTq{7HSjh5B~4wet2lXIH%PR``^+n=*6X4{3~LSRlVq}#bhMYR{?Bey#_d=RT{a;hsZyER{^ zk8U@>tgVBHI712#dX>$A9`K%{bmMN{TdikL2_dPdK>qH_f~U3Tn-7qH`C*j>%>45D z53i0~qAUYqr~x&|l=rx5$$^taKs?XV2RIvnx6NGTn9$=Er3_iEoRXH zNX3J`hZ=+jw#tCoAF+y5EQC$#Gzx5HfmnVM`fx)BX*{K?DQNRk>qiEq&Q1C~iI63W ztec?E*lx%ndGVvvZv%A}Oe2>-`Wg*#0Q7;R?zA54c55Tg52b+b9RN;&?jdHUbyKucqQwX2wwlO&l;K}X0D}Spm{QH>Pb@q z#ikDixWLT6UNQ4t2w(JXNLkCuM#ty)v_m+5AmRuE#XhhAkiCovD9ydbW2NR~RD>9Y zO(*1BJ6u2hB{iXb5g?tUxLQYjUk%srFz#dFePZ-I2xt?CHtG!*ehNki$jOfkv#xub zi+sfNU{C@mS0N9mj9{Ru(fO~fB^gjbNKb(Ad!rV;pkDdm^06d2q33Am&yXndng585 zH3RTGMe58&{!qZbO6@V(Kb_;tBrzwO ze+!OhPp*2bn%>)jsY9;oACiYL!iNji_PI(CbM&eV=H?wM0 z2asLrHF|VrFPR!8Odm9vYn=#H*fgzrMYSkUvazC+*l2-T_{KY)Ld?PmwCXG>^wU4f zU-00LDAb|@R04`NsItx~E)&UvrNdZ<@^&ZAGbp!Ey383CRha1NVYg2R8TV zE~tls(23mZihqVQ)M~tXUn=7XL<8Pa41ydwKuV662w7MRf zpxwyFC42$U$6xM@)}Hc1EiK1+9DvQQ)vKZz^ef|yC9zvxd|9nw-~K`!;C+MkG@6E@ zMZa&FD-CM&IS!T*yj(5W2t>Kn7HfZnvWCZl3mALdhDd8dfAm;Z`NcOG&rBsKT}r`J zpzuXbAVjejU4cE#`D#)jMg`k_)l=>_#X+A~qR20xuo%R5;~g*HCd44}kkpiBKr%Ye z7Xx+NNlr!w@oX`ibKv|5MGAsuGZ7T`{Su^e64P0O1NPC*LaW}va@kwxseE5*PJXLh z``&jrSx}+gv)ZolvA&OPe=s8a6H(&krUxki-#dxROsHi8lZ>2J01ZDQRo|^d`@IF2 z4ko+tm_9w02i%7jqfTYMRco57gy8uKh}~T|Zx9l&Wlhj=71zD5yHQ?YZ|EVr=~b;O zP$vJ&{f^3Suji~U-qH>X%cNu;BFOA7`X9rEM)opx=+(e^$&0 z54r$&%W|ClSdIcq_l+RBa8Zg~aTSq5D zM@3(F3~s`abuCY#f?K;$ZiF-9ifm(2j!FQJvfvgJ0yk0iyPlml-ov5?clSVsgTN#g zj+fkd_qEf;nrTi+ zArj650M~P%{5y%`q({umGW-dx(@?G7{zBJw3-B!(@b45sk1A-rYkZHgwlc(S6qBP| zn&rJ<7Xne{Xg!@MEv2yPGG7ABgN*|9ES})g5T<`BEmO3+Ca}aZUVXFWZc^`PLNP7L zqz^^`mhA>&l{Iu7Zl)hPcx=-YR%o`nmx8ozky&i3mrOZmpN3X*t_p_ziUcmPxw>(t zK2*g~lFa$F_MXM+ejLP;S-b?xg`wby8y~)IPV`0nWjTt@(dPfOe=%`CHDcS*O)m+F z^49f^AfXh^O6$CyTiyt~Kb>AmyAlChzBJ0I6{wnh@cup#WZaGjIuF|#v90(B@I>Vj z4{7k+-y>IZz2=`MBhWy0h>gfKGI5+_;qC0{{;JP{c!B&L7A8(xZDLsabQHRvJ|lLG zDY2x$_j>-1F)7rSe(3|QjM;CfIp`YG3w%}Y67+E{PaJ(#(RR**g!^oU->n%fiO+nva#aZs10d0^6_sE1|@GN-3&NBsM| zar4yl&N(8{J~xw!1Z}H00o+eYZs}nxhG<8n_UVc&AbLNH)@$C*tLJ?@W<76fO1yz7 zd1LZoO~Q-=Xyqi~`(T7ZttXfM(}2rB%-TAqd!5aoopct@1Bi0=a&ujgtlgp-4U|}k zV9=%fVthZD_OBLZd?=dZ^}WRuFm`;X~Gh&Q#z1P6k9<`x@|(nG*=^IR?_ z9o&2mhcP%~e?!g5F6q0)v}o|>?dDvMR%~w=`u#oh_3w_ExcQ#h;WL-+G`ferP#g}O z^jZb9w|K`(jSW1Vv!lGEo2dqV9~Y_(>i`Pk%GEK}i00`@0IjYbie_{)eU%4Lw& zMQBp=dPaSW@{J5u=77KtPOU~o1<@862#>-Q!(q9Jnfq@qt@<-lFpfE$A7sVkDVbAK z89PBcMWRS?*7eJE+0#E$<$NsXFXGDf2b0-rqkR;IUwdw9i<-seFZsxIlzAL}kusCZ z3^Lz!zuhRfGK<*wQJSul$s!Pt)f*^EcKF`i7io!{O1HH;&Y?+lKkU?Iu9=M*wR@Xa zxcE2fvG5{rV^Tq8xrQxwj6s0jY=V_9g@uhBCv!XRV`5Qp5*`m4d{?IKYspk$+-oTr zL&p~yyTJv9-lRC00*^+0W+NJ$V--}HE6Cb&@e`7ehEIT>qj`OtxREz;S;VmfZL|}m zPh##AX0=5aC$2#CZKIji8mwoRTv{w6CK6EC_LJ6_LseeUPjnL^4$J>QeIAuUxtg)* zLi0XO6`UF@ySPVvs!FYqr2fp>RHIlN@U4u}y; zT*;Nx3MkUgx}xIB`jFK-WW)g+063_OXSKZL4zpVy=7vt*@ezZtnR8eAY_%P0(T!c# zrO?AML}oiNv-oN^?AIRn$eBGac7Ox2HKx-arU$OU77_n`s_0p2lz&}ZiI+N}qQAAt z`Z~qc$(V$0r2E_L7zWlAN#1UA{)sra8ZWZJRj%&jW;WdC5sl~h87K#J-~VATz$##O zevUzlm{C^=Vf$A0DfLO(<~~MnjiuoylLql+b&JeE*VB$o%pxmOf}>Pi-Kgsd3cCEL z78@;*%+@;M^FUX5PIG@S4~u5!&k7s$A|=6{zKd{`g@P+t!X1q?X$a7LsRM zAj_JHwK|gZbt{-zf)bVe*r63x?CM zp5#&~vZV+`l)jk3?%rHiD^J~M?$sQ_)x0(yvN~q3U`J^=n%@+XnW1y(b$wt3bDb@n zHU1M`adW&(UYC?;bOi}C`Q)F+P(pLY6uEd4brIGxBH|u=FtE>Q&k0pqZp&T$=KMFd z-#CWH$BOdjUf1;wc?_mB70x?8467eX|ER*QJCG8PMl`UNF&AwK@A{f(1KMt_m{Dam z6GgAqOKSX+(K=$zU`?R+Flsw+r{%|Mklhkr33)?ehB|LluFAD}rEsiTS6Zpae3_wP z-#JiGs|*iro6mn`1MH#EGyZNzEd4Tqra-;2bLaJ}X_NxPw`q!QS;<$xL1P(UT>9sj zcl-iQUIQdh-BXpbebb26^+*Ep*MLJNXwZ^Q6z;d>fZkt)!e60W7>?miGv=?U)Q9G* zS3(4AJ@RIqYe@<5uTf8rt6YsO-G3zwwwpfqGg?rL^32H&;GKL(r(TbDV8H$~!1hLl zN3c}0s!;gXEUA_Jb~dZ59FhXqAlu2^)R|oGM)EpjI&wj$Ul7CUO+i|F%$oh&%N-wn z1)TR5C?3@#QGGc!kqErV~*R?rZgmRb~J=TOtbLzDnePUqp-w*vO&S%LHn z_#s&lq}BYyLS0E(cZ1iQ=slUQ{=tA3;mP z>CXYUN}3UVpbVcw5g$aQH%}CF=F_2us#<7h@NqA_QA&N6>l1$J+b52{I$FK|V?X@+ zT8ZcJQ@T%LAF51yl$G4&XS>^zr8J$nlM98Iwl>Qnc?!DFH~3@I(=uSUS=`MZUyh2* zCi!w8IePi~X>t$u@W@THQTwRT)V;Rv&vKVhYjUt7g$_S%Wj=?4-X`$wk1PGF93eHJ zzKdVJSI(0E^X;q}dU2LA}g_}IHD?dkbg{TLh4m84yB*-Q1oFlv55yb&||UM;$@ zlbyG)pF+r#C)jK6u~A)kKU@)Kd9@iKzVe)YJL&Z>dw5!T&~N2XsV98=mE!u{S*A91 zQDNL%vU^)E*~V4StutRPRChkloaY7(vF(W6x$Ek*JW_|~$2cEIcO=vgSC^bIv!_R< zve^4_c@d1(m7Bb4-uQ;6(s42dqRmKYJg)SyRc#i}`reW6c{J?H$LWkJ+tN6GPjU2Y zX@gB?5)Q1cG6@J_a>XtJotD`}9a5UEKcl135c}c^pPxcHfqx8wJ)7rw5fo$0pLk?o z9<3c6UTa(Z7G|1#5~qtRmynKUxe@x&K;4{0?CRlNB?~Jtkmj0t*vweLZX54;?&vZm zu7DOGS_EBX7oyy*E~v75nx|P7cJU{!c{jISbc%Vog}Kk)nv%tGx0=$FAsLPYLY;_P znbuRTct8I*y>rC#cp5I+a^Q>h_FtnsSNZo{xa4?UJetx-v#%@FG=WZHQpBS=73q~7 z8qoC8-}hGmgZM-YA1Lo+?xFX|ju26PTuAyXke{Q@67RCzvyf{h#`{VAusfJ`Mev-8 z`_luVtf-q3DU-~;>~ed~`)?!5HH#p+syM0@ie70Hl2_O3DRPaqvM&w|SNtydloTk02rNbvQo zKZ7zK&h02SwT+m%N^7Plg5Apdzy@*qP=8zAJI?@b&(jOSz}&E`Qx zMdf>Q4`qi&_+L1-d-!=f$26w5(DFT-!&frB9u}MZ#L`$axjAV%cw&7=m+u9_bQNFcM7fI{+qRF!a z=lQ4!py+=Ccurn>x?dg^iy9x6(AhLhhE)E8>OXOC4)eQdmhj?1^>fnyE`0ORXxuu; z(Jp=VU8p>`le4+d+`W}MOwexaC|N$)JUP*~*nV29es=ZrR)>n0EK|q8_9ynK#I_U? zm8E5xor{#;Uump%KBrg6<=AT;FU;QShn(7h+9FlpYbiBt?N57cU3IuT!!)l6z|`-z zSb6<0fPTMbe2^B14#H_`mBX$w#0guLkNYiG-H%%VKMT)?XN-H%!$)t==e2u@s5t1z zbDZCZyJi)X-Pzsh*}rSq1`QAsA}Jy-BG&cEIDQwkPw55zqV(;c;Ac{i9|_SMQ@~F} z-b9V7QwgD0j&{v-bK6n8=If22d|u|BwU|p>9US4j|Kd77#}E=ZM0DnlCrPK5evzeG zL0$eTcya56zf+{O;+?sU_<}avuHTN%!u?H#mn;-3iZYAyB9ALYR-(BZkh;d@Nb>67 zsJVX(PbhMee}8~Qo7vp1RjZ(=GKU+Y_He1#ITHkue|3hxouS56v=dB7txRe60&kso z#j`6RHT4zlw3_X?$F6IU)}VxGxSQ5LMnr|}4Wa`{QGsYr{iKpv%Qq3fdZlDi zI|6gho}w~4R0L!EeoXq_GsTgjJ%hc7ovkLY$z6`Q zF(*qt2kEEoaCBnz`HE$uL;?S&eCbCo<{`j{kG6td8g1?DULTr34ebzqZnr8bodywm z5|+g9DU)&T5TK7wJ9CUlnP!)IxzUb49|z9ZFFt9OjAtBaXc4`nig$Ir&XB^X%fw+= zGj+9&-_(o88B8(YgKP-4oReVwC$ZD&dI&aN#7$-oGaR$D1Co_>3*o-3JN*4S=nAoL z-blJJFQ7EQfMnp(=1Y|ID$bIhf9Ol<#vD-TCNSaRk1Zo!^Y(9aDQO}_NtZ?-Nu8-l z{3Ew96GfHq3r22$36ZqAvzcm`7}l`-xYK)EDZf~EY|ILzL+VVe%Yc$h`EI~5Q<_Vb zChPMs{!r~UJJ^D(u2ko5ZM08PO+6_%oef6e1L(O^xEPLUqbJrP)-SIWDArUt#cS}< zlR+0V+Oxh$a_68#^FPQ@9mLKx0uo6~=K40yok=c_6#IH!?%*Usz2wl+a1#S#bIpz$ z{f7S5%646qCWm}3LdFSNepdwcViTv}J&I<|s;55`9EZxDmKk@%AgygxO(BeJGc9as zw)L0mpTwPddW5W3_GAbys^8sUIs986(lqq5JML}$o*#NW1|Q}ivzB6xuf9hEz2j=B z_WllSOy0O08&;p$p`M9d`N8~4-IbFnHpQ5JK;?n<1q|4Sj7;(WC(-B6g_hUX8CCV{ zY(qXD?Mv~v<-0vk$_qp@!G7>1UHBse7d00mh*T@OrzU;7hL7UAR8kb&SfTr_M!yPt zCmSu5g-oCX8CRB9nv5k7nuafOZ{62O-HRJqV@6*de zN>nO=P??&%L7mKM*=X0%qAlhI#r-dI&FmpggiIj>`34F7yHgs}Q#jOjKhhZTY8BJJ z(8iU2B2HjjxUtl?A`UFcCrn<4rTu(0wmz|+y%dImN-gkQFaHW3-JMxF$4g_5Q}o{l z8xZ}vm8eGaa-+TR-B0g_*LYq>`xD3QpGOhQUtVi{D?GBXZ()8*l!#hc3N&(Utw z)F{eki)ye*{PMz@S2CSP3Lc-L+6fu1P&p(X8{?1r43ad1A&Q*C8jbU#n~Xf)no!qx zA!2?wM}{+1MG}pKByh)ILj7*_S=8Fo4xGwYz@s%h2(*#T7?B*397@Dbg5^C$Vv4u>(mi?bX3OFH_tcO;382o(Q!uCam}lgHf% zjfg|5`yqb({w#l@TB>8>^1*(9^uqHj;B7D>-KQ0@Uwu+v+Kwyp*G!W*(jV(I@*D}QRVn*|)2M8aPCUh>2b&-c_=5L{1| zt?6NwqJiyg&|V^Al*^JKGX?t#_q!>^Kds^COLXl`dos^M^tesaP|i$&w0j&3Acldm zn`YG$gP6FB3|y@Ufo%2V5oFeN&B#(J>c)pUKr*OXbJjpfT;D#(nCdh?u7ydjtq5`oi@`^SP^=Q7IsnAefGKAQ&iBF{QG z(rqb@RJb#tNA56XtYYvY5@66~Q87hLnUOWaVm7J3U&*Xdq4hQ+XNBb}hv9e{CWjw~ z+aj*0Ol8)O;2_*#s+0;n%73L}A0rFMfRRpD@he)A;WQh~Qk15A*a!G1pTu`3W1tZgPuXd8o?wWML+LrZ#S@g+H>w)MY zC8yljsW&1JQg$BC*c9Dr!^US5#dm2mq+snXzwGeSu>qOhFsp=o=g_iy34AY?{qJ_d zdP4e*UM&o?rCQV}T>2tD=VP)!a)C5&nk8JP*ni#ZRh`64oi#i&4yyg60`e?dqfSW; zRsjzlf@PXu1_puQ*W8MMpj3Sm>_B1XozN5$TOU@mZ>g8|*tb_*pA@4h2HKZeeI;7T zSxpp)NVV6upt!?+ajSr*<$Ga!!~eY*D@fNxQxMM%c86Vm^-CW6ow5J#M5D(9U~poz zjlJ3T^Bg_=bK7E>v35m#*SbYb0Cyf2kOJe$&A*_zde&?c44Nm1`!+$z&DpU@>OJ~u3 zz@3TzlltX?TS1>zjFp5`(AY$)&aEOF@dKfxlSbG%gzx`PbefS>b;sVk zO*_AF#vR(uS1SKZ>e2h5hw?9)R|9J5-H+oM*jb5+> z2+)0kwp+n?lt+nWAXY)yj!|lXkuR-Z!L6AeRlcYp#nEjW0*i&5OtSn=Tl5>p`*pqF z75D(lAARWAa*r~PO-v{_FasNm%MKoTc^|>ua=GuR>-+wk0FHNY`7s_G-9+n5Nd?b2 zr!(b7{nEb@)9k2}>i$2n-ZCn#sLR@g5G=Smg+qc%fIvY7cMri`gS%UT2Z!M9?(R~! zySuwX;oN$=NB8Y9zQ5;B)fs!AEo;sBOt9!>s0^9;iUMe+zx-ZSx6S{T--PXX_h)6a zCs`5|gVLUj_|B7kG)s~l#CFf)Cx60^v&avc9fiTbdvylgoTMcT7*@7?{m9K)$4P3n z)Q|miceJRac7dY3j;^{mDfP#A+{0hkI&>`meh&Ei;TUZ!Jv7zL!Bu=eufsyii|5)c>=){by17?*~;FhWb&2Fm+)whDbGmVTr-Q1GuAX z%L%~WuU}`sbtK3qI}bB%6Hi@0Or_d9Zy601Lt?hXu(DMO$0m=Sw+eDGek1=z5JI$H zDmxm=HcFl8DXomcnd%&yh0*`;-HFUz{pV4AC6a9zZ|B|kGwol&RVy?azW56BXnRt!gJ=GSQLBH@(g$QPxOwH?V%&e!{vlx?OiLPz~>UsNX zft`FKi@~}C$|wu$8JqA=T;5-xl+LcF2%O8P`7)F+!M8=ElOKkO+!XOj)xwX8Mfz`S zYP^9VdNoe&McPox7uw|$&53X-9u*y|njgw9RDkx~2X<&k;TxZ9=9RA7b+MVSZbaru zMRQM&0Xi1F+IX($Cac2zRCFPDlO8cxC54MUC~U{6`;Qp*LM45@|GdFsZQQ8*A0jGT zmZJ+C67J{}kqA4v&eTzWWBQKe3hzifW8x76+$;QbGrkZpXsc3o_MwEt=FGQ{ok3Af zU$$^tR4#<_KV8iKfB64*lX}KQxKDzOrrEee^oUL$oskY<>{1w^{DZu0+%9^RgFPcC z2+cv~pXJ~BIP?2miN1q;)GB2t*bBALD)B)1S|mwFy6)k$iaI~jr}>qTUY^uI?thR;zJ$x zzF$hQJL)4Kn$r0mgz_!h^-mPz%!R<%457vn{hJI_NRU%DeUDMWlJQ`?^4c}tk)&d(5g}|Fh2^wHfg>(YqHsnhVGch*FBlrcc zcmUPMN;P1V1bZT&q9*FiO`O7J5ew}Ro>4pMAw_sNW1>gP)LnP6;@QHN8OwWIyO z*d`MnQ{pgo8y<=lfdqRvE3l!w@1n8juTSd!P>%lM?m8`FFlrek zvu2n;BXvyd!$AKRqg49^=f4lK|Ib)^qr&kQHLR(whUlYilVO*rvY35LY?kI{3&WCE zKV4-ad%NRkLfk@}3MBvPv7z2F!T9PP4yHwJ$;$@G>m0CZ;}T~scJ)}!^Rm>LYw@)4 z8x>$}h}(tgufELu)F`SXk+<>6xvh~Q6p~bp6Sb(B4i1shugfQ=^m%Nop`ptZ;k!G0 zZM0nA`xhPm!rJ*HUig-|BE^$)tnPL}L+yega4mW``)bHe(!tS#zs>e3q%85r)4nxd zU`(#AvKCKL#W%Ms78k>E9NWe$*6X?BE>30G*@5@55OU{me0}Giuknbz<}OkMX}+-Jb5^USZt^4=U!36=p$3y|Ya>8P5?RY9_Py zuX}y^`RPqYSy)srX%&UR&l$uf+pkJrVJ3o0lrKtZ3agy@=nb+&_msjYQ2(siMXysR z>?jj$#=cd5D)oZvqXj13-m)9=-Uec23%zoJOXf0zMDmplf8jz_E_{$^!w66abnR?- zAw4oZ<&7J(teBt_3uK#*iA)+LJ`61UD37BIM(?E_frWa6CP8r37cg)_Q(WkgpXB5% z6aOhoU2q-?wa17jznza(DekamioT_0E^`52dquLxLsdt0VXg>O{_joZ|4smXz5Xt8 zb{GmnN@)y!E=%*760s)8_)56qfDT#EA;o-#&;4VSP{Jmq__sS}Doj(DXqwn+TDzd5D-Nq|MNm@CTaII+_ z>~_>WSk`6VtxPwKtU4^b3nX&5ANXZ~yb6c_BR8hX{pdp0{+=hDX}vi(4lzrIOG`rAl; zxmv={rVmVE+Cgnv@#1k(OsNjD9GLj+z%r{_>-q{|?zb|}BNig^k09d<-h zyXqb-m?eT98=>TmiNKt`8ByN-MjTxvo)Er}bJa|*oEkk8YF#hJ-67`?DWdQCZxlR! zZj5eLhlfzbl9VBdtLsj`0;=~jh`8c-joRyah`1Tgi^J;SUaNLG%IW(O2dzjVCtqOs z3JwQ(;)WHGn>*XL*ikgB^c%H+5!UAKy}y2*xFo@G(#pSYAF^+O;i2XQhZ)y@A^JT* zLui{dc%>o+SpN{i)H8v5XP2iTYTyQDP*Cn3+2&!JdjwJ-d(G2~g4k*#>H?Y<-4B)+ zy03q?w~Er-h2!Yxk3^rf>K&cjJXdcOcNcR-DA~EH^!y?M)#y41hH>$HXmUW-Pp#P- zXK-NK6`vaJ%~u%PkE&nrPypk4<}KGox@06a?T{En#h;L3IN6U(I|`|SoZ+N5;tWc4 z(V_PMugl+5DLjGp4vB6TA|gErV;eGf*=_6+UsRcFy*Zxt)OrpV=z`8xe7FUkR;Nn` zdVKq&@p=R>fY|7`x!X6a9jLjw9%2Z@r76oFA9~O>ZY)k};xhw2l)#c5d^0Kd{}mYw|>zIubSf66kjdl%H+yn7fE5xRXoYD-1^zxH!VO09Y{RI*a0GgK^k^-xx_&ZXtfi z4c^bT+$?Sc|8))ieRb5a$o7 z=_ZOf#8c(qXyWzGBRUdQmn%h>*dCy}MC7sJg#UVL5N-x<Xc-<%O|KbuH=z$fQkTU^ z7?}HYSe2g!1}CXMQZ$L0P?aW;EyDU4IEEZ&?W^l~mALpruTtt6@cNjroA4{22rr-S z7S6Jbhp@L6sl-qhHc2o({)4-UF!M9&Wf1n*8ma8~l*(f*KR~NYtby8zI8?81z%CHY z@LNMvhXI_2#C1N+1;yiuo=rA)&iw{VPp&hAY>|V_tzH>0VBJumSQjUzZmtgilFqdv z_RIQp0Bw8}w)pNJg5f7tn6tdj26HQRM8d={d}fY2@_xn}Ki*NLeyzU0cwfJ=;unxLd|JzM$Lsfl)f+y=-1p48#l$CvNHXzIOUG~6i;NXq}MlqK0>{10*J&tO~P zfyrln6p?>qB5q24e(*(>9F+kfX=hUm^Dfr~+DH_L2wE0Cn2nf~N?m#aInV2K$7hdz z3k(@WoE30GB!9lum)m%DT}gZ4l&ihLOoR;jb6($j+?+O1qCb8|5&@PLp=O=^LlNm> zIzEX=6lxOTVzr$(@0aZ7-&*33oj?LY52pfMoe;9h&xvhBGIem;o~mStn}6GKccNU! z&98v%XvIdMUcW54GIsVm>yjqtlVH1w!YKQ@Is#$mP4+K3?b{8l4>FyfHqd>=Ku@mo z_O!ESx89J;&g0|P8(Sh@-v*2~tEbKqy|+lOw_n!Jg>z=Mb{TvM5ISp8p8l@PzOL~f zm*l(sA11QISUu@6P5Oo`EqJde&t0NN`15NJx4#Dn|2zA{@myt`P`QBN`lt27w((De zZ4qNWOx8~sJoY&k9AJr>@N_g}?1W|UiC{|$Po$eZ=33WHEBdx`D&L8L6S6+Ei-)L& zRBlh+z4ta}34Wx)c5?cZ#9;TdAEYAnH|SmE`nYXvvmSZ<*@Ex%dSb(aJoy{Q3S%=P zfA`T?9u?pxc)kLCBBA9s{f#adON4+OD7Uv~L|NlWSl8rQ`mV(BVBFMbw{kte-KUJ6 z)2zcRNJ2(3Wf1(%QYIFbtC&2rQ#_G%5+$(g% zT+g5-$|x({Di;^ffmLw@{=>+AU)`^n{15zE%B`Rvc{2WhZDq=P+l}+%Th9w122GTn zr?=IE@`i$yYc)RA)d*?t4Zh9hE5AfW8S*4E4qY3ir#_?gtJOk7d3bZ;YO$^$hTwyK zMYz2};tvSqw;NOxx&C>*;`rz&2Eo_&{#3Eu!&!xZzGH#x*VRHjxi@3H?T_k-$k?>cS@HQ)_Z;(joe!Api;|yrSwx$jAg; z9RFP=hmrrakx{Us>f=d#?hOyl`B|=Mx(W$8c_zXM1-^}YWbG$>@op--qsxc_p)&I% ztgPA_>cjY&rMLUGodT7Am#G&UHeCgsfInHky$@dxM34YjI`Ukb?pu7eI$$Beg&FR2 zsP5N8b~9s*0i5Hf#DcM~`duf67@m)Czu^pSBw&CN17E%bLrFa73zjcT8g08##*b0b z;D?vorbm9_YlTt;3li^jA({=>jn{0=xe{*Hj%W+6%?p*8voptL2a~%9ex@pUAyGJA zvGopos&p+imbK2rmea3ldU!s#xv<{_k}p>M5D}I+uiCYfIKNuXv#BnhJnPU`>TYO~ z`cC*C=;auPM$@~JgXK)Gn6_+u)}bp9`+`0V%)c%7+rW$wdb~(C$Byiz%4XFHK-&kXWpTzfnLB0z4r0q30>EU3>M;n4bL^+SKiu_ zB`P1m8nvQ0)i|Z~Fb`cT7iXkA-AELLiFhx8i$ia&$U+f^9Lq$Ot%}xj)Cmj{BKeEe zA*(@5$PKF?hFPZf&j&>W-QQMAHG!P+AOU?ph~(CD-Hd$gb#d(uLp`uE*dz;3o0-eV zZLwT;!qCc*xx{GZRL%f7iFkz1=QJ;e>)xdu(Mq2S_Xkcj#N$nO z&m_HepGEEC$s<%EFI|I5#lBMJekrgxeX0fZZ{13BjQ~&b)r%6+b=bB-0q01N;BDvG zPsg*>v$6{Aal$aD`1~ifV5XLK_)m!u=`sGN@tgg>*7>^a9*FWkU;&oBuG))X{{Ryg zrO-My^)^#&x(F^(A@RV_m~$z&>CJH5W7@S@MMm(3L}u>QO9q5|pfWi>qW=cc3ijk; zWesBtk6BN=Lsl^!CAM2#6d8_6BpPGH^Cb_?oAS(1IgwERf?2q6$Fp3_cL79mfAqF! zTAoa@$#utB$`r7z$a4)@_ayagWYsl7=;~Osv`;tsubS+Cs5T@p?}aH| zOs`9zw71i-%Lq(S#al86y3fSd6#ASXzhFfw+(=Y|#4Ym{V zA9c#&0}ub+KSf=t2beALh05KNN5zapDK9mhYp32FZ(J{to_>qjFmO*4l`bh8MXUQw zLHe}l4SfbE&b7|Fc+V|Edqg_#7wq+4l16OqlSMB0Jt7KZ(5PTqr0o><3}9&<%AzzSAuvU zQTB!#y9dL`BS==L^He@B_bV6btx{$-$uk2grtY2sZio-+{E7&{h@h(?(F7h1-f$7W zht2fT#ueSXcQ-%L@8?OL>TZ5H3l+#Z5CI_zN&$GgMH8hK$l4kGa+8Fu^gj@5`|;vV8vE2PLx4#@ zk$$kXGcrOmeiDn+&BoPL+nDHy>g~y^#%iQ+-aeLhbuHP*c=;qgka8gOw4m!g)TR|f zzF#yDwcps9TwN zOm#Vuz-&435)JsJ-vdkdgg^?o$9%lHZh!OQ|9uEy*W_7uC+_S?vsr0~2v<7TtpB|L zUf}pRET|QVD_^4v)u?UKrGO0933{{1fsPmt{O8`X9=r?Q@IIO8=~6Q0z;|?tFkeK> zBP54_D;u?=9Dk}%x3IINrLUUeT$yWonMqZ zKE>Kz7ParP!o+3cWGE>kg94@LBd^fVp%-Sv^?Y6~+Y!o{3Z-A(H(0LwH6^{@%_Kpd$G1l= zIn(ka1V|+Mb0wQc+{G4u~!x-!1$z0_4yq5=*fz!b|Y=wwBFwl+ZEMvk9sA{%kp! zAn6C`A0BRDtEVYOL`7C!sX|RRHKERW)l?UQOO=U0$GnZdW@O8M&H|w^&{|8FO(Thw zNR=J;DWS1cv_kG_H7e_=aT~EH8$813>89Ozzcwn$(}4m!w~vZ>>Yr=3x}?-{#nD74 zo82Pjr!FWd)_o4haNrBARh_72ds>*UU2}!uthqt$ z82{K*3%k|(PVKu;__{cS5;=879hLPi4-C@2IzOsZj-A|iobtrjQy z7fPqC1B+D~&yfo(pF?R_k{rWmZ90A!<%-?xXG87ZrcvAN8W*9T9rLug)#9{vul6Gw zbynty{h=x9IYN0PH^-&RxVAh6Xs=B^;2^u$S>$Q1sP=SiBIpmXe4>_!_>@2Vvnp

Y_2vBKgdIe&EXCa-@L$M#bff-z?{3CjOGB#?FDCI%_j39wqM(+ z25%B5!tEaPi$~NKjgk@YLw^jY#my_5eR<#8$g|T30|W_@S;NC@JL4IRBU)X~R$c+m zXi$qt7@ceqWusb#+v_yUsrtCEKs6pNQ!!h{k9c2oypzn=C4#bKebnD4 zT?abmVhp_;l*hdcWZS-)NDt~AsyB1{jw}o7T&C?mQ1N#TtRuZ18nqe*UW@B@vb;Xe zc?5f2itIjG?ha1P>ptPxT6Xymt0?KW%e{&V(-5%!9DVXhxeO+`hkoD5KL$JWF zo+sNLi;rUY7bl-+Wz)E(o=AB%^zutt^mo&2N9^v14Di1EU^_y1Leahw@RfFNGcF!c{@e$^&*O&>f{wA@JdXxeh-P(yPwRr@%@5X5EFa|?R$2d}X zPGO}Jm<)ChE_5p`1es^t#u*_SUX{mRt^RcrZm;wygi&BN})zDj84VI9KtV^#Dv zT8u(JE>^B0y^Vc7-F3pa4U4rP_8`}9c`<|sojgBd2Okh$hC{RrFWwsIjO_)o#i+AG zW7pXsBZB_6q#x;-<`dHtVh0fAWKp3_(xGmf;{r1=3%74$dKo!6rvce}f`+{eQJ zqrw(X@*$_rvs-!HPyZ&yuaI*EGa@~&p~h&^7qtNxN8tboLvRopvb`LRCJYWXqdXLj z|Bc=Gc(#}SqRmJx<6u0!v>6(_W5bEFQMRHF8GR-+L=8#WSIInFKC>cG&=+ml3H>r> zS>Vg>Bem)^QXYCPFfL<`6XUiM5$fO{%bAU)Y54kt7MdneNPFH65fsUjzhkQW6kkd& zD!TaU_Y9B>Ih6XxpaNyiJRp&HE;p_4TW>Rw8F7AkVpySmY%rhWrtIm>Fezh-`D)f} zyhIG?lSx!qwLj3gA3x4_VbX45qny$mdG8j&SZaj=UYBk(%+k}NJ6Z#W4l@JC(s@|3 z$AZ@Mpw6>`Umz60h6TDsaJ~tXD4jv?8;3Z z&&Dj|C9(Il#}a7?XCuFnzMg3w?)$j|^V%KHs`*42Je1|7Zg_wC4WA`O5v5Q!>dUIo z=qFE*P>#|z={?)xENt5XKhXG>rF4d{nJ#1=S(&9Ul+T-`Me4JW(N<6rTn7afz zP0&4!A0a0`1PYmyGv~C1OmtrUX`d-zo@%^M#dndiz5D%i1l>i?G{O7WVz^CnLFvSrFYb9QT421cH~QTIJKEbcTzeO@cnK;tqYn6*?k?d87PZ8xm4gP3ZDQv(1k;(5d6Q^%Zz zzC;>w!iTJO5`m}u03^9q0>^>tXoHU4K!UqEwk2E1(dSEWFZ7QaO&B_8kODBM4soW9 z6twNO#O`$ioP-hTm?C#pD@RfGDRxkSoaaP(hMP3Yb!XAhgVu(-MN`OFlA+=esWihs ziwqqTNB2jiA`{_L^hO74%v=?2c_F|Rt?TuXO>#TnL^}V(FwXB(^}_1!k2)QBvNxYFMW|z#A?(QPjAfZNC%&FJrTgrbSl;v+;Q^;8=4IYV>g9s) zEOMEx0N=L7j9jCxlc5Lfo0;Jcjk7AFQX`O27BgCCK9!ZTPFAzq^iUxoi)bEHGWnI) zOTv=gF@{gI;wL9%P)R$a!Ba!KZ?%dm4+qRZNf%h}Z??bBG*+zCBQQ~uuK#!mej=*b zmmlO!-KuQ0TQ74*VN?@UqLO%{3O$-BT!Fk-v|dQ1<@*?%vda&i*aTLOmuc2(HlM*% zl33p0D4;epO#q+M#InfpyicnK&K{xHKItEoO8i2AVIRpqe79<;zgm-=@D~oWW=zL$ zSdlAtCtg@ovMq#L4!9*Er5J6j;TP%6$537y#g!vYZI+q++_UFaj)8DB9YIvU7q?|}NAIDrC>yDAAk zZEBz-!;G}i)W@MGTCFb$frSu8%k)GAU;k1Sd4aJfgZVND^F*|O?Vmg1)w+wxp&oTt zKRm{t%Qt0^jYhM6qhg16>|F7v24JsbJtW{JUEn#5{C-qDTUP-Tr;Py=Fw$=LiVYw3 zPcr0D|HNj4G4AAGShch){OF>?(!EOj8!FLtUSM_qkgB^w8}dOcdM(3Lvxr|~<^{gD zqkCqDCV;>kO~%K_Q)hMMKG%UDv+1^Ggc*Hjy0fs%1pB?wj-8}D9KaPx#B-4D`+H6+ zd`fgiMP6>Uc!7d4XKh<@?TeE7N!%&1aap3*z=I78_kC-+k>lmgtusXK%=nW-CeDZwY;DRC-9VYU2XWVjX-5enc^=ka+?e~rVq@Qc{+$y9GX z#=(L=!Rb6A`j86gg{l$wTPxH&u76kQb#7@n!?=5AE6x0HOOPT0l<9gpR? zbyk|NNXXp2;QFJ97*^&jz#6omq)9yeVXEcwG&S7wf+X6!Y^ya2QO1uNLXi zS@M3IcwW@4=1Q@Pj@yj&a#jhj+y`|c^vn0781}6YULJd~4MxtMG@)v%B?pw&$9*lF z@|C*AF+5nAM3!1Fia&m{*QCJ#BBc1t8uQbF^a%d)o>n*V0*f>sFk7thV5je=`XkZm z58zP63x4QU@ZoVbCf}!xzt-sVvuQtUriY249jJ66=R|x4|PN zl6`7DFx&2xf)XfI=@0IKhP_B(;Qa)yaSCZ{u+R?w(5AF?BM96?osKhL+@DmYQ>bg$ z<+s~LYqgzc{Fw3nOB0|@Pf9U36#RyuJdg?EcSOs9X8#4L6B8lST^)GC3N3WLN46y+ zvJt`n4n}Q*rQ+7>w5C;p8M;SNpba%3A+=wk5ysP%U~m;}dw9$vBekf8BZVCq=>ldb zOt0*}#eeJ~BM0rb-_DxoZ0b%k3}0@Wb6C3(E(&mr>#$(VhJ2bfGN6Krlnh%3S?t3d z5@DCKWt!SvjlR5SH}SHB_;1{QQg)6bUDb4cPf=IVS~7)%dtqZ>b32gmZq zsVeK-k9rx%ztRC?(D#%5*%#2q|mt;w-;aXh_pM4=vpIH1SKVkzz!|lU>m|Q({OWO4i_CWlx)S(s! zXPiVg@70f?GE51JhAE=%f+)?j3A56M8WLpgO}$luaf5SqILITZJW;(q%Zx8Ymw!dM z_9ygcrpo1OuZng1hTxqad7Dcaar_k?6xVhQwZddA|YGWI!pZ|&|{VLqEU)+1VAs~IesIuqy zlKJQ)C{cNr*p?^x#T;zL*TaWg-5Byt?J&3;#RuCzf_Vnc$J`!Xeh9`m@Wv%~-#)VQ z2%aahwwlh9I-IL53c2zU7E2oGCk0T3d8+7^R}N=;&-BY?EipBn)Gr@J^!)> z*?>=X$~zIF6OQx}qoGP}hIb;kpt0@!18{$!REc^}ehn+)dyV|+!ldNNf5J2aS4al4 zoq!UsNnhn3wNcD3>)u{x1!pw*z9@TBK4U z=C*OOj`NhEV`ROJ?E;8^w7KoMVmNaD&=|+L&!`0c0B+iWq2EV*4d=-IPjDg6a3Mbc zm2cn6@bztA$tTEfckWCl5ZvDF6tu}&B)PchqY!MoWIpFpU0J(1_x#{|a~>&wRTZE1 z@cG?CYsD1Xb)!(Qe%0#Rf>l|d+}=GSq*MOYb;GkV-w5>s8VOg`mH#n(1}qdx&0a1s z^r{D~$_JsaoPK8C-Vt88Co@^RM(wjTKT_<2@mY>dlM$Ss{zNeUR$JAcTT$%1;`_e4 zYMcf~#o_WdjNwiR>vb0nLy}mO8sH&0N~^*jdm9s~vm=!y`}n0qVp>E_bz}&=qxu zPPWcD(&Iz1(eMXqkbB<1_>=o~D4IVFhcHiZOFEO1(=vs4&}%J}>B#7DaggEa6Yb;| z_~_af5ih)Atg<0f!)T?_uE_swUwuyH6|Rchctq!W)1cwRO5=OZNjA|2lT?R>+uO_I z*)Yb=BE_qg=!&|35k{5aE8&0twPcQe+}N;t}_Y9rGRSz0Xt( z#XDeF!W$B;N&wR7>u|DlYwb(z7SO!Iq4?_%0tskX6bXRbXVYfpXeZb1y-Uc!-^sQq zdt)v;z)3fS`xj|sr7HEPUe%ye>R_cvEBCaKCKj=zT8ZY|Fn1SS`(r{gfyGizvmh=B z7@k0nyf+z@XJb);kF8I5fs;QNU1tIXdUQwG5eE@WPk&;x#?#7LPvZJkr`!TpKV-1e zo8k|Y{SWWszeH(#2roMW z!OxSVk8mvoR@i)!YF7+LCjua=ChlAfD}O2M_eGvL@!$vyzyn;}H`AHFy1o&nt&qqwX+9KY1u=*4O3$NT(+~TS zdd2soLoY|qyEX=+8P_{iZ)+nl;)#hF$GiI`doZ-6g?5nUTyVP@mnw=>NrJ18^Uu_h zq7WSx=D5!Ej9@EVp3K;!y>Kq9>ZGETj+81D*-ySmHtOXM%2QD5Ma#_j)C7FuchkFS zf+zbINixbhi-m96_RySm%LUGe%Px;cbV-*U)*}*)>8o{~`ACut(YW!IQ+t=)`khGL zLmPlvdy2b{{^(4Hj|G6666Q%NHY&)nKhZYDSI$*x$aDIX;$J+2_h?pU2G7M*5YIG+ zIJ?tr= zol%`kN1}A@)7<-^IG})UM#w>b#}pn^^JXboDB+uJ$;lqfT|BAL{c`AlF;ogukfwR3Q1q%ZMd`o8A?HO zM6gMN-MI_V9g1qP+q1J-f#?y%^pT#d7RLd#!|UD0lKvIShd5)?b(|O4SGzEAkO`8QnFJu3Yu`7&I2g$q zKt6iEhI!n7g6my1l&>{)*J&f|h>69hD&u%cVy9iCqWjtRY0o&|4p@r#>si~UxSXu5 z5WrLU49ra*V!u-9_n`l33q=DUol?FIjO36bjXk(Im=<&5#ZJZXE-69G<=-KK!*#bV zPfd4btOE=D7!VIXMl0G=U{oOeR@d`gLaOa2Wn1>d1~$FwWLOa_KaCIinK@17<71kQ z23deFS{(oSdhI(4#UL;hmI3TSi;N6%L55Zpi}y#9*|c*7M~?TNn+YQOK2vOJAR8Kupw*SKua{9Y^NImgp2G@{pHr6^ z2M1D2g2V<{4~?$5G9m=c?K^$>Bp-GxvidG+0M`& z)4k`6*rD2Z5deWy!NNhB@S)33G281gd=|6JIkJ+-bOaN}J6Z(+=5tg8S$`vqhb76( zO})lii?dD>6xY>59VK9Ml-_mg@my;r|6I(I+O0{?jgr37$SxJ`B>}KT$Rq@zk5gPT zzGi5l;)5QIY+;+6kRahQey<-kANGAfTf{Jpcy&wS$9|r+08hv891YSficEAxAc%El z0RARG&a|#FnFl5|G3}UR{75C#T8p>o<4fy!gU#|XZ zh2bXLULbsOEyq8>aa@oe$aUDgNpH7)(JKQ#fEdn?cqy$#q=mjTedQXR_noam{pO*? z#gL?r;A5(mGsLM_?Sh~C0p=Nq`dev$#HQt&WFMX60)LufYYWy7_qqc8qLG7OrzP-! z;km(!U3gio%nNAM{@L1;ZA?LlEJ|=*UvztW0<3bfI}wQ@UjVyJogTw^pu|7cXGP=~ z^ER(N>+!JuTKIW#Af+vP_+X3yQn%i6G?djOc2=qB=ma^G09P?k^>qEX7D7g?3;J49w+%8nq&-PR8*(CK5(pdb<)_rZ%vFJWI2DigH zTBqAp>vw-368GYy5D1WR;Nt#=|vfdqh1=gLt^ z+R|={1gU4JSodE0V~}M}nmbC5qCEoabdS4BjMTJDl-TMh-_~mF1S)H|o0Q?N)QF)? zd#oAGDa?F`ly!37|M~%{76>1hb&Q*Sju9s$#8LR+I1)8{!W&8CEPV_Rnv`5n{wTr2 zPbU0Pt-rLVubq`sO>_DT#Ym6Dhy)Y@_ik!ufvD|1H%He2$!Ef%mVVJ~;iWn8#8qFx z!t3jKDk2D8=})m8+@w@UF?p{x8X(%5D9BRSD3vXXk-K|#6y*AfP!_7U)M%?-?U0Z4 zDZ_Vf_6xL&tray`zqMa>Fcl0y#&kiYVbY;+Yq2FGm%6nI= zwBC5tf2Gx#Wz(5ISb{N%NPnBO)bLff?>?T(RR6*h4g&qK&qMDHA{MThIWjxOt*E&B ziXGxs4^xFZ-q9P%+*8+A<>Kh;aWHPM9bjMZ0p_YlTuZ!^BP6QX5yXW+h>Fq0vpvmI zZ+0E%=PA9N&k=5R<`;6jbYrL?T4}Mz3CaVLK#S$<;8GlNE8$|05k|D2{e$()g26fB z5Z??Brp`&n%WNAb&5jqAphoqgHt!$)kk9f5CZx>3+e@%e|E#P4R3~Tr%CsZvlV+X) zbgzl%taM%;Z%e=J=T(`r$2x;XMQa|@%EWEgH?BIhOL+|XrOO}ir1Xx7wX?8Wu8Zc$ zE9sxw*F-b{*<>75JsSI&0t_CK2*oD^U;klgx*@37I5kK7LMkP(ZR;-UQ6UJ?V|r#y zbhh(*0wg>Wu2gdTgk@T%e%%};A!7a-<4jK@kRdi7H6cXbae3Vh6wWtadve~&S7IGT z=}kkOgJC*@E2HlcBhbO@Zv*lT+5|<|4j(!8#Vfn(m|S?bR|nq$`k_|(bq)pmq8~7V z6;}HE_H;o{mq#yNBJxx!woc0wb~v$~TSI7m!w|_L+Z-0ySw` zcaXqnI}ul@8tq;Ye#w6&u)Ao3ERZg~qZRA%iWEfVRhG;ZkuAF%k|rP>+|pu;8trXk zNa-@w&7;&4Fx$0yP;P8xqeFS4CA)(=_$dd z$paKw>S|#ofL#x0eq*6bGsm`%SZ2q2)W0$2@r%H0W^Z>z#tU@i9twnmuQ%HM*@ z#hTRO*d=bSHcd(=gTeUY5c8`Z3^4tXCFRB>p3MHr%b%bgEwopMpN;2F{XV8ATo)9de#Za+GLn?l36#x$qytd> zO}#AJ-n+hDrR(caWBF_v-4WT<%=-JiHE!7Kgua=q?Rp}@Vsx=JU#xpttwl3qYh4%< zbU$^p-7^#+&6cRj+-2lm47lEteV2nDLKb~1luNaFRM37UAuFw##G!kqH~J za5z~Y-NQER2v<{7-Hgw4l~ihB6gNr7Gok=7%i((8Dwu*%Ou*;SL@Vj0dv&tNz+bW1 zuz9jz?pL-+DR)1vdcB1+L*ckcC%T%j-F65f!R-sVS^&4TLJVWZOT&~QOavK-{ofZQ zSr$2Iv!-Cl^eoT6xx7#!jW-coxF@$?Q~MvwF-}qic_gnH8NwTcit8yp1EcKqj#r+s z=d$+$a0BZORmF2A7-r{Vv3JNQldUT*B7r_qa;Leiy{43%QZNo>>B34FmM-QVp$^IZ%lCP-ds!F_05CG4;E= zA+Jf6ERsKw!@v*4ikgS@2wx+^LwfDB&+Mj7bIG3TEEO-DYGHpLFHar8vf<>79x`=H zqp9?X9iMU}SIK++ihbJ;af%cxbRuCmq2l<+wD#IgGI$9B&x|8lqQ$Ps7du3A(V55N-!i25U-S?htNc1e~G z=XooFkf!DOd50>!q7uCGpkfbnj%dOfOKn9PYb@iC_uQ!Af;bml=RLfFnx2A&jLXqb z66Y@kPa?frWq%-iXC8ocK2p|OP7F$r-H{D%XM}r zIqiY#=@xLk$2hRSkx$P(yKmr}K*0yn0q<6C40eL<=$2q92S_J!TBdz5ciHTShtKHU z7<5YNo~o^iR9hPeeRpK<{QUEvSQT4k*5bW93>yCOU_xl&x_In!k|t&=)MHWWmC9vJ zEI;dm0Z9$_H(5^HpFh&6)NL;VQGEYvehN@2s$Y~rD%=@R zr~y+yrLcEv``X^GDypJ_mc?Fk0>YzU_w9dwfFF~`cWj1|B_sL*H5;XaUuTh$BFo@a z3guDIh_{EYP@1n8jc#IEL`HKGM|u1DNOB(h68GG;zOd3#+McJ)v!>NxBl7(EYB9^z z6MprPv)*_}nQu*C@ueEtE$Z1n*$41IF`Ii;~S;^#_b)K1dX7A6I z5ZPFL|1C_E+(tH7V1Qg|732XmViu>IovTpOGI_uw49Bs<6}o^hr$3>wYZjN_m1L?#VJc9~$4@a}VAP4(`4e?5S4pj7ea2A?dK zKe>K7f>>mJwVSKaDFkZ+e`2!OUxA$pn#DCUKhN@@Z{+expKvUM0ax*e?(NiX9GBC= zR0EP4jg?~~cEh&N#;scX-M5}5S#Ag)A1fAU9Wz~uOpN!iLJ%paoe7sxaB}sqyP#W* zjh%rO2~Xb`iFKW7_uTz-^>xYCNK`RPcF~89wDLOQ&lQ(1^v+`ZfA%aX=d8YTvrx_cs ziWFlqTY7TC0Cvih+Bd4ezW8e`6nf_E-aH zgw|8r<%>PLF5(};J2k#VG5^8umfDf*4@>{KS~>V-?6XnZ4y8h4Z3PLmo3w zVq)MIZ>k>c+3;#hkNkoGk@$SxbctE&>BM30Y|R(jTq82;F`F%3ChTBlVWm&b|G*D` zx3E2t{XY%(#cl`G_HEb7cGt`49MU9~eH+qIlvKiv4ki>8m>6PoIU_t@{&~!VJw7;= zkq|{av*|U`t*$;-O_oUL5zV#1x*0#mvcr2{!mj?l?pbhFTq3nxKQgtW)S>7N_!8;Q zV1b0bJjs$7BGY>p;6{=BOt#l)n9c$wjNGhoPu+ogVHA2weMI8=|ABMvZp4#3cLrBC zS9ZptK(VAvqXD z;d5ZoVa6o54}d|MqEzl|mlBKNeH}~m+bA%>qD^Uzfa7j<3eQcJ6G4>bYKHSXQghN~XQVgBW?HqjC z{wgfsib!HQE^GmM@wc%s3V43%+5Rk_ko5gG;lKk@(!}E1=q~T6X`Vc*o%nMX2*6(tAtV9vhIz|WQa+{YKqMq-!XV!YC3u7Mb=O)(Tj ziVklfU9n7`dCr4j%@@tZU`kwj&CYc)rc%#8)tW?U98>LB#Z*-XUx0C>aC&O5#2B9^ zDvscbqeEPXxO=5?JYe(ewPCx`^J<<)p4Zt#PQJ;{QzlRSikv8vT_F>Ke|oVl5CL<- zP;)QHpQ6r-dVSKtj4zY;TbVUSHpcbT1v`LK@1n-^nl5kn6)n3H*;T2@#>j*)6U7x>QoK8{gCfkFz?O7c2cJTUgPw^lKk;(HgO9x+(ihvCaZ03yZP${+iU^EVszwg%-^W}bT5A3@Qb4;o8;O*KL9HvQUDynPfm zZ@=Sp_P~e&<5P$2dQG^8VIAe#n5U<;m#9o{dxa%p&j;V2!MY*bqt?m|qpQr7a03bR z-n=Z`_bkfR)x*z3B7lk)9}z(o+AklM{c0wu@y73sqrl3As^DO1t)cqJ)rkLJk4e3chD#84-oIc{qqY$b*zGszvZgthsFUc}Rvopl zeY^Y}h_BPQ1@NYI=ME_*kG+tDRk*DG3*}ap)z`SlS%d`DQWei8R_C$B$kSPLYquUDbqrqx~Am&A`N<05mEd z*%6&NH4H?evDYb{jvYn6N`b+|{Z7#-Toazaj_?jVZp~(>E6GmVt4MjY7idECVg3X_ zl9vs2`W+8LTZ-bDxX7pq1u6& z47k3jV|><7Q$lFP>e9SccWdjM)Un9PC}I1#f1metF1;)H{4O=$HN~>f3YXKw^-3*u zc%+5MZvIV>p9_p5&L{^&`|;z>*d&dNz+a$c&qB_K1--b2+cVTo-6B-sYwrD@Wdihb4Yz4O!$ zU$E4L?O5d*WeXS`<8v@n=G((g+E>Ttw(j_*I1bEzpscyh;1#L4z^?>Kgr9AuW#>d{ z`YJ6fGrrTex7()Bz!|j_ARK4kXG;Lbrfwd<9-hyq@8#(>a=uQ8XvdiPQ6KxZv>I@O zr+o9A&SG2hMKcw-ft)e-y@PA3#WLH;X$IV#m*))Z+g@JnQrWjYz+VGbx8b6zKym@{ z!3U_G$C^=jYSXHB2K5r672fOju=cZPH?m8@n}dEoDEBndYFY^9FtJq23 zQ?!5mlM?kRV+t*Kw*FGyQ=iAVaO+C-@aW|rjFx`qOf+xj@*cT5--)g?&;?mw>}%E96ywl4Bj_{ zLsq#|_@r)h<>wwv=3H|a1{4=pX25G&Mbq777{2CXX&*VR;Kf9*?&QWXO#&_Ul<1S0 z)qbdLvb7c)*OT+zjnHiW%1czRwRzwJ_$xzv<_GLn_r7Z7;iLK_Ep7!L`0Y;@x5eN~F&cqlL`b!tf+@{gfp`E@#8b_NOz zJ6wJUsRt~5B)KT#>d)%>9_H7!P-6`Ar?;r>eLW-rz?XVo-xA!)Vx8JubiX26$q;a; z?FKx|5^2mlD@)TleWH1Vy8cWCtk$eqY|%kUT5XuO^4QBq^S7Gt z6dZ~h_`Tf$)YYg(yJp@sOhRp80DCmpCGE$ByC_f?FLx=xr$UUG$OlEAf`IRSzi{OI zAz}pRqvK0R(okdPdefVp@uDBdqgKMku7buRN0K`-Oehm<)YSu|<`UN#qx+DE@nQA_ zn)Pi3LUMZpjapcoIrY?>4prc}c_YZ0#Hl3===4ugh?BY}S@859eW*8u6YcfyPt8-z ze^s#RE1EYHeL3pB-~v&8J`18=-f*Iv0vxt~bUc!vfDMKjGG;EI4lHl5-9ORA=>ugU z&jA(NIZV7Jnw*BZWK1-tigcf#vWl;l^cI0y187_ve}mrC>jHhEfxbgVt?2a!O_LV zfD!a%VC#ipES48x41mX2_c4c)fhhNANoh;PW``N^AoWt`rC{Fo41x8o;|Vv*F+&2Y zG=cz&-2W6B8Ug|=ECdnO?RT5Yl_0Zw(?rx~DX-7en53w~_W(akpJx-@cfrp=hgypU z))dJed1jmG?^!2vB)e+1bPmrM{4i(Q*n5dye8LrE-$o;|$DHatlj&Mmyaz-kVlg4H zrP#i7dvd%|!xw-4d=1MOe%V2Wj~{zitW7!H3*2oITQ3Pm8Z$f@yC7cDy}h9S84x=9 zlLGqfHM-H(iIm{!Iu+`lXZY(Z0uq8-7$Fv98ye=7=X-y%Q~&I=UjFJugrZE8{y5G_!YgaFQ}L97Wcq=A5CqpCxV6EZ~HfX(f9BXq1}i9SrTf*{}$T+`v2(Y z|M#V1F5O>^;un-9$+$bOvHm}A{O3CT&&#gvm``ah1Q&fimH7YP0sr4`_|FF^-(m&7 z3JA(em;8YCKNbD|{h@z`GBk28D38QwW`Y0jJO6!0(9vIGqxYb9NlcCXN%-$6;s5_J z$G`M{7my=83JAH!{O>=|<&TLHlNuuUa@SDkmAQWv&)^w@h`FFvY(BM*brTcN0cP6H zy$Nf7I+#+LuuwFEd`*9<6r2+N5!(5%w4{;~odP>ym-SB5dB=PD16aiThD%$#eTKz^ zSKr%-&i1)x1*BBP6uV}0s=?Aw3_jPmI~l#iRMxsN>$s88e^z~7H8`MVcyXPYg0=I% z7ps#BMf^Mtzv{V+^F{-klFHH3>j}Fvs_0SRu)U2t5L58z6;3Sd%F%<);RZ_7+2=aE z`Vg@I-b}2BdQO6NDq%Vtnz|tbMiMJ1=RFc%mKb3O1-VU*(*9ud$f;)(rS_0(b$^AO7-_u>d>NBI5C|J+P} z9#r3L5J3~t6nTfZ;4qylOHO$B+q=D;sE|;hRX3oK`@Eaihe??d15k-oIiCa51v2ESbr zIiK}+q{;He%WE2KX|%=jOtBVe10CUP|S(Ij^IED?`{><;gfnbx9I3uy$1 zM!o(kVgJ^T{pE(pu`H2=1ZgLyFe%2s8z6pZ$hjy9idMJH|% zvso;0o7F=hiR^mpDSYJ%n(da&UI-i@@rXv8K?-}F_`?N6_DS_s+SpJEUxu!)lf^qA zduVr#aZ((XFC{(0_mTbJ2k<+pFcX8TEWzH#Z3g6%%ZNu9LHJ)! z?vo7`=ZouDi%qfPOGMXC*I>z-=9-@xyjg=A2X~r*u6_ikIlkvs@A!{YBD<|+kqx`; z9dS@WKvn2NSJWrSik6o?bqd+>*_&!+qJE96>h^Q&Z{NPf&nSxMkqg+f<{V&K$J2%E zBpUUyYN^sVK277teo2-OXNIwVUHLu%962?=+l8#7nUM2-WzEbYpx_6nl)KT+02~?{t(W zx-obFJsSjYFlIUbwh|WX+?yji+8sOu8Lu=>KNO}G{>9Ev$j(WHJSOUWq(8$UW-f3O zoyn8ZKqQ>efMF+I@thrgpUBR{*cUp8wV%?&uPMESq+=*Q1Och-QRi<|7)Ov<+X{^- z4wvt^6(7TsY1Lz;)+cz#7aqS3f_BH!3tFumR-ZdX3e|U<_YS--J@qbjhw0(VS@h#E z_UucwW)G5rzPBE_n}pLXXU$W!M02Bk9fN<^n3RiSNJkDIp>e)<=WiA#@s$+!_i+=W(yb@Hm$!Wy?$g9ptBhS%Mj z6E;rx;OMba3->#n^7#$tK|2B2rRfh16*Ycvh`aO%4j>jHmb5njxw!I)jr37`D=({C z9SLpAI6Sd3`*k0`{+#cnh49ol@r4kk3VV{ncI;t4GdPD%I*zFsmy(+&N0M1~kkFlY zfM1%KeSPHQ*#7iY0??O56cAYg%m&zVQq80Z2$^4(m{jUN2KY(U1A7J+V z`j!Gj5m=oPEH`+J)#n6l` zk;tlAuPf#7xla{{ZL0AcwM*ndpEUCU$qUAwEXJFO*ktQjI}ib^Jr9v7Q(K;z@3bY| z8o06)OzP?v~)Po(e%+u^z}{nW7uCweEH|4 z0F9(Pc!)piWs1aErIk=OS0FMO6fj+y6)NdB@s9=njid^a&a=Y-P3z%< z{SJxt)l04BrJLj#6{nZMUk<~36t~anOoy5AftLXl`VGqoKlo2Dr?~IL;zil~VydA& zosE9wc2#OI7atUo6j@Cd3_N=iodp$*-?xfoUtBmJjj*+_aIw{ha`ZHNG_BcqA*Zvk zi}!eMcJS}Spm_1%gA;Q8^1qgoXjmxEg38;&iHxHq)tyt_iwvcISUf9rnrcx-|KW>s z(nfT9KYCxex|LQ`jyO!?J2l!a?esM=rUhL7Df8gaJk2lLAx>#z%%s5rjm;~j3xB~( z(vqX{>A8L7T=F_VLF6i_;Nb?;)#iKW{EqLyM#oC4*)blM@)n%Ls-;ZMFFLKxc79m4 zhF@o%F8`uOD@6Q!2!sEJ5y4*tMyyH7G#-!ax9qBu*Aw~k6qP5(zpeCscZIE?{Oahc zP<2;RgRVTCxO<(K7P~=w#3XX>cTx>(IW{S&7I#PDRgefY+>a_n_3$<%Tz#o})YZXP zkBDjA>qhM3pB+IRjd@Km-FD9!xlF-tels0Im2UoC;%Yyi5jblWWr5NW)&NR3Ri4ot zVKcRE)Ar*g0N19A&e@AzV>lJLB}6$^~-2R^fwFf`4H4KLyiS!NCY@@0_A zv<$k;6mqeCJl|U%$*+9kQHv`u_+%+*i;Sdy6ab*og<=4o7u5*d5`}6W@)tA1lS0O2E+I#vtBWDuaI}EsEuSHm!GLh{E z^=h&CFkvQzMMlGbrB*eTUKH9bB(5>3w9Hxm`@@?+eILD$qU`-MSAX>|+D6pBC$6WM zlD2%Omoo}{Y*?`ZCP*80VqbZ+n;G3Jlmvd0mysQ1OIPW4wtZMDC%P@lA6CMi_D?sg zKn-zXitqNzvtc92<@I8rI1m&aCgc$r?apYbM*G4F_}M`^cIsoRs@Fr72Ocn+mTLO2 zQ!)8R-6BoSHS8U=fPJR8MW<{pJ;j0ktyLfi$~D{3-z(c0AHJS%k+I7A zU|ehq$-N9_oBllWCyU$X+BW0#pH>E^Q6A!UE4HjOp+7i@jXaikGO=Fa5(P_KRI*&v zk~Qx)ePXdMqur(0SMm1P#S8xr=7l(xPhtI36qYf<{zFhOM%s(KhWZQ!G01giLSobS4j0?PK%19s`v5JWO}S|X+uo6cZw-Z2BM}O z4@cc(X9?A_In>I7`HMRnt!9?jWe?{o7uYO$T*9dj(zuWYsM%Cv^k zKQlmbfY2kpifO;@o9GkbWhLM8;n*i-#X-zjr(A0<)k^JtFv?3DlKMRRwN`mMrl>e1 z2q7IWq)d%@scu97(&-ZJcHVv$$_Zvtt4ipI+_iczb|?x2!#t}e`Rgno@lES~I*M0< zvtVLmlP$`hqI-jg3ZGTC{_FQtW;3Yt$WGda-8NvWhl15nY$1*r_WyJS&@fQM{S;2X zH`$=}jFK{+2Y2{(!_(u=flXE2oRIh=W0q&$rB7ui-r_B?3T4So2w%uaB&UY|yr!B+ zbz|<+_q!`>+*Ce0#bB5vQQq*`n~atF2Wo+^aDT^ahO5T)1}JhHJHF)d{L-x7a4(Rk z!i0uq|4ZU*5+5V|QxI2OW$W~>AH5@Q_PLJSlwj=Uni1!=>Kh$W;n=mxYM(&?7E$gO zy)W?3i46d9JeJM-yBVgxb|LFV2Qh#7H7>x2X<56!>(@y$c0YP@qlc3wvV=d1Q<)7! zy_^0XqO0EH&?eRx#v8jCW(bNeqvz1MF2}|dvaK!5Z|y`J$g-dTgj& z&T+2PZ$qTmhDHDZ0=o`k)hwpDwDUO>BZVX~oXiqg;zHoIO-&5EDM6S+i zdsyD>%!J2w2Zd=G6yfDki}{Rb60Fu*`PKCaL3~o=jR~&{XP|Sq0e}td9Py(_bz`b z1kP6JtppceOxQUj(jl|jLlkiP8OCcZMHDhb*YBAEDMPW!58k8nu%hpZ!j+C5z~^PV zcYo!_kTaP|hAEoHFX3VtuZwLyqcA%FS?AW81VUxP(jt&ypJ4JBl$&ui<-9X8TnBM$ z@*pH<8X)?;6?AIe8Dl-@kh5={daqKo;(1IRfyR*neFgV9IcDTj>575 z_>t8$5LD(2UX_PM&wu;fi(g#|(X?wh4ZL7`q;SCfYdLGSS@2C=eVINCOpoM@mP_v` zq(WTjUguHP;{^&PVp{A4GZ6*;D602J7n;61d+G~*m1y!q23{=+@+Mq$XXGzN0PEIk7oc_QoAcmT?A;G%2y&;JWQ}i<}F?3^|=rBB|LhSh%}k zLgcq1F+#}u#17K^Hna-bb+wz-o809FFYfgm>f?ENzv*z@bu~Hbtm$@;neJ+88+X zWGm?OpZDOfPre#)Zta@XCSlXIO~j>UJUeh3DQjdxCY{_n#ef{r_DnxhGOLvpta^`* z^E<`-8Nqi$Jqb=!Y`VcSGpK?{WHd7B_~`vPSrm#8*&=2r*ywD#`EKm>q;|8*rlZp9 z-$irza}sk8O{dXbb}l(pHET_7POV6lO0`%aOPJ5C?-yaIuw>5toQA;?@lC21m3FW7&_pDe8+?3hML;FviP&-%b5)lMNF4cuX<7D+jO0f_c$6q`&X5o+Bnt2!uGK% z3)6+|mI2cNFFi}+lFASWH9 zYh>goP^aneac(-bx_0nhZG^S;uPKV2sai+Q4;BmS&Q|U#4Hjj4JcW?J_ARo%iNP=H zHqYFYyFCvg3h%r+{<8^R0*lQv&Z0IK=I~g=^!x72`R&e2{AnqD!0FJh_gRZEG6LOS z@(BPrI#(I_be`UnG6$#SRAFmn-eBVKH&u1@-$Jr%?C;;DO{5dK09NwU4xW!YAU(Ht zw~30ZzQYM0c2g^$o~G61+Ida9!li~1J1h%d)iPdCv#Gr-QoYAWq6WggKns1JMB{d^ zMULg{7QkptrDb$>8?7Vu_I{r1U-J{Cc8ggCOV_BqW{*odtE3Cd;>aQs=QZ4jmsZQ% zOwL68T?!thM(?|V@C#Eae~hm`QinOumAM3qs5rDe#?0DOx6vNQ6vg@!txX!x^6gpf zr18Vs>f>f--C^!uJK&8gS2o+%J3CwPW!2#CTrQ&XVzYfm ztkl=-BD4yb`BZ#5$PQ(mD9iuaoZYotJ=@EQ9cH>adDK!2V)^xts88QW9)bRl)2L_H ztz=R7JFIyvh~2Mi+St0DGw>PI)SmrCmhU8Q1#O=agrRsoYeG(T~sFl z0qA~PoICXcylEFgQKdR({8j;o39*yFSsW4fh_Fo$+Y2No z4fs@W=Ou)Al)h6Gzq(Y-%B*wLD{T-}YmUD7A%e5p^z}jHaywBGuB?Gr2RY>S|G}4N zrznNAoq#Ru2Zt$rZk@&uS-m%d-xMi1!G>GBu6tp-#f*dn3eQhIZLo*kf?~C_9EGy< zg^M+xf<_^54^P}qX#70e?-58f;_q@GvbOtT`+7EzRyQ zS5D({A(6(DxUCF<>EyXZzp108I<00l#sjKBjbs)rRSjQ*zr4b^wT`N;v<7ONgE_ZG za{%KHIK&T5Cg4Uj8R8?im2O9z{0f5dKPrsT3tQdCyaK?Ae-iqy4Y4Y zXD`Q{TgTBpuXP{=rey=qiK}imVvW|p7?@??2GIt1#|yw1WzzF`)>kb=92HljY4K-4a4D2So@xkj_D5JDqE$WuFAbt9&3J) zmHMDumJ?Dv*Pm1r{Re8&j#Hat#8WExnUmk0GPSM(e-IipJN}vTDYC~La@BOwNoOy# zF1DnL0|ckM=qq!YBX8Id(u*#l3@hU6DvGYD_0eN>UXD#oCGLJgTTh{=YFSk^e7q|$ z=*$+q{D&6EEReFgR9Epiej`9ymOFa_+Pf(Jf|w}|8dPA2qLwc2QqzH&*FkfyORX2O z@a>~crMSG>qf$ScR#o%$5`5lbiqdM~m{o^X`U5Gwi*R^lteq`!7My4@R|1e$#`T*9 z*-0d+zd;`~mfM(DC=>$rKTO<&j_yol36_6Up>?&rxYxTQ znIX0%)*S0-1Hi`55=tb>11fcDiiQrCt17j5o4n)wK)=;PQnMNdCUZJXlsdySqdm@V zFx{4@yadV7(C#~nbUcl<*^p)YwVu38CEzm%V65{R-A3Jisww~>bmJlDCu8X!?Dqp6 zfJ|Rq`Og1Hxc*N@clmQI@pQYf4p)VoR%D+>$G4Lm*RfMs>1=`DhH==dXO_YeEYTTM zCI{EYTC|n;7r1qgFMC?s6BnYN!rfo~X3KDW7&3_k(&i6FCI37D+bKQXUATs8aNLB} zTST()EBupw>_V}_x<@wMU>q?j~Uux^}zV}H1*|FD*5l4ki}9!jMy*A^xdF2NwvLz#(hI* zmGG?z0WpYF=hHI&iCNEWz{SrSPw~I`wnsJQ1mA?*_dazo*=3G)b$s86DB_mYD!~in zW-gAq;E@pq>N`A3-x>yWqaDm?UA(PB5qxUhEFq$klp8Y4+YZTI)`#G89rv50veXTZfXW}qm;J03qb9NLD#QzjP|P61 zoG3crNFh0&a=te4nu1HHyd_uj@#0yaAW3+$*ShY0q|H2n1PJQhqVv@8)CSH(jdd_? zK-!2-H{=ld&w)*l@ zj%qJJLL}AL95M6ezAYXM7qqsB_#3@;(JtxBDB~yeImX5>3YYx0Y6)uChVLtS@^urq z>g~7QDD{Hgo6w9V6aI<#D&xU7mejUKw}E@R#UiKCfy{|=2FR&_&(P)2xy3G|Wv9dG zWl$}Vp9LMNZ{KCtG}gR$ytG(u`rbX0gG5dn+0tvm zV$AMm188WscamSc{b^9*257~BqBNr1E3Ug)yjpX6*nLMG0^}OmT;fMbK|a$&NUcpM z*&-n|1=jRpjUxxoPT{Kl>+^r&k@E|~A{e%4Gv}?QI~f&NYI_D*)Noa zYR|d^^t*sD%C@_+6TG0eLzvW9gduJr@1jwMG&ps?8glH`LBMp?o%0+|aN6v$;bY@2 z+|J7(I9_wt=GAXruUhQr$1igq>|SuGMvpjEko%}{`G#zK7<%~%3?eR5t>ftzSctD`53+K z;c2`79)|x>RcN{0cj`FA-&v;}Mla~|ZM5{>kq({NEibO$AXO7uWk<^c{iEuC#Tq9P z?@5{Cjfr?~l@?fAA_vw&ut^fMc#4*&l}BE4?IGm(NU%%Se)YP#F}E75ak*6D0~j@X zf&uuwi`3}$a&dbpOnl;AWq86CW9@_2hl**ON_eGI4qGwk{B43uLD+ZLh0r=u;&Woj zdKJ!0hBq^zU1jp2*L#x`6cRLe2f_-<46{W)qT#oX1 z19{jxCmv->w_%|yx+^1h@Xg%5J&I}CI@&2TpxNG0sG|)aMWE^ajt3QvS(E(YYrV;9=^$j&8q#|`%VWUGa~i%w6Ic|E=)>#J7vdZ z&A27lEsgS&(zcW77PIbrALPR|nl) zg@X9;rN#8t6f(s7_}kxKjc7g3+aFcTj`oyN@E~2A{lKMWa(w^6JCP(0D#)+SoOZ;& zNTY8sN;uwjY7zhcq(Q+7ub4r41+j`pPLBHzg3Razh5%mP$b0~E1i4_cL1;TvI_pE0=Vldb}wg{^5E)#rQaq%w}Pfv@tsaiSs!IkKSXP^deicUt64O93$&901lyJ%ZizM zD?(QfU)v6@(I2zN{)!=vwqP04pYSl8Rp|z&ygNkMG)`n~W+yNjwN*n5;Agt#N}k}f zOGng0?*`^Ur%Uh-MEJ}TlFUZ*kaEkF?hB!H5I73vwOgHB7qPbpv{?8UwyX8EbLL4e zyAX+E6tFF6l$!YKkfJz51bv7umUnks^`-m=0i3*FFsIRb4XUEewizIoDdAFI8?kUV zV5Na$Mw6aEl%5~2#QFOZx~HdTDNCkI21}iy1zac3Q=)D+S<(yUMfNWZ{CT9s$SYyU4KTi#@HJ=IvFcIg=tzO*0=ty158HY?SJ~C!SpVD&LpC0UM_=Q zZ(ZFlC;C?T@Zrz9g?~H3m%XuOaVd5M+6cMKuR}CrwA%kAYZIg)oOH-nYABnNx~UjR zR1~K&=qDGo{2S8nBi~Ko>ToWVj(OGz9l8;=;Of8=SZ{bN=cNttvUr1{CKkm25=N** z=07yT@5{0I;{2P(Ycc?vTr|Cbh&NP=#zN^g^~XHP5+|LEHq#jPSXcy5TCBs{gjQ*_ zr)*?~qqUCN94NynU-xv8hzl#I$X{TMVN|Ph4z%KIT1N!OwZWzN%yQ*}X5^JAX#XA$ z<@M*>ECHKVJc4ma{}RmYP=bq{=vVPu1JO7*>yJSRQHy;BJpK zuQ1WGJxPXHlem+|n{~pqFo)0Y)W?L-C|K_6G0_TST)#juBXnoz9RLfbB{~m|JR>p) z6-A+?1fzq#uxF8($^y zfBur1Tq1!6O$3m#^NRk8tq~boSst~_eg&l74dgUIovl>_!g=dz6)v%vqg2baG&o;3 zia6_+FZ3O3xS2Wy67N1$-|(2I;F@!J_J4b6qRK}0Ga2P%M?>FiV6g<6dxKdvRsN+$ zxUd_tjdnSdAFE~XCD;6LB<>I3HxQn*s2=6D*Q+~KzyD~=K{X>^drLOgT41A*k^A`v zodIItP%5lE-Y1Jyq9-Ynd=Go`{s4Og4m=TLs>)&%{BxGvi<{ZN-3NAG%`~SwQ;T^&nUnTyn9PL1O>s^ zGFDx>37!Fbctm~$m^e`F0-qr^plYPW`0vWCh4ixsla8>Skdy zC|1-C5>qZ#tx3(Z*ChAS?$=Gv_Q#n^&}sfv15%kW`m*)AFXAKo=p7nTf4jK(Vm|cR26Bl-1_i0G8|gmlf7XupQ}Q zP1?O0>~VOVrt7@Fa~A%J1j}H-W2xQi;xahNpwjM+pEdI)s5CT@ASfPZ*VloipX)|D zgTR#HjiTNIPeADeqH+r&`B8ZeJs3E%VxwHW0b$0kJ{R}_Bco=tT|zH zdqV1u%(hACS)(f1AmTUe_Cg|xhh#qZv6Ot@Lvj5K9w)*=*svY-nk{Kb>A?>MrA6pa zx;>44yp|ZEAA%^G{iHrCZPVE@RFMS>*(sX-?Rr9Aep*CZgxo8n-)DhsV0}=+(V6UI zgUgoCNd!5UZ(955XO2U(D5Y6<3B=u^3V6U2csuM0)AM-bpx?>;jP+c?akc+Y=6l^T z;hW=};9hXfAnMWlB;L{}i>WgwbU#*=!0|xaI#{PBXQl#_lf%-TzfuQvd+_qf+@gpt z_?-AJQcp-*yQPQr;=3uB2TjbH&`-<|qh61#IzfV#Pg#x%U6fb&oyQv%meD_tZpHk^ zs5H}I)n9nW#W*AcpWvLUFp0HikfB`C{LO1+$&+#NnzCXr3qiwJZ(nNtLFX#uwjh_m zZRDF>13jC{VHWAZ=vUyTaFD{gOwaVGZ!6ZIgIST!G1kkAtm+rs4*nDA&ZhYT(-q%L zG9C}s4?j@wHmaBRzw>GqjYTdDkMObjvcJtGOySd^qS|Aqwcmoy@qyVY>WrT3cb@hd z#M0&&uW(#X>HdMx;FU$=zsuZKg}9Z~EQ%GM^)XXbw)ylVEI2%m9)DYdDjfeHip)n1 zv;1D-n@=HStMs6`%@%Tfp5ztm9hHKaFQn;(TgXFBje}BRCq7nl9}_LM5DV`|eE%M= zRG!?Ul0I6XagId(^X_?NH=b8d4&v_4 zdaTuKV8Bb#U$>0n5=1C7j>K9{c42HqUV8Fj)N7jg9n@fsQ3nDIDz2Z(PI#Wae{9mmfuRSY-r+2M=S^g3xD@2LrK&0mKmGdAK%#QmAn)_C zFcC93zqKhUDTFO$M+T{H`yiWKD@+&MkuTG_2D({u^1()~OI5pz(?u6Lit+A$c-iNP z{%z`Ve>GQ{=z$5!O{s?b9e%MqQS;RomC_%5sJ#rl-hNl3PP(}>JC`EqI@+^u{rppc z0qyF^TB0rK7_N7Ip}exyW_d;@;uphD2Ur)8oTKwLS_48JN?l8*-KA!Si!+TGrF_r# ziz8;5th?JojmkswSdSgb))k=6xXH8>SacRntG9Y)10$X)F*-9h zQ<2`n=(iI(oKRNZll>v~zn{pO#4CHdYj>27n3NIkR`yp06d+wy=zds@){{!s2gv-m zZ9#BFjDj`s0%%fT6^W*Gt4S_TxfW8#R2k=^n8?JuJc- z&8%KwN@R0NadpL%;j3EJC~Tni`1_#QeI^vTy#(_)sB4xQd|qK2w*^(_mwpyupdUeT z+F8@meK?JN_9#Zwd;XE+9_Tv_zfGEkYIkI9dm4+x6}9Tjo8XEc@8M$!d>L}T zk*DLo4QyuEpky2YO5t2pkjW%`FE$4Q?;Fw=UbEqi&wm$1SBI*LU#2NTgoe#|tcY^#TtgI|TFOImdRrwUw2vAs-? zNJ_&nAOg}2(k0y>Ff<5=bb~Y!(kWeobeD8@clW&ee(rkS@BR3TgJaFG*WPPiYhCd> zuizP9_z1prAj@PAElFAq<8!C%FzjjRc$OIDp*n?|b1ylv9~FZ>o^)ct&*I%~R;<0f zOt&*wo0R4Fe-+f)9m{j?PE~Z+Uqr)hrVpGy0o9HRX3O%>Sy4Sez8~p}YIPCM@7^?N zJN5C-reU35S&e-S<1dsQlD&BS|GIqM_zA>HOAMB zf`eI6mGMuR3_?k^^@7@a(F+Z6GqpXZ)n5%iH9|*8^FPF+=8S%vWw|#lHqSP3X@u(a z=pi=n?%3s_{N(lTL(ymVY8|0Ou8`5bbUS~dlhYgbI>N81nI62Tsj7C5wA!g5g|c)o zdgzXv2k5Co1ZaqMeS^|a)sGjGL7M^kj{H$8uF9|UU#j-R1;0>(;{z&2A3fJ^^+^u2 ze<;@!PDs%8_;qCOZB&*FQahpteGtxW8!AU~pDW66IJ7szSdd_ebtQ96(V2O_YwanH z{^{$u>|Fb~g9BH8HH|XzQ=ok z!QgR1$_gdemj(qw1ygyh++MhT`G%IuOkJwZ@i4;HOdA^z^yaHA{e5JG>-8@mHwEXr zCIo*;ds~Dvm2T(|Hu2TuhvbvTeqT{pVJ$c)QnEJw2*8)mJ}xg_50QVg`%a6ZEx~O8 zXiduDKB)a0GpIxEVb_UFexrYh;wSj?BkXmseD70h{JBOW$HK zcZ_}tB+We%Pv*9*5)ArXsX;UgJOR|gJ3*x3BSO6?>BFlD=Hj`)tw7|5+3F5{l4rc8 zJN+N_+X}UG@<~2YsOix?Uoyyef8CS7#2OjGA}CKMqM+G>$XT1FY5#AbOTunfG-vOE z8Z)i2y}SB$iNZh~kG&&MZ6AumfYZ0V2Wv%F#}GqeI-pT6pBv$ft?`fwmZ;)T$ekzL zP7Tz{I-a(~<|~o8J392j#0)3&8gW*|j5Q=p&XL;p)XOO$Sml49zRc1c@%mP@7f-be z)@=T0-YDDDj2R?%5Gb$%+W)z5&;xUKLn9&4of|rx`W?r;0hJ26=bZ^`B2SCJ(aM%z z6PwQ3mnQFOyI$H{GdOr?;~G8JrUx2wznf1RJbm9e*$46r1cz)kJ}G^mT-M~Z<_PBv zdJYp3%9kSl_^5Yd2<6bcCQZIJcbU@Ap@m`pr~Y93O&7vun0EzJz={fDfCdZOoxCF?vq)swhLj)v(gBDpFck5cMO|I z{4`=qZ_`~KR`;;gxp*5f3DSu6?(X}zpEmI|NRT=6flZ^*Wcp=l_t$BzL}aM9)AGm>9+I+dHxvNUfmJ z5O<_w5bwruBE5O}-25A_d9c$v6UerIExT81=u2T_@}Fi?c(s+%;8~K3Wr?=4=c!qv zk3`FnrMXq{S4E8BW1v*RZKO%?Dcu{YwYkxaubS4JnKlsaQ9dnSu~TAQR`=>PP8CYN-F_a!;quJ z*G%6=0lP@e#bOI0+=ZTEg=O%66q@88F^@{tbU^oPDloLsR%@wtH2+vnohoUU6d&y4k}~VCEfza^-$%LIRg2xOEQN z$ynWzNzVM@4l;M0?Q&l6?s|3eA;@?LbFrOMQ8%{jx~*@u-a8h)x;oj;+tb?!P6L8- zu3ow}E8v@DXJ~5=kH*4CrGMM+zWuu_k0l=O=p$IHPq82BWtj^T9hF3Ud8=q26S{x`eB^Ua06?e&cNoqXzMa2iaB-=#@q zP}SNI_h&d6&q8%&Fp#B2C&*&X7o`u*o%tk}5zR0JZXAWQ$7Jcz>e zV7ulM_W_4N{OgNpCCi37hi~>muWJ`vCX1a-=xEDTV_prRg>9}+7px156@?#USvt-s z-n0}JgvBR1SX9=oZym3KXI`~u_bAhfT{_u}c}jXekJT`~zNS%(p9#~%6vvL>yP;Q3 z5Oo62BqF{sUj3+Hv( zbf8GBCBCMx&pggN$b;v~4zZ3vcP`xF{H5)dhN;(`6{gQuyyUluu4zNkw>DkH@3StC zWInkIrA^Fh$6UL*!Npg>4Pf1h+Mc^;K=LEzD)w9A5aPvIa%(TEmaEgYZ7cama^!tO zoqU8h&_Sd(In>zA92nUtyS9FOpY%K}iUa(k=c|B*SCCgN(fv|tKa^e9u#TuLRbISz z$3A+UY@qA($kNUA)hdzf>h=CX5+UUXFN?puw$UUDNnvW>u1r;GN7LC!_yn_tjv$4R z`&9yy`ryOm1;sOF)5^i^YhWF={yvp2ia3=#ObW&KQk426(dO&|2QLJ9xYWCBz<=AC z%Jp2inN{2f;LqSX-II+`N?~wyca7w92Mi=}hm~%aR_YM-ExE$@YU|VWAg?uEK=y=d zO<*WU)Cn|pJL4taEpC;^Z}#&qkPUNEh`laS^=rR05q$VktW)?UR-R~W8^v)mjuScM z^a(QI`5l9WeWiiljY(pq__@XCPCL!VO3iOFk_@?TJ*>E`*KK#lpQ}jct5A0wy~{K8 z#&5|Ajwl<-MpLK5YxI2|(W2J~W7o=o!bRQj!JYH%(18jp__ymYbO3^!3( z8MU;HqCRXiDPS%-xP660(L+Z(O2^tAC>h@7uo?RT+FzmkXurx$j8UF%Dim$Pth)EMtFM8qXHNAoPc z_(+76s4}#$dc#h&SD0(xbgbB68qu%DOm^#MWae z?00PfR(jvBhfeMJhHt-I;@@54SDK=6c;HeMO?7>B8thdqkOs=tqchPT|D>Vd)h+ce zspK)Ad*A6HAa~PvQLR~|W8BrIZ)UcfnLY+vqS~H6MKvGG{_z4E2s~}>_pdAU;`g;9}hVI|}l6Ird`9~vP9c?!zNBWNDw**j1TSJOkord?L%I10 zqm)es5@|#wJaZ+^J($;}64?6sG{y&gP!xAZ9Az2A*Y-QMDYP%$mhG|+5?({q`w7Kgs&#~;^hCAN-d|457a zI&=3LQn9K4(Q*tdxH0|h2rg8~FY2m#U2!V&bDRA$IiG(g(#=f@LvQtG3k%)|M|(ag zjNPu;>T>V2tXGCzVs-_>?2yB1f6_Ul_%1Tf z@w&7BZf!f2eSy4(Nt~KEm*nh$Az@KA?qeiXVbWuvg~z*Csgx63Eg$d#FxuFddxx0A z@@?!sx9&`TOY{CAcL1OaaujziWZ`GcNvX0!17T0xZxqK(I|QVzOqEq{=Y!zwzbu3- z5Zi>e_v}TRpE=jxcarGJD3uLRL00lw+mD#}*^$L|13=e%`Br=mJjmKWc{hNI8H!x%mg7ShJWvfUBsY|JOvBAWrx7S2uan(WWdDnB0{V2_f zzKor8lEL?n$K6czdKfM*^&BOHF`1E66h`w2{17Qxp;1N-LX{4#n2-u*3yp;FP0_h zqI1IHdFKAo{QrHi|3EyJ7T;2d<|5r`1>jsi`WN}}Uu2IzK2l~&fJuaO`>3mh_Wwn0 z{5|rP3&q}?fTnX7n?~aj$-l1$g)bYjUD>6N0^&c{a>PUBL(u8%)~Sh*i9Z5j|Eb68 zPrnJeK8^tp5U-5mzCmsL-@)l*&sg2vrfXUBnzbuThYXxPA@U*{N<(_L`N+7f%CanL zY!_Jqu!(Pe$I!wpuO~~j04jWQTX;9~hq9JMi&qH_wv$FKHCEHF11X;E5`|AUcQPE| z{4+z}aN#m!yNnur`acONQ$qwxkt+V3L$Nly(vi`(#HtBJMz1>ovsi| zbG_i(9?8nByAIkwG*GgKxC3?Qqxp^`FWxv8b+mbVQ?srnEqeW|PjMQoi{`PV)Hqs= zP`i*#=I$8+be16`tnmQcqVF?0RMYbr!18q5=vPFf_ou?z_tSPZr)<=4^$rnVp3*Cc zl)#tii1TU=qsLam$oZuB^*n-#wChG&=Rtvshr^YA82Irw65Qq0H?q5E_d2i$G9KN* zhNZU*0GPnw)Mdk%7z-c({q^8^eF%Z&f?>BBkoo7jyT?QJZxO9jkdA-N_3Nq2*{`9uPz-NWYvUL4yFikLXM}Z?))VfqVQc|6aZv`y(eE2 z&lV4+14c=U17b`XqsD#llKDzGYci?NzwtXIHO`c#|Dmj1cz*OFG@VVPH#T4YfSAef zv#W~FCBI!jf2tbKNse5d!$vF<3tR?h-lUeu<1ONG@}=VB@T{#!W3)!zRs-(U?D(%YSd&)F1B|hE_=6&7UtLoDdm}#cV4O)GL{rX50#T`vcLShGYW=;~mH(a!f-n z2QhTLBJ{C{S#{@15;;1A3;=C)T)Cb%HR=LPw&G)tp#hN!eTiCeL4H*m3IE+uQni%V zM55`p+0)}jh6odiA$;$LrWEbv^=I9k4x3ufEq$c%txI83ZTu74DBj@q3r6 z9@p^eI-ZX!WxqF&9z;Vu>$EKGo;FRV%}sNtr~^{W{pfC9a`c=i=-PRiOC zu{p^UW}t%mrZXTW-f-547~is*u9#*N^dKPT$yY-Cbu!zd^-yR--1MrF^XS@8!Vqi|kY}>^ zC_Qd%qy+lcKUKNu%gKK4%v^Je$lkdh{vn&pY+9~VCq{m+ZEE_KGvgu0KnDqeCE&vZ> zeSQ}ys%kcQ9SBS$Ng%Kq?;1AUl3v}D4o#P9`8e)*RabmJ+)uCcsFYgmf+#S!$?=9HFkM=|2Yp{VGs!CeI}T=x$qRz^OZL4 z)O9@{pZr6syH-7W7+H@;Dt(xs8$Wwks~o(GeGK4%`gX<(*Xws}BLN=H_k#V>9{b{O zbzcWxtY%F6ysAZqT||%LkW0cc>E^FM%IZcA)9~Uw_?ZKU*v7GkCPK)f6_IS)RD6M? z8%Z}19{-0kn>P8$2u=aMoJaV)2qL@Kwv)y`eDTI)OXxi0V|V0t%?+OH-yJ$@U6r1f zK(^;?IA3*+w$pjl=w+Px`auH%tjee!FU{KU7T^sLCz~mVJd}5(M<(bUFr|BN^rH=w z8@cF&VGx}duHOOr)|iCb#+M*Vpy!7z(Kx8TifkAj*;FriqCO#Cheg$0H>#2GUoLG0OrXt zOJsByLL3m486DmY)ZC>97^v89v%*Ocz-**;iR3Dq?y|(gqK{X*`q;awqVH~JQe z=}&y4UNXw>`OrafGWKRD$@e9EYzAS7#9gKXPOYh9_$Gw)Go4vW-1C*R{K zCevKJ{e!Xe#JUiWxkrWYJC!?p=;!QzEpqkmhA;93SR%aLq=G6u|Jm^GC@`spMKFJm z=*>;54HCA4U`s+oogcm*j%~kmy-3U*e|yKz2N%U~-3ijICJi~d2kz@s0Y_ooI+JQb z&7!M>P1E`_EiCx)MzXPA$<5^DZ-wS~!O4ct!?Se3>4tEs;3#OGXMBeK&dX32f~sU{FqLHu#WAU8uijFewwM4m-u+!xLkS;~hy9ZkyG4 z{;Ve_LHp(YY)woWBrTzjwV_>Yc1ZQ|m{bVhO|1tkcp4wzFKVQrt19b{@2GZ$fc#z9 z44fKbNQVj)n+oP!4k6EUf5@1d4`=wwI=6&C9;}qNuJ3-17HKGtr!5*BYftfXfxJf1 z7C8~X8tcu`5$FT5#9%GG9qSMCu(tEj+YXWyP4wJ!=cKuOA`X{Dr(8CTk=>333+>}K ztHJ^$+6}+xUZ0S3+s1G<>1{CpaoDlnaspbSCe!@8YWIA+Kht z9Coh+TWnth1IDytmNuUI?w|AK(vk?384|gmHH8J~pDZJGG=eMq5va;_T_ARzrr=nn zF;2dEX^CdEo*ByFhy;ihpfa+HypX5`HO7jY=btM zbwlF*+s3m@vFS?tm`c7M<)kPntPuB$=z}4MQ7iPz8P)7$srnGkK!~MHodh5_w20XvzhG}+zfqAx7j5qdY=FEx#PiU5 zc7FyCTu9#lx;yoy?`<#NzAoGzma~UegM}Dz9@0!WBTko7t8EwQlD)M^Ns6Y8bjzij zW*rYum`NWE51G%P*~q3tZV8-`Gen*A1jq6eMO}BcO=a#WRc~_Byk^vzv9cC0Hvpy( zJ2Izw$=t>UhVRAKFR~v;GN%?0D%>+bUotu?_r(4;%ciFDE(@-@TI1nwW63Y+^|hSv zAwf zp)~Jb-%(^=ERRtd$LrWe#l`q3ye6~=`pQ%}-AwM6hI)x&r1R`cKK zWL%$;Onq#9M|=ez-vh8J-Ftv~#jPOVc{){JG zV4xA${LGty6fUuRyZKyNy7uy@Ja`tMUadu!=oq)Q2PwgIAp*^|$=G=ca$HLoVgXgJ zH4M{Bwy;lFuFdX5N7aUO6nN;=kmi-s={QY>qI5O}lMGOP6w`IRoVIOlWMBT>wBq#s z)J}v*3rStqzAocO_yCgH@5I-&i00d(gJy>`W-Tk#MKFOr>d^EliF5;}2oFo&f%ewL z9>Ok6LXk4)>FJEd18n}KqR}k2QgLYZW%X3EQA5GDx@wq@u2QbEb=9n0?u-JAyLzVN z_%Y;FwT-SS=WxP14sHLmczxw*T;V^Hzxc&jOK_576Kar#)?)S1t$I z1wjfQ>bIO@KRzw=u890Ee-H@pil8pR=bOJf|L5?yq$lD-yrMS_1EG}R`0!P1mQ~aS z3w&l^8pcFK6qXD5dv6JF;chXF0v-nke8i%A01M|Rvhcz&f+`r@4NTIgGt~tR#J&p% zPEh^M@k7TBHqgd=P8&VAOTAS`7HT;$^~_I~wfU7%XLyy- zF~3Xwd!YRJ{TJN(59<9!Nf<)*isgprtAQgTxj7b3?e+85;LtPU0Gb!pO-hCPLKA=@UMIXx_@C#MV^YGQYF~T>Zjk;+OOtJ;y^xT zSQzuVz9{dEpzDlNFDyn*?KU4?!gM{0vT<1H$(;$pZpS#hb9D%+;h8|-to0GOxT9yS zvuH;uqZ%5Y)Ekf2n~Y1Qhndf^EH%BA|EO>2CvOu;vi5~KncMtjTcBt54np8q7w|Su zj=R!7Gy;M5Zr~V7)Yado@B3dS1Yv;20mz1R5Vt?iPw{`?r#!q)k7NNh{3$5GS1iJE znmH@{8lb(IQ^tV3e)EbXxxWA!<^}3chg#3u!+ND;2+O;DP!(3r6%WBugD~h9)uQ8& zV24tp#`R`xH9*5-K5V`XfBITw>f+eZiv07YJd`y~q8?K8=-Q%Sm`|?xH)OWSy(vTd zBD|()smfwJ9%m3LADlqXknH5tl&%>EDlFJID#;f=RV&tJKr|pkUU0xN$R{&%H2M+N z`?e<)7SHyEi;>9Z6SN}Yjr6=rDTkyANYmQ7b)Du-)09t}X$Fi>jKQ{-G)psU}P2Fd-sw|3!#vaw(^;)hy;bNN-*?(-8vUtmAhX3Nk z_bIEc5>UHq+O^|K#$WQ`0&&oAm>r?n1%i5|tR(cLwY?3%o_<5>{zW+_kaV-JlpDXA zadA@DTeO)%Kzzb>3gA|b%cwC?x*!gPh$6|jEv&?!5Dsqc_TUUb7e%d!apR1_`6kA0 z0B_}`jFn1(c83*!yBq1^*o%>`)G3e-TIpa0oQrh}{nF30ZI=uIS zz-!4-dYG=U%Ay`L=dXNo(_u2sTm9lcf5@WAz!JW3`^r5fd;n-r1z)~=XmAXrzDK3@ z9!MH@SPkPbYv8O73bKiS(!_v6xn!hw%R@0Q4&hJ*c|!V^=ev^$0MyPBGFhJA zWLG%bHZurL_sFD-`0$>sWva#&RClvu`Kj-%l_B=tU!p_6(gGey3w3ghm27o)*{s_e zg5^bYltr8^kbEnZi@+ODORWISq#5anFBqYS2%U=|Ii_M&H{^G5uN4cwgTdM@6CB>% z11R1l;degGa8XsC@|Ccb&LuKbO{OuQXMT3xj|gt~HXEx8FloB2W*TL&pMAx0#4v0U zO}wN)S&(3h4rIKNhu(h_U+c}u?w;dwRB;kWCcV;P#Vm3fe<|o`1CXel+sT-E09a?` z{{6#y^!iq}vso-~d+4F;D9v8;oke!+Sk^K@iF&~fjbzZQe3RNMzaXmR-UyiTa z`=GJTI)5pJ2OO2#AAToejl82HQnw7|<&M@-wu%CbGYAroV`OzxiK6(tCUh9FPJ!YNn zG0%ydaNbdR9`K$vmuU``YSqZyB|1`)RSsrJL~uV$5q&5vyv^$bLiM~QIl|iEBxt4B z#JyyLecn1YxkQm*A~#@3am->o?-5Xve@O-JvG^L*^OTcds?)BYz3T&Hxg+h?gWsUd z9M|gCRNk^`p^7TCdsRtiDO;AWRSmBi_;Gg(SmwBBpIgJV&1vP7H#X zU+IVZhcB)?3&D(KJ8&Iqu`<<+@`)bxEA>*{EYCG%jiUQ)-g(_fEql#Iv+^|nHx!(| zk50#0wpeBW0BW=MW22P@ak)NkKTW;rDbZq^sWi7vB8~NJ{Pu~jK;uo{;xX;(e-j)J zeVBXaXhPEA@MO~GW@XVAL7A+o{g`2Ni6%@14$4B zf`ah&imu;Zl6_i`{gHq~1roV1UUmAjxXbTwy-St$rXV1z=uS4+km3U4xG7whl1bkR z-$>Nud-D4gfU1Akxo=)e)%ASR_iaS*4Jm6cw232o6C_J^@DGUjNKah9G!*HC(8M_{ z&i4{}t@3PVPv92gbbY|6(D(fOLvBzE-L6y%N{G6c>77=M70W7EZsOFVYib9(z&ST% zY~sSDBDgyk=S$4v-}nAuq};=*J_8#y`oAJhqBeeodOeo?salK_Q*GS0`WjKQ_`+-_ zDv!EUr(tmPXF}{ap$gx0SDnMSh1T-YbH-LLHLl;CVWjVstJLnwPky9atDr~m7cG|Q zDyfK-D`wGHiv>y|eZ5i2jmm0{y0gd&YRVmm;FG#!Gl^MF_bf4&T)BT-9ESfK z{m&RW#v2q&!~@G&(C<9Z)Y64FN$-0i+0ElQkiq;Rg&s_J1n99;8|YmDQ}hxdT(VCM zJf+27`6laIdFc^rT5*&%CdGs>>E ztOGNO==J0+3C)MB*l!7ejvck25u?j;Ntk>H$UN0924syTcDg|+6>6LIUwb45GD=T^ zP_#-4K0_;;Me60;ZAO9WV1gV4P(~!6W7Z{@>NYce5oK-n4zi87pc25p?tXNxs=x5V z;&a@-)k5Kcw>T-KtN)9GWiwi}dBT1@k89QIy;D}}a|);mll1OGqlGFH00RA_R@E0= zPf{7@aWhTgaCncOpS1ST)wo62+*R8#KdT@~UE8fZR-S>$THcK2{!wGDAWuH&ilW25 zDq@wX)Rdz}1-U7VQZZH(9kFa8lUI&?^ZQJDBmc&`6{~oBX=HhHqg~p%^wqd2(l00x zGFO4R@~Vnc_HGA->e3?pdSCAQ7pi*NKN#a`wet8g3njq5EU>={q8=lA2fj3;{#2*X z+0;Psbgn(5^Z2aY9?{M_*wr?_Z+;Z9kq$JMhmuaZ5^Nx@3|trHf#N)W+8 zo)vz*l6KjMl!z|*(B0*=<;#)UAg?re4n&^b*+SozN0!rPJ;S+o&{iUbBs%t#ee^3n zn>1TPzBxSG2vJg5E1MxA-G@W3NXXmIQ0`M)vK7_ZgG5X5o*1z zAB7vNn#{XjrM@2V%)lJ=H~w7~O#JE4Fa{t&R;UE6I1B)dGQKLVj<+7YbzKk01=?(z z|9Zl1KZXAx6Q;O*Jl6I@M?q;d-EjfSlQJhO_ zd?=l@lyfvFh{Sz?+P3-DcVGVF(h@4^1skQy-pI*>z+sQy5S$TStYf*!Z935tOjgp+ zc_DNw)%I)H{4K?w#z36#{nNEk>4YfQi=dcpXqNfc1mI@6)(T{8UyA9bZ312|^Uwk8 zBd3*jNlYAb7BSZGUjPv*sw0pDl_(JsUD56iqTY#Y%@04YCt!a_C1jDW{}EE*x!I!( zEFXTMRxYpSlfDGm)UQcwN5>zSwaUo|v?%nQJX_l(9cE=Gd!3uo^Dg;&Z2dVvGsu0D zMG>GEl>nC`k;^qU#kDhENgakC>P00wYkmC{n#`H=M;AfcMFz9)Xg6Ez}m7z zTI4TLq1ea!LyB#0Ej|HMR~Ur(=f5fbzq1>EW8Cj?eQ{@zgd-)&5C(%N=B0&OEP1OD zW!j}S)AMQ!bgPl6VvX8M?H05FptlO-!mR#m>pmFw-t3zNVQht}TPQwdl5_hL``;Un zi^yX`;_mHN!9CAXF>CjKC#;+8_ z^&r10IM)SVyU5k8ckX5@b)1ZD#!bG;(SGq$qXq5#zs(Q+Ne})>-rpO-{P@Aa$(L6z zZLYq)c|mRV*zX(CSB;U$aWB;@?b5=-^PPzs#Kj@ zhIpWTfF2otV_4KKK-Rp zKbsqSR({4)cv2(M#-iKLdH7nh{(gdl?^BYQ!uUhS!iTH7#nRv}yBv^<5J{@AYy zMuy5K)cLq)>Nzo#URH_LH<@G7ka_*ztmzl)`5J!whf$L9rt1{r&iXt}R5%zZfM(5=`BVVn zy7UE~XNFl9qQCgyhHu-Adr1a=#lTz9Y4X8%ou%+<+f?g#hKYM1=5dL3)kM9v2GwIv zRH;DTK$?GN`PYQ0QZZ9vP=r4}G+n-~aG5Bov~9j;`LOv@hk;7^wqbV>pZ(TIyicc@ zhaUM;mJ&hx#Pql|%{-TPw)CR6%EODI!$}>-Djjym`hg_Aws@hkDK)sdVOW?fwTkd( zkE~53g)QG1dsg)4$*=F9tQhAndY18F-afF$nnh<)w;7#sJ$x)%NY>F-dH$A24PG@k zLljgSs?N2Ad$?X)ws)Mn$5(1MJQI&8<+CKaG#T|aqddPuN@(F@gxfl50EghRB*A1c zbNN7yR%7cgOm&Y?>@+k#oYP1D`+)*ejTA={7Xm1~IQa(>YBn0nw#o1oUAJ>tfw^IQ zjK|y`w$=4!DR4Gs0mM=3EvUD5fB1VF_xFsJA9AUN4TYCeo$^9eGoI)1tJG*g?*fY* z14NV^rMsu}w6(1;9&=7jSg%(bndYQ!@tI`J0|+9kk zpOca_hx!q9PL-S*>ZXC~Ya26b(sUZh;4|e>_vAM6yGrKX8nFJa_oIaYg@jz-0l*e2TU31Ag(4Wo zCue3h4$jVh@k7@uq;&|sJzu$xfYJN(t}@sx1lEr?wVxWGWV&Paw73G$p&>kvd z3QI^xz$CXKCxuf=`FfG5mifWpT<>t!BG1a2nm|jF`laveq*`oV9t2m!(HT{*^4cve zT|SzvgtvaVVYf9IvHNy0=1drMR-P(6%;U9nww({so(Jk z55;<&0;09^&73|a`!oN$7o_rj|79VN&_-ig_!y8r;XFYadu7!ZrzcB$9!o^Uh>;nY zk%fk?u}k~B{ejJVNuHc$H&Zt^A}F15*nQ39Z>zv$H4q4{E_czqK{V}J<{N&ZDf<^e;KT$ zel(>L6oQ0EpA0(Ry2bcKZ@P;v*!{zTa;yEF;FzLg;FF`6uFmtA+vroAcCWA)Dz6)% z6;}!_@mqPDo!vud+)v^3@h_xI0TK=&MlrWA{J*T=zteqBg0fkJ^py-6(;}dPQ1G-* zT{9|JT}k;F2A$;_fd0Pzextk}a=U{0e?Q5Cg0}Z#mZ#P0ALIJZ*R}~Gp|`LJ8%zFw z>@T4(a-_{7=B@8PqyJujxTwfak=l^rXsQ3U*8dFI7DqxCuJ@$b!}vdMKN~KZ0unB& lO1%F+qyPTGY%*U2Qt(4){?%(yWhCH7QcUh$$y@!;{{xUCRZIW? diff --git a/reader3.py b/reader3.py index d0b9d3f9..21657657 100644 --- a/reader3.py +++ b/reader3.py @@ -100,48 +100,59 @@ def parse_toc_recursive(toc_list, depth=0) -> List[TOCEntry]: result = [] for item in toc_list: - # ebooklib TOC items are either `Link` objects or tuples (Section, [Children]) - if isinstance(item, tuple): - section, children = item - entry = TOCEntry( - title=section.title, - href=section.href, - file_href=section.href.split('#')[0], - anchor=section.href.split('#')[1] if '#' in section.href else "", - children=parse_toc_recursive(children, depth + 1) - ) - result.append(entry) - elif isinstance(item, epub.Link): - entry = TOCEntry( - title=item.title, - href=item.href, - file_href=item.href.split('#')[0], - anchor=item.href.split('#')[1] if '#' in item.href else "" - ) - result.append(entry) - # Note: ebooklib sometimes returns direct Section objects without children - elif isinstance(item, epub.Section): - entry = TOCEntry( - title=item.title, - href=item.href, - file_href=item.href.split('#')[0], - anchor=item.href.split('#')[1] if '#' in item.href else "" - ) - result.append(entry) + try: + # ebooklib TOC items are either `Link` objects or tuples (Section, [Children]) + if isinstance(item, tuple): + section, children = item + child_entries = parse_toc_recursive(children, depth + 1) + + # Some magazines have sections without an href (just a label) + href = getattr(section, 'href', "") or "" + if not href and child_entries: + # Use the first child's href as a fallback for the parent container + href = child_entries[0].href + + entry = TOCEntry( + title=getattr(section, 'title', "Untitled Section"), + href=href, + file_href=href.split('#')[0] if href else "", + anchor=href.split('#')[1] if href and '#' in href else "", + children=child_entries + ) + result.append(entry) + elif isinstance(item, epub.Link): + href = item.href or "" + entry = TOCEntry( + title=item.title or "Untitled", + href=href, + file_href=href.split('#')[0] if href else "", + anchor=href.split('#')[1] if href and '#' in href else "" + ) + result.append(entry) + elif isinstance(item, epub.Section): + href = item.href or "" + entry = TOCEntry( + title=item.title or "Untitled", + href=href, + file_href=href.split('#')[0] if href else "", + anchor=href.split('#')[1] if href and '#' in href else "" + ) + result.append(entry) + except Exception as e: + print(f"Warning: Skipping TOC item due to error: {e}") return result def get_fallback_toc(book_obj) -> List[TOCEntry]: """ - If TOC is missing, build a flat one from the Spine. + If TOC is missing, build a flat one from all documents. """ toc = [] for item in book_obj.get_items(): if item.get_type() == ebooklib.ITEM_DOCUMENT: name = item.get_name() - # Try to guess a title from the content or ID - title = item.get_name().replace('.html', '').replace('.xhtml', '').replace('_', ' ').title() + title = name.replace('.html', '').replace('.xhtml', '').replace('_', ' ').title() toc.append(TOCEntry(title=title, href=name, file_href=name, anchor="")) return toc @@ -152,11 +163,11 @@ def extract_metadata_robust(book_obj) -> BookMetadata: """ def get_list(key): data = book_obj.get_metadata('DC', key) - return [x[0] for x in data] if data else [] + return [str(x[0]) for x in data] if data else [] def get_one(key): data = book_obj.get_metadata('DC', key) - return data[0][0] if data else None + return str(data[0][0]) if data else None return BookMetadata( title=get_one('title') or "Untitled", @@ -192,7 +203,7 @@ def process_epub(epub_path: str, output_dir: str) -> Book: image_map = {} # Key: internal_path, Value: local_relative_path for item in book.get_items(): - if item.get_type() == ebooklib.ITEM_IMAGE: + if item.get_type() in (ebooklib.ITEM_IMAGE, ebooklib.ITEM_COVER): # Normalize filename original_fname = os.path.basename(item.get_name()) # Sanitize filename for OS @@ -200,49 +211,115 @@ def process_epub(epub_path: str, output_dir: str) -> Book: # Save to disk local_path = os.path.join(images_dir, safe_fname) - with open(local_path, 'wb') as f: - f.write(item.get_content()) - - # Map keys: We try both the full internal path and just the basename - # to be robust against messy HTML src attributes - rel_path = f"images/{safe_fname}" - image_map[item.get_name()] = rel_path - image_map[original_fname] = rel_path + try: + with open(local_path, 'wb') as f: + f.write(item.get_content()) + # Map keys: We try both the full internal path and just the basename + # to be robust against messy HTML src attributes + rel_path = f"images/{safe_fname}" + image_map[item.get_name()] = rel_path + image_map[original_fname] = rel_path + except Exception as e: + print(f"Warning: Failed to extract image {original_fname}: {e}") + + # 4b. Extract cover image from epub metadata and write marker file + cover_fname = None + try: + # Method A: OPF + cover_id = None + for meta in book.get_metadata('OPF', 'cover') or []: + if meta and meta[1] and 'content' in meta[1]: + cover_id = meta[1]['content'] + break + if cover_id: + item = book.get_item_with_id(cover_id) + if item: + fname = os.path.basename(item.get_name()) + cover_fname = "".join([c for c in fname if c.isalpha() or c.isdigit() or c in '._-']).strip() + # Method B: item with properties="cover-image" (EPUB3) + if not cover_fname: + for item in book.get_items(): + if item.get_type() == ebooklib.ITEM_COVER: + fname = os.path.basename(item.get_name()) + cover_fname = "".join([c for c in fname if c.isalpha() or c.isdigit() or c in '._-']).strip() + break + except Exception: + pass + if cover_fname and os.path.exists(os.path.join(images_dir, cover_fname)): + with open(os.path.join(output_dir, "cover_image.txt"), "w") as f: + f.write(cover_fname) + print(f"Cover image: {cover_fname}") # 5. Process TOC print("Parsing Table of Contents...") toc_structure = parse_toc_recursive(book.toc) if not toc_structure: - print("Warning: Empty TOC, building fallback from Spine...") + print("Warning: Empty TOC, building fallback from content...") toc_structure = get_fallback_toc(book) - # 6. Process Content (Spine-based to preserve HTML validity) + # 6. Process Content (Collect all Document Items) print("Processing chapters...") - spine_chapters = [] - - # We iterate over the spine (linear reading order) - for i, spine_item in enumerate(book.spine): - item_id, linear = spine_item + + # Aggressive document collection: Any file ending in .html or .xhtml + # This is more robust than relying on the EPUB's internal type metadata + all_docs = { + item.get_name(): item + for item in book.get_items() + if item.get_name().lower().endswith(('.html', '.xhtml', '.htm')) + } + + spine_names = [] + for spine_item in book.spine: + item_id = spine_item[0] item = book.get_item_with_id(item_id) - - if not item: + if item and item.get_name().lower().endswith(('.html', '.xhtml', '.htm')): + spine_names.append(item.get_name()) + + # Add any document that isn't in the spine + all_names = list(all_docs.keys()) + # Sort them to keep some semblance of order for non-spine items + all_names.sort() + + final_names_ordered = [] + seen = set() + + # 1. Spine first + for name in spine_names: + if name not in seen: + final_names_ordered.append(name) + seen.add(name) + + # 2. Everything else + for name in all_names: + # Skip common non-content items if they aren't in spine + if name.lower() in ('nav.xhtml', 'toc.xhtml', 'navigation.xhtml'): continue + if name not in seen: + final_names_ordered.append(name) + seen.add(name) - if item.get_type() == ebooklib.ITEM_DOCUMENT: + spine_chapters = [] + for i, name in enumerate(final_names_ordered): + item = all_docs[name] + item_id = item.get_id() + + try: # Raw content - raw_content = item.get_content().decode('utf-8', errors='ignore') + content_bytes = item.get_content() + raw_content = content_bytes.decode('utf-8', errors='ignore') + + # Skip very short or empty documents (often placeholders) + if len(raw_content) < 50 and i > 0: # keep first one if it's a cover + continue + soup = BeautifulSoup(raw_content, 'html.parser') # A. Fix Images for img in soup.find_all('img'): src = img.get('src', '') if not src: continue - - # Decode URL (part01/image%201.jpg -> part01/image 1.jpg) src_decoded = unquote(src) filename = os.path.basename(src_decoded) - - # Try to find in map if src_decoded in image_map: img['src'] = image_map[src_decoded] elif filename in image_map: @@ -254,7 +331,6 @@ def process_epub(epub_path: str, output_dir: str) -> Book: # C. Extract Body Content only body = soup.find('body') if body: - # Extract inner HTML of body final_html = "".join([str(x) for x in body.contents]) else: final_html = str(soup) @@ -262,13 +338,15 @@ def process_epub(epub_path: str, output_dir: str) -> Book: # D. Create Object chapter = ChapterContent( id=item_id, - href=item.get_name(), # Important: This links TOC to Content - title=f"Section {i+1}", # Fallback, real titles come from TOC + href=name, + title=f"Section {i+1}", content=final_html, text=extract_plain_text(soup), order=i ) spine_chapters.append(chapter) + except Exception as e: + print(f"Error processing chapter {name}: {e}") # 7. Final Assembly final_book = Book( diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..7fc55920 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,55 @@ +aiohappyeyeballs==2.6.1 +aiohttp==3.13.3 +aiosignal==1.4.0 +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.11.0 +async-timeout==5.0.1 +attrs==25.4.0 +beautifulsoup4==4.14.2 +certifi==2026.1.4 +cffi==2.0.0 +charset-normalizer==3.4.4 +click==8.3.1 +cryptography==46.0.5 +dashscope==1.25.12 +distro==1.9.0 +ebooklib==0.20 +edge-tts==7.2.7 +exceptiongroup==1.3.0 +fastapi==0.121.2 +frozenlist==1.8.0 +google-auth==2.48.0 +google-genai==1.66.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +idna==2.10 +jinja2==3.1.6 +lxml==6.0.2 +markupsafe==3.0.3 +multidict==6.7.1 +propcache==0.4.1 +pyasn1==0.6.2 +pyasn1-modules==0.4.2 +pycparser==3.0 +pydantic==2.12.4 +pydantic-core==2.41.5 +python-dotenv==1.2.1 +python-multipart==0.0.22 +requests==2.32.5 +rsa==4.9.1 +six==1.17.0 +sniffio==1.3.1 +socksio==1.0.0 +soupsieve==2.8 +starlette==0.49.3 +tabulate==0.9.0 +tenacity==9.1.4 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==2.6.3 +uvicorn==0.38.0 +websocket-client==1.9.0 +websockets==16.0 +yarl==1.22.0 diff --git a/server.py b/server.py index 9c870dc6..34100ce0 100644 --- a/server.py +++ b/server.py @@ -1,110 +1,1980 @@ import os +import json +import hashlib import pickle +import asyncio +import sqlite3 +import tempfile +import zlib from functools import lru_cache from typing import Optional -from fastapi import FastAPI, Request, HTTPException -from fastapi.responses import HTMLResponse, FileResponse -from fastapi.staticfiles import StaticFiles +from fastapi import FastAPI, Request, HTTPException, UploadFile, File +from fastapi.responses import HTMLResponse, FileResponse, StreamingResponse, Response +from fastapi.middleware.gzip import GZipMiddleware from fastapi.templating import Jinja2Templates +from pydantic import BaseModel +import edge_tts +import httpx + +# AI Imports +from google import genai as google_genai +from dotenv import load_dotenv + +from reader3 import Book, BookMetadata, ChapterContent, TOCEntry, process_epub, save_to_pickle + +# Load .env file automatically +load_dotenv() + +# --- AI Provider System --- +AI_CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ai_config.json') + +_PROVIDER_DEFS = { + 'openai': {'name': 'OpenAI', 'base_url': 'https://api.openai.com/v1/', 'default_model': 'gpt-4o-mini', 'format': 'openai'}, + 'anthropic': {'name': 'Anthropic', 'base_url': 'https://api.anthropic.com/v1/', 'default_model': 'claude-sonnet-4-20250514', 'format': 'anthropic'}, + 'gemini': {'name': 'Google Gemini', 'base_url': '', 'default_model': 'gemini-2.5-flash', 'format': 'gemini'}, + 'deepseek': {'name': 'DeepSeek', 'base_url': 'https://api.deepseek.com/v1/', 'default_model': 'deepseek-chat', 'format': 'openai'}, + 'grok': {'name': 'Grok (xAI)', 'base_url': 'https://api.x.ai/v1/', 'default_model': 'grok-3-mini-fast', 'format': 'openai'}, + 'dashscope': {'name': '阿里云百炼', 'base_url': 'https://dashscope.aliyuncs.com/compatible-mode/v1/', 'default_model': 'qwen-plus', 'format': 'openai'}, + 'volcengine': {'name': '火山引擎', 'base_url': 'https://ark.cn-beijing.volces.com/api/v3/', 'default_model': 'doubao-1-5-pro-32k-250115', 'format': 'openai'}, + 'hunyuan': {'name': '腾讯混元', 'base_url': 'https://api.hunyuan.cloud.tencent.com/v1/', 'default_model': 'hunyuan-turbos-latest', 'format': 'openai'}, + 'minimax': {'name': 'MiniMax', 'base_url': 'https://api.minimax.io/v1/', 'default_model': 'MiniMax-M2.5', 'format': 'openai'}, + 'moonshot': {'name': '月之暗面', 'base_url': 'https://api.moonshot.cn/v1/', 'default_model': 'moonshot-v1-8k', 'format': 'openai'}, + 'siliconflow': {'name': '硅基流动', 'base_url': 'https://api.siliconflow.cn/v1/', 'default_model': 'Qwen/Qwen2.5-7B-Instruct', 'format': 'openai'}, + 'cerebras': {'name': 'Cerebras', 'base_url': 'https://api.cerebras.ai/v1/', 'default_model': 'llama-3.3-70b', 'format': 'openai'}, + 'sambanova': {'name': 'SambaNova', 'base_url': 'https://api.sambanova.ai/v1/', 'default_model': 'Meta-Llama-3.3-70B-Instruct', 'format': 'openai'}, + 'groq': {'name': 'Groq', 'base_url': 'https://api.groq.com/openai/v1/', 'default_model': 'llama-3.3-70b-versatile', 'format': 'openai'}, + 'mistral': {'name': 'Mistral', 'base_url': 'https://api.mistral.ai/v1/', 'default_model': 'mistral-small-latest', 'format': 'openai'}, + 'deepinfra': {'name': 'DeepInfra', 'base_url': 'https://api.deepinfra.com/v1/openai/', 'default_model': 'meta-llama/Llama-3.3-70B-Instruct', 'format': 'openai'}, + 'together': {'name': 'Together AI', 'base_url': 'https://api.together.xyz/v1/', 'default_model': 'meta-llama/Llama-3.3-70B-Instruct-Turbo', 'format': 'openai'}, + 'openrouter': {'name': 'OpenRouter', 'base_url': 'https://openrouter.ai/api/v1/', 'default_model': 'openai/gpt-4o-mini', 'format': 'openai'}, + 'zhipuai': {'name': '智谱AI', 'base_url': 'https://open.bigmodel.cn/api/paas/v4/', 'default_model': 'glm-4.7-flash', 'format': 'openai'}, + 'modelscope': {'name': 'ModelScope', 'base_url': 'https://api-inference.modelscope.cn/v1/', 'default_model': 'Qwen/Qwen2.5-72B-Instruct', 'format': 'openai'}, + 'custom': {'name': '自定义 (OpenAI 兼容)', 'base_url': '', 'default_model': '', 'format': 'openai'}, +} + +_ai_config = {'providers': {}, 'order': []} + +# --- Dictionary Management --- +_DICT_DIR = os.path.join(os.path.dirname(__file__), 'dict') +_DICT_FILES = { + 'ecdict': {'filename': 'stardict.db', 'label': 'ECDICT英文词典', 'label_en': 'ECDICT English', 'size_mb': 307, 'gz_mb': 134}, + 'cn_dict': {'filename': 'cn_dict.db', 'label': '中文词典', 'label_en': 'Chinese Dict', 'size_mb': 48, 'gz_mb': 25}, +} +_DEFAULT_DICT_URL = 'https://github.com/Golden0Voyager/reader3-dict/releases/download/dict-v1' + + +def _load_ai_config(): + """Load AI provider config from JSON file.""" + global _ai_config + if os.path.exists(AI_CONFIG_PATH): + try: + with open(AI_CONFIG_PATH, 'r') as f: + _ai_config = json.load(f) + except (json.JSONDecodeError, IOError): + pass + _ai_config.setdefault('providers', {}) + _ai_config.setdefault('order', []) + + +def _save_ai_config(): + """Save AI provider config to JSON file.""" + with open(AI_CONFIG_PATH, 'w') as f: + json.dump(_ai_config, f, indent=2, ensure_ascii=False) + + +def _get_builtin_providers(): + """Return builtin provider configs from .env (in-memory only, never shown in UI).""" + builtins = [] + gemini_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY") + if gemini_key: + builtins.append(('gemini', gemini_key, 'gemini-2.5-flash')) + zhipu_key = os.getenv("ZHIPUAI_API_KEY") + if zhipu_key: + builtins.append(('zhipuai', zhipu_key, 'glm-4.7-flash')) + return builtins + + +# Set of provider IDs that have builtin .env keys +_BUILTIN_IDS = {pid for pid, _, _ in _get_builtin_providers()} + + +def _get_enabled_providers(): + """Return list of enabled provider configs in priority order. + User-configured providers first, then builtin (.env) providers as fallback.""" + result = [] + seen = set() + def _make_entry(pid, p): + defn = _PROVIDER_DEFS.get(pid, {}) + if not defn and pid.startswith('custom'): + defn = _PROVIDER_DEFS.get('custom', {}) + name = p.get('custom_name') or defn.get('name', pid) + entry = { + 'id': pid, 'name': name, + 'api_key': p['api_key'], + 'model': p.get('model') or defn.get('default_model', ''), + 'base_url': p.get('base_url') or defn.get('base_url', ''), + 'format': defn.get('format', 'openai'), + } + if p.get('temperature') is not None: + entry['temperature'] = p['temperature'] + if p.get('max_tokens') is not None: + entry['max_tokens'] = p['max_tokens'] + return entry + # 1) User-configured providers (from ai_config.json) + for pid in _ai_config.get('order', []): + p = _ai_config['providers'].get(pid) + if p and p.get('enabled') and p.get('api_key'): + result.append(_make_entry(pid, p)) + seen.add(pid) + for pid, p in _ai_config['providers'].items(): + if pid not in seen and p.get('enabled') and p.get('api_key'): + result.append(_make_entry(pid, p)) + seen.add(pid) + # 2) Builtin providers from .env as fallback (skip if user already configured same provider) + for pid, bkey, bmodel in _get_builtin_providers(): + if pid not in seen: + defn = _PROVIDER_DEFS.get(pid, {}) + result.append({ + 'id': pid, 'name': defn.get('name', pid) + ' (内置)', + 'api_key': bkey, + 'model': bmodel, + 'base_url': defn.get('base_url', ''), + 'format': defn.get('format', 'openai'), + }) + return result + + +# Initialize provider config on module load +_load_ai_config() +_enabled = _get_enabled_providers() +if _enabled: + print(f"AI providers: {', '.join(p['name'] + ' (' + p['model'] + ')' for p in _enabled)}") +else: + print("Warning: No AI providers configured. Add one in Settings or set API keys in .env") + +# Google Translate direct API (fast, connection-pooled, independent of AI providers) +_gt_client = httpx.AsyncClient(timeout=5, http2=False, headers={"User-Agent": "Reader3/1.0"}) + +# Shared httpx pool for AI provider calls (reuse TCP/TLS connections) +_ai_client = httpx.AsyncClient(timeout=60, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) + + +# --- Unified AI Dispatch --- + +async def _call_openai_compat(base_url, api_key, model, prompt, temperature, max_tokens, extra_body=None): + """Non-streaming call to OpenAI-compatible chat/completions endpoint.""" + headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} + body = { + "model": model, + "messages": [{"role": "user", "content": prompt}], + "temperature": temperature, + "max_tokens": max_tokens, + } + if extra_body: + body.update(extra_body) + resp = await _ai_client.post(f"{base_url.rstrip('/')}/chat/completions", headers=headers, json=body) + if resp.status_code != 200: + try: + err = resp.json().get('error', {}) + detail = err.get('message', '') if isinstance(err, dict) else str(err) + except Exception: + detail = resp.text[:200] + raise Exception(f"HTTP {resp.status_code}: {detail}") + return resp.json()["choices"][0]["message"]["content"] + + +async def _call_anthropic(base_url, api_key, model, prompt, temperature, max_tokens): + """Non-streaming call to Anthropic Messages API.""" + resp = await _ai_client.post( + f"{base_url.rstrip('/')}/messages", + headers={"x-api-key": api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json"}, + json={"model": model, "max_tokens": max_tokens, "messages": [{"role": "user", "content": prompt}], "temperature": temperature}, + ) + if resp.status_code != 200: + try: + err = resp.json().get('error', {}) + detail = err.get('message', '') if isinstance(err, dict) else str(err) + except Exception: + detail = resp.text[:200] + raise Exception(f"HTTP {resp.status_code}: {detail}") + return resp.json()["content"][0]["text"] + + +async def _call_gemini(api_key, model_name, prompt, temperature, max_tokens): + """Non-streaming call to Gemini via SDK.""" + client = google_genai.Client(api_key=api_key) + config = google_genai.types.GenerateContentConfig(temperature=temperature, max_output_tokens=max_tokens) + response = await asyncio.to_thread(lambda: client.models.generate_content(model=model_name, contents=prompt, config=config)) + return response.text.strip() + + +async def _ai_complete(prompt, temperature=0.7, max_tokens=4096, task=None): + """Unified non-streaming AI call. Returns (text, display_name). Tries providers in order with fallback.""" + providers = _get_enabled_providers() + if not providers: + raise HTTPException(status_code=500, detail="AI not configured — please add a provider in Settings") + # Task-specific provider routing + routing = _ai_config.get('task_routing', {}) + routed_pid = routing.get(task) if task else None + if routed_pid: + routed = [p for p in providers if p['id'] == routed_pid] + others = [p for p in providers if p['id'] != routed_pid] + providers = routed + others + last_error = None + for p in providers: + try: + t = p.get('temperature', temperature) + mt = p.get('max_tokens', max_tokens) + fmt = p['format'] + if fmt == 'gemini': + text = await _call_gemini(p['api_key'], p['model'], prompt, t, mt) + elif fmt == 'anthropic': + text = await _call_anthropic(p['base_url'], p['api_key'], p['model'], prompt, t, mt) + else: + extra = {"thinking": {"type": "disabled"}} if p['id'] == 'zhipuai' else None + text = await _call_openai_compat(p['base_url'], p['api_key'], p['model'], prompt, t, mt, extra_body=extra) + return text.strip(), f"{p['name']} {p['model']}" + except Exception as e: + last_error = e + print(f"[AI] {p['name']} ({p['model']}) failed: {e}") + continue + raise HTTPException(status_code=500, detail=f"All AI providers failed. Last error: {last_error}") + + +async def _stream_openai_compat(base_url, api_key, model, prompt, temperature, max_tokens, extra_body=None): + """Streaming async generator for OpenAI-compatible endpoint.""" + headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} + body = {"model": model, "messages": [{"role": "user", "content": prompt}], "stream": True, "temperature": temperature, "max_tokens": max_tokens} + if extra_body: + body.update(extra_body) + async with httpx.AsyncClient(timeout=60, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) as client: + async with client.stream("POST", f"{base_url.rstrip('/')}/chat/completions", headers=headers, json=body) as resp: + if resp.status_code != 200: + err = await resp.aread() + raise Exception(f"HTTP {resp.status_code}: {err[:300].decode(errors='replace')}") + buf = b'' + async for raw in resp.aiter_bytes(): + buf += raw + while b'\n' in buf: + line_bytes, buf = buf.split(b'\n', 1) + line = line_bytes.decode('utf-8', errors='replace').strip() + if not line.startswith("data: "): + continue + data = line[6:] + if data.strip() == "[DONE]": + return + try: + chunk = json.loads(data) + content = chunk["choices"][0]["delta"].get("content", "") + if content: + yield content + except (json.JSONDecodeError, KeyError, IndexError): + continue + + +async def _stream_anthropic(base_url, api_key, model, prompt, temperature, max_tokens): + """Streaming async generator for Anthropic Messages API.""" + async with httpx.AsyncClient(timeout=60, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) as client: + async with client.stream("POST", f"{base_url.rstrip('/')}/messages", + headers={"x-api-key": api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json"}, + json={"model": model, "max_tokens": max_tokens, "messages": [{"role": "user", "content": prompt}], "temperature": temperature, "stream": True}, + ) as resp: + if resp.status_code != 200: + err = await resp.aread() + raise Exception(f"HTTP {resp.status_code}: {err[:300].decode(errors='replace')}") + buf = b'' + async for raw in resp.aiter_bytes(): + buf += raw + while b'\n' in buf: + line_bytes, buf = buf.split(b'\n', 1) + line = line_bytes.decode('utf-8', errors='replace').strip() + if not line.startswith("data: "): + continue + try: + event = json.loads(line[6:]) + if event.get("type") == "content_block_delta": + text = event.get("delta", {}).get("text", "") + if text: + yield text + except (json.JSONDecodeError, KeyError): + continue + + +async def _ai_stream(prompt, temperature=0.7, max_tokens=4096, task=None): + """Unified streaming AI. Returns async generator with auto-fallback between providers.""" + providers = _get_enabled_providers() + # Task-specific provider routing + routing = _ai_config.get('task_routing', {}) + routed_pid = routing.get(task) if task else None + if routed_pid: + routed = [p for p in providers if p['id'] == routed_pid] + others = [p for p in providers if p['id'] != routed_pid] + providers = routed + others + + async def generate(): + if not providers: + yield "[Error: AI not configured — please add a provider in Settings]" + return + for i, p in enumerate(providers): + try: + t = p.get('temperature', temperature) + mt = p.get('max_tokens', max_tokens) + fmt = p['format'] + if fmt == 'gemini': + client = google_genai.Client(api_key=p['api_key']) + config = google_genai.types.GenerateContentConfig(temperature=t) + response = await asyncio.to_thread( + lambda: client.models.generate_content_stream(model=p['model'], contents=prompt, config=config) + ) + for chunk in response: + try: + if chunk.text: + yield chunk.text + except (ValueError, AttributeError): + continue + elif fmt == 'anthropic': + async for chunk in _stream_anthropic(p['base_url'], p['api_key'], p['model'], prompt, t, mt): + yield chunk + else: + extra = {"thinking": {"type": "disabled"}} if p['id'] == 'zhipuai' else None + async for chunk in _stream_openai_compat(p['base_url'], p['api_key'], p['model'], prompt, t, mt, extra_body=extra): + yield chunk + yield f"\n" + return # Success + except asyncio.CancelledError: + return + except Exception as e: + print(f"[AI stream] {p['name']} ({p['model']}) failed: {e}") + if i == len(providers) - 1: + yield f"\n[Error: {e}]" + continue + + return generate() + +def _detect_cjk_ratio(text): + cjk = sum(1 for c in text if '\u4e00' <= c <= '\u9fff' or '\u3040' <= c <= '\u30ff' or '\uac00' <= c <= '\ud7af') + return cjk / max(len(text), 1) + +_cn_dict_cache: dict[str, str] = {} + +async def _chinese_define(word: str) -> str | None: + """AI-powered Chinese word definition, with in-memory cache.""" + if word in _cn_dict_cache: + return _cn_dict_cache[word] + prompt = f'请用一句话简明解释"{word}"的含义,像词典释义一样简短。只输出释义,不要引号不要前缀。' + try: + result, _ = await _ai_complete(prompt, temperature=0.1, max_tokens=200, task='dict') + _cn_dict_cache[word] = result + return result + except Exception: + return None + +async def _google_translate(text, dest='zh-CN'): + """Direct Google Translate API call, ~100ms with connection reuse.""" + resp = await _gt_client.get('https://translate.googleapis.com/translate_a/single', params={ + 'client': 'gtx', 'sl': 'auto', 'tl': dest, 'dt': 't', 'q': text + }) + data = resp.json() + return ''.join(s[0] for s in data[0] if s[0]) + +def _open_dict_db(path): + """Open a dict SQLite DB with read-optimized settings.""" + conn = sqlite3.connect(path, check_same_thread=False) + conn.row_factory = sqlite3.Row + conn.execute('PRAGMA journal_mode=WAL') + conn.execute('PRAGMA mmap_size=67108864') # 64MB mmap for faster reads + conn.execute('PRAGMA cache_size=-8000') # 8MB page cache + return conn + +# ECDICT offline dictionary (~3.4M entries, <1ms lookup) +_dict_db_path = os.path.join(os.path.dirname(__file__), 'dict', 'stardict.db') +_dict_conn = None +if os.path.exists(_dict_db_path): + _dict_conn = _open_dict_db(_dict_db_path) + +# Chinese dictionary (457K entries: xinhua + moedict, <1ms lookup) +_cn_dict_path = os.path.join(os.path.dirname(__file__), 'dict', 'cn_dict.db') +_cn_dict_conn = None +if os.path.exists(_cn_dict_path): + _cn_dict_conn = _open_dict_db(_cn_dict_path) + +def _reload_dict(): + """Hot-reload dictionary connections after download.""" + global _dict_conn, _cn_dict_conn + for path, conn_name in [(_dict_db_path, '_dict_conn'), (_cn_dict_path, '_cn_dict_conn')]: + if os.path.exists(path) and globals()[conn_name] is None: + globals()[conn_name] = _open_dict_db(path) + +def _dict_lookup(word: str) -> dict | None: + """Look up a word in the local ECDICT dictionary. Returns dict or None.""" + if not _dict_conn: + return None + row = _dict_conn.execute( + 'SELECT word, phonetic, translation, definition FROM dict WHERE word = ? COLLATE NOCASE', + (word.strip(),) + ).fetchone() + if not row or not (row['translation'] or row['definition']): + return None + return { + 'word': row['word'], + 'phonetic': row['phonetic'] or '', + 'translation': (row['translation'] or '').strip(), + 'definition': (row['definition'] or '').strip(), + } + +def _cn_dict_lookup(word: str) -> dict | None: + """Look up a Chinese word in local cn_dict. Returns dict or None.""" + if not _cn_dict_conn: + return None + row = _cn_dict_conn.execute( + 'SELECT word, pinyin, definition, source FROM cn_dict WHERE word = ?', + (word.strip(),) + ).fetchone() + if not row or not row['definition']: + return None + return { + 'word': row['word'], + 'pinyin': row['pinyin'] or '', + 'definition': row['definition'].strip(), + 'source': row['source'], + } + +async def _wiki_summary(term: str) -> dict: + """Fetch Wikipedia summary for a term. Auto-detect language.""" + import urllib.request, urllib.parse + is_cjk = _detect_cjk_ratio(term) > 0.3 + lang = 'zh' if is_cjk else 'en' + try: + encoded = urllib.parse.quote(term) + url = f'https://{lang}.wikipedia.org/api/rest_v1/page/summary/{encoded}' + req = urllib.request.Request(url, headers={'User-Agent': 'Reader3/1.0'}) + def _fetch(): + with urllib.request.urlopen(req, timeout=5) as resp: + return json.loads(resp.read()) + data = await asyncio.to_thread(_fetch) + return { + 'title': data.get('title', ''), + 'extract': data.get('extract', ''), + 'url': data.get('content_urls', {}).get('desktop', {}).get('page', ''), + } + except Exception: + return {} + +import re as _re + +def _safe_dirname(title: str, authors: list[str] = None) -> str: + """Sanitize book title + author for use as directory name.""" + name = _re.sub(r'[\\/:*?"<>|]', '', title).strip() + name = _re.sub(r'\s+', ' ', name) + if authors and authors[0]: + author = _re.sub(r'[\\/:*?"<>|]', '', authors[0]).strip() + if author: + name = f"{name} - {author}" + if len(name) > 80: + name = name[:80].rstrip() + return name or 'untitled' + + +def _process_pdf(pdf_path: str, out_dir: str) -> dict: + """Process a PDF file: extract metadata, render cover from first page, copy PDF.""" + import fitz # PyMuPDF + import shutil + + os.makedirs(out_dir, exist_ok=True) + images_dir = os.path.join(out_dir, "images") + os.makedirs(images_dir, exist_ok=True) + + doc = fitz.open(pdf_path) + meta = doc.metadata or {} + title = meta.get("title", "").strip() or os.path.splitext(os.path.basename(pdf_path))[0] + author = meta.get("author", "").strip() + page_count = len(doc) + + # Render first page as cover + if page_count > 0: + page = doc[0] + pix = page.get_pixmap(matrix=fitz.Matrix(2, 2)) # 2x zoom for quality + cover_path = os.path.join(images_dir, "cover.png") + pix.save(cover_path) + with open(os.path.join(out_dir, "cover_image.txt"), "w") as f: + f.write("cover.png") + + # 提取 PDF 目录(outline/bookmarks) + toc = doc.get_toc() # PyMuPDF 返回 [[level, title, page], ...] + outline = [{"level": item[0], "title": item[1], "page": item[2]} for item in toc] + + doc.close() + + # Copy PDF to output dir + dest_pdf = os.path.join(out_dir, "book.pdf") + if os.path.abspath(pdf_path) != os.path.abspath(dest_pdf): + shutil.copy2(pdf_path, dest_pdf) + + # Write meta.json + meta_info = { + "title": title, + "author": author, + "pages": page_count, + "format": "pdf", + "outline": outline, + } + with open(os.path.join(out_dir, "meta.json"), "w", encoding="utf-8") as f: + json.dump(meta_info, f, ensure_ascii=False) + + return meta_info -from reader3 import Book, BookMetadata, ChapterContent, TOCEntry app = FastAPI() +app.add_middleware(GZipMiddleware, minimum_size=1000) # gzip responses > 1KB templates = Jinja2Templates(directory="templates") # Where are the book folders located? -BOOKS_DIR = "." +BOOKS_DIR = os.path.join(os.path.dirname(__file__), "books") -@lru_cache(maxsize=10) +# TTS audio cache directory +TTS_CACHE_DIR = os.path.join(BOOKS_DIR, ".tts_cache") +os.makedirs(TTS_CACHE_DIR, exist_ok=True) + +# server-cache-lru: Multi-level cache for AI Analysis +# We use both in-memory and could easily extend to disk. +_analysis_cache = {} + +def _get_text_hash(text: str) -> str: + return hashlib.md5(text.encode()).hexdigest() + +@lru_cache(maxsize=20) def load_book_cached(folder_name: str) -> Optional[Book]: - """ - Loads the book from the pickle file. - Cached so we don't re-read the disk on every click. - """ + """Load a Book object from its pickle file, with LRU caching.""" file_path = os.path.join(BOOKS_DIR, folder_name, "book.pkl") if not os.path.exists(file_path): return None - try: with open(file_path, "rb") as f: - book = pickle.load(f) - return book - except Exception as e: - print(f"Error loading book {folder_name}: {e}") + return pickle.load(f) + except Exception: return None +# --- Library metadata index (avoid full pickle load for listing) --- +_LIBRARY_INDEX = os.path.join(BOOKS_DIR, ".library_index.json") + +def _build_library_index(): + """Scan books dir, build/update lightweight metadata index.""" + index = {} + if os.path.exists(_LIBRARY_INDEX): + try: + with open(_LIBRARY_INDEX, 'r') as f: + index = json.load(f) + except (json.JSONDecodeError, IOError): + pass + changed = False + current_dirs = set() + if not os.path.exists(BOOKS_DIR): + return index + for item in os.listdir(BOOKS_DIR): + if not item.endswith("_data") or not os.path.isdir(os.path.join(BOOKS_DIR, item)): + continue + current_dirs.add(item) + # Check for PDF (meta.json) or EPUB (book.pkl) + meta_json_path = os.path.join(BOOKS_DIR, item, "meta.json") + pkl_path = os.path.join(BOOKS_DIR, item, "book.pkl") + if os.path.exists(meta_json_path): + # PDF book + meta_mtime = os.path.getmtime(meta_json_path) + if item in index and index[item].get('_mtime') == meta_mtime: + continue + try: + with open(meta_json_path, 'r', encoding='utf-8') as f: + pdf_meta = json.load(f) + old_display_title = index.get(item, {}).get('display_title') + index[item] = { + 'title': pdf_meta.get('title', 'Untitled'), + 'author': pdf_meta.get('author', ''), + 'chapters': pdf_meta.get('pages', 0), + 'language': 'en', + 'format': 'pdf', + '_mtime': meta_mtime, + } + if old_display_title: + index[item]['display_title'] = old_display_title + changed = True + except (json.JSONDecodeError, IOError): + pass + elif os.path.exists(pkl_path): + # EPUB book + pkl_mtime = os.path.getmtime(pkl_path) + if item in index and index[item].get('_mtime') == pkl_mtime: + continue + book = load_book_cached(item) + if book: + old_display_title = index.get(item, {}).get('display_title') + index[item] = { + 'title': book.metadata.title, + 'author': ', '.join(book.metadata.authors) if book.metadata.authors else '', + 'chapters': len(book.spine), + 'language': book.metadata.language or 'en', + 'format': 'epub', + '_mtime': pkl_mtime, + } + if old_display_title: + index[item]['display_title'] = old_display_title + changed = True + # Remove deleted books from index + for key in list(index.keys()): + if key not in current_dirs: + del index[key] + changed = True + if changed: + with open(_LIBRARY_INDEX, 'w') as f: + json.dump(index, f, ensure_ascii=False) + return index + @app.get("/", response_class=HTMLResponse) async def library_view(request: Request): - """Lists all available processed books.""" + index = await asyncio.to_thread(_build_library_index) books = [] + for item in sorted(index.keys()): + meta = index[item] + books.append({ + "id": item, + "title": meta.get('display_title') or meta['title'], + "original_title": meta['title'], + "author": meta['author'], + "chapters": meta['chapters'], + "language": meta['language'], + "format": meta.get('format', 'epub'), + "mtime": meta.get('_mtime', 0), + }) + return templates.TemplateResponse("library.html", {"request": request, "books": books}) - # Scan directory for folders ending in '_data' that have a book.pkl - if os.path.exists(BOOKS_DIR): - for item in os.listdir(BOOKS_DIR): - if item.endswith("_data") and os.path.isdir(item): - # Try to load it to get the title - book = load_book_cached(item) - if book: - books.append({ - "id": item, - "title": book.metadata.title, - "author": ", ".join(book.metadata.authors), - "chapters": len(book.spine) - }) - return templates.TemplateResponse("library.html", {"request": request, "books": books}) +def _find_cover_image(book_id: str) -> str | None: + """Find cover image path for a book. Returns absolute path or None.""" + import re + images_dir = os.path.join(BOOKS_DIR, book_id, "images") + if not os.path.isdir(images_dir): + return None + + img_exts = ('.jpg', '.jpeg', '.png', '.gif', '.webp') + images = [f for f in os.listdir(images_dir) if f.lower().endswith(img_exts)] -@app.get("/read/{book_id}", response_class=HTMLResponse) -async def redirect_to_first_chapter(book_id: str): - """Helper to just go to chapter 0.""" - return await read_chapter(book_id=book_id, chapter_index=0) + # 1. Marker file left by process_epub (most reliable) + marker = os.path.join(BOOKS_DIR, book_id, "cover_image.txt") + if os.path.exists(marker): + fname = open(marker).read().strip() + path = os.path.join(images_dir, fname) + if os.path.exists(path): + return path + + # 2. File named cover* (most explicit naming convention) + for f in images: + if re.match(r'cover', f, re.I): + return os.path.join(images_dir, f) + # 3. File containing *cover* anywhere in name + for f in images: + if 'cover' in f.lower(): + return os.path.join(images_dir, f) + + # 4. Parse first chapter HTML for image reference (often the cover page) + book = load_book_cached(book_id) + if book and book.spine: + content = book.spine[0].content[:2000] + m = re.search(r'(?:src|href)=["\']([^"\']+\.(?:jpe?g|png|gif|webp))', content, re.I) + if m: + src = m.group(1) + fname = os.path.basename(src) + path = os.path.join(images_dir, fname) + if os.path.exists(path): + return path + + # 5. Fallback: pick the largest image file (covers are usually the biggest) + if images: + largest = max(images, key=lambda f: os.path.getsize(os.path.join(images_dir, f))) + return os.path.join(images_dir, largest) + + return None + + +@app.get("/api/book-cover/{book_id}") +async def serve_book_cover(book_id: str): + """Serve cover image for an imported book.""" + safe_id = os.path.basename(book_id) + cover = _find_cover_image(safe_id) + if cover and os.path.exists(cover): + return FileResponse(cover, headers={"Cache-Control": "no-cache"}) + raise HTTPException(status_code=404, detail="No cover found") @app.get("/read/{book_id}/{chapter_index}", response_class=HTMLResponse) -async def read_chapter(request: Request, book_id: str, chapter_index: int): - """The main reader interface.""" +async def read_chapter(request: Request, book_id: str, chapter_index: str): + """Render a single chapter, or serve an image if chapter_index is a filename.""" + # Handle ../images/ or ../Images/ relative paths (book_id would be "images" or "Images") + if book_id.lower() == 'images': + referer = request.headers.get("referer", "") + import re as _re + from urllib.parse import unquote + m = _re.search(r'/read/([^/]+)/', referer) + if m: + real_book_id = unquote(m.group(1)) + safe_name = os.path.basename(chapter_index) + image_path = os.path.join(BOOKS_DIR, real_book_id, "images", safe_name) + if os.path.exists(image_path): + return FileResponse(image_path) + raise HTTPException(status_code=404, detail="Image not found") + + # If it looks like a file (has extension), serve as image fallback + if '.' in chapter_index: + safe_name = os.path.basename(chapter_index) + image_path = os.path.join(BOOKS_DIR, book_id, "images", safe_name) + if os.path.exists(image_path): + return FileResponse(image_path) + raise HTTPException(status_code=404, detail="Not found") + + try: + idx = int(chapter_index) + except ValueError: + raise HTTPException(status_code=404, detail="Not found") + book = load_book_cached(book_id) - if not book: - raise HTTPException(status_code=404, detail="Book not found") + if not book or idx < 0 or idx >= len(book.spine): + raise HTTPException(status_code=404, detail="Not found") - if chapter_index < 0 or chapter_index >= len(book.spine): - raise HTTPException(status_code=404, detail="Chapter not found") + current_chapter = book.spine[idx] + prev_idx = idx - 1 if idx > 0 else None + next_idx = idx + 1 if idx < len(book.spine) - 1 else None + + # Fix SVG cover distortion: replace preserveAspectRatio="none" and width/height="100%" + import re as _re + content = current_chapter.content - current_chapter = book.spine[chapter_index] + # Normalize image paths: EPUB/images/x.jpg, OEBPS/Images/x.jpg, ../images/x.jpg → images/x.jpg + content = _re.sub(r'(?:(?:\.\.\/)*(?:EPUB|OEBPS|OPS)\/|(?:\.\.\/)+)[Ii]mages/', 'images/', content) - # Calculate Prev/Next links - prev_idx = chapter_index - 1 if chapter_index > 0 else None - next_idx = chapter_index + 1 if chapter_index < len(book.spine) - 1 else None + if ']*)\s+width="100%"', r'\1', content, flags=_re.I) + content = _re.sub(r'(]*)\s+height="100%"', r'\1', content, flags=_re.I) return templates.TemplateResponse("reader.html", { - "request": request, - "book": book, - "current_chapter": current_chapter, - "chapter_index": chapter_index, - "book_id": book_id, - "prev_idx": prev_idx, - "next_idx": next_idx + "request": request, "book": book, "current_chapter": current_chapter, + "chapter_index": idx, "book_id": book_id, + "prev_idx": prev_idx, "next_idx": next_idx, + "chapter_content": content }) + @app.get("/read/{book_id}/images/{image_name}") -async def serve_image(book_id: str, image_name: str): - """ - Serves images specifically for a book. - The HTML contains . - The browser resolves this to /read/{book_id}/images/pic.jpg. - """ - # Security check: ensure book_id is clean - safe_book_id = os.path.basename(book_id) - safe_image_name = os.path.basename(image_name) - - img_path = os.path.join(BOOKS_DIR, safe_book_id, "images", safe_image_name) - - if not os.path.exists(img_path): +async def serve_book_image(book_id: str, image_name: str): + """Serve an image file from a book's extracted images directory.""" + safe_name = os.path.basename(image_name) + image_path = os.path.join(BOOKS_DIR, book_id, "images", safe_name) + if not os.path.exists(image_path): raise HTTPException(status_code=404, detail="Image not found") + return FileResponse(image_path) + + +# --- Rename book --- +@app.post("/api/rename-book/{book_id}") +async def rename_book(book_id: str, request: Request): + """Set display_title for a book. Empty string removes custom title.""" + req = await request.json() + title = req.get("title", "").strip() + index = {} + if os.path.exists(_LIBRARY_INDEX): + with open(_LIBRARY_INDEX, 'r') as f: + index = json.load(f) + if book_id not in index: + raise HTTPException(status_code=404, detail="Book not found") + if title: + index[book_id]['display_title'] = title + else: + index[book_id].pop('display_title', None) + with open(_LIBRARY_INDEX, 'w') as f: + json.dump(index, f, ensure_ascii=False) + return {"ok": True, "display_title": title or None} + + +# --- Delete books --- +@app.post("/api/delete-books") +async def delete_books(req: dict): + """Delete one or more books by their IDs.""" + import shutil + book_ids = req.get("book_ids", []) + if not book_ids: + raise HTTPException(status_code=400, detail="No books specified") + deleted = [] + for bid in book_ids: + safe_id = os.path.basename(bid) + book_dir = os.path.join(BOOKS_DIR, safe_id) + if os.path.isdir(book_dir) and os.path.exists(os.path.join(book_dir, "book.pkl")): + await asyncio.to_thread(shutil.rmtree, book_dir) + deleted.append(safe_id) + load_book_cached.cache_clear() + return {"deleted": deleted, "count": len(deleted)} + +# --- AI MODULE REFACTORED (Gemini 3 Pro standards) --- + +class AIAnalyzeRequest(BaseModel): + book_id: str + chapter_index: int + +@app.post("/api/ai/analyze") +async def analyze_chapter(req: AIAnalyzeRequest): + """Analyze a chapter and return structured insights.""" + book = load_book_cached(req.book_id) + if not book or req.chapter_index < 0 or req.chapter_index >= len(book.spine): + raise HTTPException(status_code=404, detail="Chapter not found") + chapter = book.spine[req.chapter_index] + + # Use text field, fallback to stripping HTML from content + chapter_text = chapter.text.strip() + if not chapter_text and chapter.content: + from html.parser import HTMLParser + class _Strip(HTMLParser): + def __init__(self): + super().__init__() + self.parts = [] + def handle_data(self, d): + self.parts.append(d) + s = _Strip() + s.feed(chapter.content) + chapter_text = ' '.join(s.parts).strip() + + if len(chapter_text) < 20: + return { + "summary": "本章内容过短,无法进行有效分析。", + "key_points": [], + "difficulties": "", + "insight": "" + } + + # server-cache-lru: Fingerprint based on text content hash + content_hash = _get_text_hash(chapter_text) + cache_key = f"{req.book_id}:{req.chapter_index}:{content_hash}" + + if cache_key in _analysis_cache: + return _analysis_cache[cache_key] + + prompt = f"""你是一位经验丰富的读书会领读者,同时具备深厚的文学素养和跨学科知识。 +你正在带领一群认真的成年读者讨论这本书。你的风格:有见地但不卖弄,善于发现文字背后的深意。 + +书名:{book.metadata.title} +作者:{', '.join(book.metadata.authors) if book.metadata.authors else '未知'} +章节:{chapter.title} + +【本章内容】: +{chapter_text[:15000]} + +请阅读后,以领读者的身份进行分析。严格按以下 JSON 返回(不要包含 ```json 标记): + +{{ + "summary": "用 150-250 字概述本章核心内容。不要罗列事件,而是说清楚:这一章到底在讲什么、推进了什么、改变了什么。如果是非虚构类,提炼核心论点和关键论据。", + "key_points": [ + "提炼 3-5 个本章最值得关注的要点。每个要点用一句话点明是什么,再用一句话说明为什么重要。不要泛泛而谈。" + ], + "difficulties": "找出本章中读者可能卡住的地方:专业术语、文化背景、隐晦的表达、复杂的逻辑链等,用大白话解释清楚。如果没有难点就坦诚说明。", + "insight": "分享一个有启发性的深层解读:可以是与其他作品的对比、一个反直觉的发现、当下社会的映射、或者作者没有明说但暗含的立场。要言之有物,避免空洞的感悟。" +}}""" + + try: + text, used_model = await _ai_complete(prompt, temperature=0.3, max_tokens=8192, task='analyze') + + # Robust JSON cleaning + if "{" in text and "}" in text: + text = text[text.find("{"):text.rfind("}")+1] + + result = json.loads(text) + result["_model"] = used_model + _analysis_cache[cache_key] = result # Cache the processed object + return result + except json.JSONDecodeError as e: + raise HTTPException(status_code=500, detail=f"Analysis failed: invalid JSON from AI") + except Exception as e: + raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}") + +@app.post("/api/ai/translate") +async def translate_text(req: dict): + """Translate text using unified AI dispatch.""" + text = req.get("text", "") + if not text: + return {"translation": ""} + + # Auto-detect: if mostly CJK → translate to English, otherwise → translate to Chinese + cjk_count = sum(1 for c in text if '\u4e00' <= c <= '\u9fff' or '\u3040' <= c <= '\u30ff' or '\uac00' <= c <= '\ud7af') + target = "英文" if cjk_count > len(text) * 0.3 else "中文" + + prompt = f"""请将以下文本翻译成{target}。 + +翻译要求: +- 准确传达原意,语句通顺自然,符合{target}的表达习惯 +- 专有名词(人名、地名、术语)首次出现时附注原文 +- 保留原文的语气和风格(正式/口语/文学/技术) +- 只返回译文,不要解释 + +原文: +{text}""" + + try: + result, _ = await _ai_complete(prompt, temperature=0.1, max_tokens=4096, task='translate') + return {"translation": result.strip()} + except Exception as e: + return {"translation": f"[Translation Error: {str(e)}]"} + + +@app.post("/api/ai/translate-stream") +async def translate_text_stream(req: dict): + """Translate text with streaming output using unified AI dispatch.""" + text = req.get("text", "") + if not text: + return StreamingResponse(iter([""]), media_type="text/plain") + + # Auto-detect: if mostly CJK → translate to English, otherwise → translate to Chinese + cjk_count = sum(1 for c in text if '\u4e00' <= c <= '\u9fff' or '\u3040' <= c <= '\u30ff' or '\uac00' <= c <= '\ud7af') + target = "英文" if cjk_count > len(text) * 0.3 else "中文" + + # Build context from book metadata + context = "" + book_id = req.get("book_id") + chapter_index = req.get("chapter_index") + if book_id: + book = load_book_cached(book_id) + if book: + meta = book.metadata + parts = [f"书名《{meta.title}》"] + if meta.authors: + parts.append(f"作者{', '.join(meta.authors)}") + if chapter_index is not None and 0 <= chapter_index < len(book.spine): + ch_title = book.spine[chapter_index].title + if ch_title: + parts.append(f"当前章节「{ch_title}」") + context = f"[{', '.join(parts)}] " + + prompt = f"{context}将以下内容完整翻译成{target},所有词汇都必须翻译,不得保留原文(专有名词首次出现时括号附注原文除外),只返回译文:\n{text}" + + gen = await _ai_stream(prompt, temperature=0.1, max_tokens=4096, task='translate') + return StreamingResponse(gen, media_type="text/plain; charset=utf-8") + + +@app.post("/api/quick-translate") +async def quick_translate(req: dict): + """Dict lookup (instant) → Google Translate fallback (~100ms). Chinese words get AI definitions.""" + text = (req.get("text") or "").strip() + if not text: + return {"translation": ""} + + # Step 1: Try local dictionary for single words/short phrases + dict_result = _dict_lookup(text) + if dict_result: + return { + "source": "dict", + "word": dict_result['word'], + "phonetic": dict_result['phonetic'], + "translation": dict_result['translation'], + "definition": dict_result['definition'], + } + + # Step 2: Chinese text → local Chinese dictionary (457K entries, <1ms) + is_cjk = _detect_cjk_ratio(text) > 0.3 + if is_cjk: + cn_result = _cn_dict_lookup(text) + if cn_result: + return { + "source": "cn-dict", + "word": cn_result['word'], + "pinyin": cn_result['pinyin'], + "translation": cn_result['definition'], + } + + # Step 3: Chinese text → AI definition (fallback for words not in local dict) + if is_cjk and len(text) <= 20: + defn = await _chinese_define(text) + if defn: + return {"source": "ai-dict", "word": text, "translation": defn} + + # Step 4: Fallback to Google Translate + dest = 'en' if is_cjk else 'zh-CN' + try: + translation = await _google_translate(text, dest) + return {"source": "google", "translation": translation} + except Exception as e: + return {"source": "error", "translation": "", "error": str(e)} + + +@app.post("/api/wiki-lookup") +async def wiki_lookup(req: dict): + """Wikipedia summary for a term.""" + text = (req.get("text") or "").strip() + if not text: + return {"extract": ""} + result = await _wiki_summary(text) + return result + + +@app.post("/api/search") +async def search_book(req: dict): + """Full-text search across all chapters of a book.""" + book_id = (req.get("book_id") or "").strip() + query = (req.get("query") or "").strip() + if not book_id or not query: + return {"results": []} + book = load_book_cached(book_id) + if not book: + return {"results": []} + lower_q = query.lower() + results = [] + for ch in book.spine: + text = ch.text or "" + lower_text = text.lower() + pos = 0 + while (pos := lower_text.find(lower_q, pos)) != -1: + start = max(0, pos - 40) + end = min(len(text), pos + len(query) + 40) + snippet = ("..." if start > 0 else "") + text[start:end] + ("..." if end < len(text) else "") + results.append({ + "chapterIndex": ch.order, + "chapterTitle": ch.title, + "snippet": snippet, + "matchStart": pos, + }) + pos += len(query) + if len(results) >= 200: + break + if len(results) >= 200: + break + return {"results": results} + + +class AIChatRequest(BaseModel): + book_id: str + chapter_index: int + question: str + + +@app.post("/api/ai/chat") +async def chat_about_chapter(req: AIChatRequest): + """Answer a free-form question about the current chapter.""" + book = load_book_cached(req.book_id) + if not book or req.chapter_index < 0 or req.chapter_index >= len(book.spine): + raise HTTPException(status_code=404, detail="Chapter not found") + chapter = book.spine[req.chapter_index] + + # Use text field, fallback to stripping HTML from content + chapter_text = chapter.text.strip() + if not chapter_text and chapter.content: + from html.parser import HTMLParser + class _Strip(HTMLParser): + def __init__(self): + super().__init__() + self.parts = [] + def handle_data(self, d): + self.parts.append(d) + s = _Strip() + s.feed(chapter.content) + chapter_text = ' '.join(s.parts).strip() + + prompt = f"""你是这本书的深度阅读伙伴。基于章节内容回答问题时: +- 尽量引用原文中的具体段落或细节来支撑回答 +- 如果问题涉及更广的背景知识,可以拓展,但要标注哪些是原文内容、哪些是补充 +- 用与提问者相同的语言回答 +- 回答要有条理,必要时使用小标题分段 + +书名:{book.metadata.title} +作者:{', '.join(book.metadata.authors) if book.metadata.authors else '未知'} +章节:{chapter.title} + +【章节内容】: +{chapter_text[:12000]} + +【读者提问】: +{req.question}""" + + try: + gen = await _ai_stream(prompt, temperature=0.7, max_tokens=4096, task='chat') + return StreamingResponse(gen, media_type="text/plain; charset=utf-8") + except Exception as e: + raise HTTPException(status_code=500, detail=f"Chat failed: {str(e)}") + + +@app.post("/api/ai/chat-context") +async def chat_with_context(req: dict): + """AI chat with arbitrary text context (for PDF reader etc.).""" + question = (req.get("question") or "").strip() + context = (req.get("context") or "").strip() + title = (req.get("title") or "").strip() + if not question: + raise HTTPException(status_code=400, detail="No question provided") + + parts = [] + if title: + parts.append(f"当前阅读:《{title}》") + if context: + parts.append(f"【选中文本】:\n{context[:6000]}") + parts.append(f"【提问】:{question}") + + prompt = "你是一位知识渊博的阅读助手。用与提问者相同的语言回答。回答要有条理。" \ + "如果提供了选中文本,请结合该文本来回答。\n\n" + "\n\n".join(parts) + + try: + gen = await _ai_stream(prompt, temperature=0.7, max_tokens=4096, task='chat') + return StreamingResponse(gen, media_type="text/plain; charset=utf-8") + except Exception as e: + raise HTTPException(status_code=500, detail=f"Chat failed: {str(e)}") + + +# --- AI Provider Management API --- + +@app.get("/api/ai/providers") +async def get_providers(): + """Return provider list with status (key masked).""" + _load_ai_config() + result = [] + seen = set() + def _entry(pid, p): + defn = _PROVIDER_DEFS.get(pid, {}) + if not defn and pid.startswith('custom'): + defn = _PROVIDER_DEFS.get('custom', {}) + key = p.get('api_key', '') + name = p.get('custom_name') or defn.get('name', pid) + entry = { + 'id': pid, 'name': name, + 'has_key': bool(key), 'key_preview': key[:3] + '******' + key[-3:] if len(key) > 6 else ('******' if key else ''), + 'enabled': p.get('enabled', False), + 'model': p.get('model') or defn.get('default_model', ''), + 'base_url': p.get('base_url', ''), + 'default_model': defn.get('default_model', ''), + 'default_base_url': defn.get('base_url', ''), + } + if p.get('temperature') is not None: + entry['temperature'] = p['temperature'] + if p.get('max_tokens') is not None: + entry['max_tokens'] = p['max_tokens'] + if pid.startswith('custom'): + entry['custom_name'] = p.get('custom_name', '') + return entry + # Build map of builtin keys for filtering migrated entries + builtin_keys = {pid: bkey for pid, bkey, _ in _get_builtin_providers()} + for pid in _ai_config.get('order', []): + if pid in _ai_config.get('providers', {}): + p = _ai_config['providers'][pid] + # Skip entries whose key matches a builtin .env key (legacy migration artifacts) + if pid in builtin_keys and p.get('api_key') == builtin_keys[pid]: + continue + seen.add(pid) + result.append(_entry(pid, p)) + for pid, p in _ai_config.get('providers', {}).items(): + if pid not in seen: + if pid in builtin_keys and p.get('api_key') == builtin_keys[pid]: + continue + result.append(_entry(pid, p)) + return {"providers": result, "available": list(_PROVIDER_DEFS.keys()), "task_routing": _ai_config.get('task_routing', {})} + + +@app.post("/api/ai/providers") +async def save_providers(req: dict): + """Save provider configuration. Empty api_key = keep existing key.""" + providers = req.get('providers', []) + old_providers = _ai_config.get('providers', {}) + _ai_config['providers'] = {} + _ai_config['order'] = [] + for p in providers: + pid = p.get('id', '') + if not pid: + continue + _ai_config['order'].append(pid) + new_key = p.get('api_key', '') + # If no new key provided, keep the old one + if not new_key and pid in old_providers: + new_key = old_providers[pid].get('api_key', '') + _ai_config['providers'][pid] = { + 'api_key': new_key, + 'enabled': p.get('enabled', False), + 'model': p.get('model', ''), + 'base_url': p.get('base_url', ''), + } + if pid.startswith('custom') and p.get('custom_name'): + _ai_config['providers'][pid]['custom_name'] = p['custom_name'] + if p.get('temperature') is not None: + _ai_config['providers'][pid]['temperature'] = p['temperature'] + if p.get('max_tokens') is not None: + _ai_config['providers'][pid]['max_tokens'] = p['max_tokens'] + # Save task routing if provided + if 'task_routing' in req: + _ai_config['task_routing'] = req['task_routing'] + _save_ai_config() + _load_ai_config() + return {"ok": True} + + +@app.post("/api/ai/test-provider") +async def test_provider(req: dict): + """Test a provider's API key by making a minimal request.""" + pid = req.get('id', '') + api_key = req.get('api_key', '') + model_name = req.get('model', '') + base_url = req.get('base_url', '') + defn = _PROVIDER_DEFS.get(pid, {}) + if not defn and pid.startswith('custom'): + defn = _PROVIDER_DEFS.get('custom', {}) + fmt = defn.get('format', 'openai') + # Fallback to stored key if not provided + if not api_key and pid in _ai_config.get('providers', {}): + api_key = _ai_config['providers'][pid].get('api_key', '') + if not api_key: + return {"ok": False, "message": "No API key provided"} + if not model_name: + model_name = defn.get('default_model', '') + + if fmt == 'gemini': + try: + client = google_genai.Client(api_key=api_key) + resp = await asyncio.to_thread(lambda: client.models.generate_content(model=model_name, contents="Say 'ok'")) + return {"ok": True, "message": f"Connected: {model_name}"} + except Exception as e: + return {"ok": False, "message": str(e)} + + if not base_url: + base_url = defn.get('base_url', '') + if not base_url: + return {"ok": False, "message": "No base URL configured"} + def _extract_error(resp): + """Extract human-readable error from API response.""" + try: + body = resp.json() + # OpenAI / DashScope / most providers: {"error": {"message": "..."}} + err = body.get('error') or body.get('errors') or {} + if isinstance(err, dict): + msg = err.get('message', '') + code = err.get('code', '') + return f"{code}: {msg}" if code else msg + if isinstance(err, str): + return err + return str(body)[:200] + except Exception: + return resp.text[:200] if hasattr(resp, 'text') else f"HTTP {resp.status_code}" + + try: + if fmt == 'anthropic': + async with httpx.AsyncClient(timeout=15, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) as client: + resp = await client.post( + f"{base_url.rstrip('/')}/messages", + headers={"x-api-key": api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json"}, + json={"model": model_name, "max_tokens": 10, "messages": [{"role": "user", "content": "Say ok"}]}, + ) + if resp.status_code != 200: + return {"ok": False, "message": _extract_error(resp)} + return {"ok": True, "message": f"Connected: {model_name}"} + else: + headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} + body = {"model": model_name, "messages": [{"role": "user", "content": "Say ok"}], "max_tokens": 10} + if pid == 'zhipuai': + body["thinking"] = {"type": "disabled"} + async with httpx.AsyncClient(timeout=15, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) as client: + resp = await client.post(f"{base_url.rstrip('/')}/chat/completions", headers=headers, json=body) + if resp.status_code != 200: + return {"ok": False, "message": _extract_error(resp)} + return {"ok": True, "message": f"Connected: {model_name}"} + except Exception as e: + return {"ok": False, "message": str(e)} + + +@app.get("/api/ai/export-config") +async def export_config(): + """Export AI provider config as downloadable JSON file.""" + _load_ai_config() + from fastapi.responses import Response + content = json.dumps(_ai_config, indent=2, ensure_ascii=False) + return Response(content=content, media_type="application/json", + headers={"Content-Disposition": "attachment; filename=ai_config.json"}) + + +@app.post("/api/ai/import-config") +async def import_config(req: dict): + """Import AI provider config from uploaded JSON.""" + if 'providers' not in req or not isinstance(req['providers'], dict): + raise HTTPException(status_code=400, detail="Invalid config: missing 'providers' object") + global _ai_config + _ai_config = req + _ai_config.setdefault('order', list(req['providers'].keys())) + _ai_config.setdefault('task_routing', {}) + _save_ai_config() + _load_ai_config() + return {"ok": True, "count": len(_ai_config['providers'])} + + +@app.post("/api/ai/fetch-models") +async def fetch_models(req: dict): + """Fetch available models from a provider.""" + pid = req.get('id', '') + api_key = req.get('api_key', '') + base_url = req.get('base_url', '') + defn = _PROVIDER_DEFS.get(pid, {}) + if not defn and pid.startswith('custom'): + defn = _PROVIDER_DEFS.get('custom', {}) + fmt = defn.get('format', 'openai') + # Fallback to stored key if not provided + if not api_key and pid in _ai_config.get('providers', {}): + api_key = _ai_config['providers'][pid].get('api_key', '') + if not api_key: + return {"models": [], "error": "No API key provided"} + + try: + if fmt == 'gemini': + client = google_genai.Client(api_key=api_key) + models = await asyncio.to_thread(lambda: [m.name.replace('models/', '') for m in client.models.list() if 'generateContent' in (m.supported_actions or [])]) + return {"models": models} + except Exception as e: + return {"models": [], "error": str(e)} + + if not base_url: + base_url = defn.get('base_url', '') + if not base_url: + return {"models": [], "error": "No base URL configured"} + + try: + if fmt == 'anthropic': + async with httpx.AsyncClient(timeout=15, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) as client: + resp = await client.get(f"{base_url.rstrip('/')}/models", headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"}) + resp.raise_for_status() + data = resp.json() + models = [m['id'] for m in data.get('data', [])] + return {"models": models} + else: + async with httpx.AsyncClient(timeout=15, trust_env=True, headers={"User-Agent": "Reader3/1.0"}) as client: + resp = await client.get(f"{base_url.rstrip('/')}/models", headers={"Authorization": f"Bearer {api_key}"}) + resp.raise_for_status() + data = resp.json() + # Handle both {"data": [...]} (OpenAI) and [...] (Together AI) formats + items = data.get('data', data) if isinstance(data, dict) else data + models = [m['id'] for m in items if isinstance(m, dict) and 'id' in m] + return {"models": sorted(models)} + except Exception as e: + return {"models": [], "error": str(e)} + + +# --- Dictionary Management API --- + +@app.get("/api/dict/status") +async def dict_status(): + """Return dictionary install status and download URL availability.""" + dict_url = _ai_config.get('dict_url', '').strip() or _DEFAULT_DICT_URL + items = {} + for did, info in _DICT_FILES.items(): + path = os.path.join(_DICT_DIR, info['filename']) + exists = os.path.exists(path) + items[did] = { + 'label': info['label'], 'label_en': info['label_en'], + 'installed': exists, + 'size_mb': round(os.path.getsize(path) / 1048576) if exists else info['size_mb'], + 'gz_mb': info['gz_mb'], + } + return {'dicts': items, 'has_url': bool(dict_url)} + + +_dict_downloading = set() # prevent concurrent downloads + +@app.post("/api/dict/download") +async def download_dict(req: dict): + """Download and decompress a dictionary file. Streams SSE progress.""" + dict_id = req.get('id', '') + info = _DICT_FILES.get(dict_id) + if not info: + raise HTTPException(status_code=400, detail="Unknown dictionary id") + if dict_id in _dict_downloading: + raise HTTPException(status_code=409, detail="Already downloading") + dict_url = _ai_config.get('dict_url', '').strip() or _DEFAULT_DICT_URL + gz_url = f"{dict_url.rstrip('/')}/{info['filename']}.gz" + dest = os.path.join(_DICT_DIR, info['filename']) + tmp = dest + '.tmp' + expected_gz = info['gz_mb'] * 1048576 # fallback total size + + async def stream(): + _dict_downloading.add(dict_id) + try: + import urllib.request + os.makedirs(_DICT_DIR, exist_ok=True) + decomp = zlib.decompressobj(16 + zlib.MAX_WBITS) + + def _download(): + req = urllib.request.Request(gz_url, headers={'User-Agent': 'Reader3/1.0'}) + proxy_url = _ai_config.get('proxy', '').strip() + if proxy_url: + handler = urllib.request.ProxyHandler({ + 'http': proxy_url, 'https': proxy_url, + }) + opener = urllib.request.build_opener(handler) + else: + opener = urllib.request.build_opener() # respects env http_proxy + return opener.open(req, timeout=300) + + resp = await asyncio.to_thread(_download) + total = int(resp.headers.get('Content-Length', 0)) or expected_gz + downloaded = 0 + last_pct = -1 + with open(tmp, 'wb') as f: + while True: + chunk = await asyncio.to_thread(resp.read, 65536) + if not chunk: + break + decompressed = decomp.decompress(chunk) + f.write(decompressed) + downloaded += len(chunk) + pct = min(int(downloaded * 100 / total), 99) if total else 0 + if pct > last_pct: + last_pct = pct + yield f"data: {json.dumps({'progress': pct})}\n\n" + remaining = decomp.flush() + if remaining: + f.write(remaining) + if os.path.exists(tmp): + os.replace(tmp, dest) + _reload_dict() + yield f"data: {json.dumps({'done': True})}\n\n" + else: + yield f"data: {json.dumps({'error': 'Download failed: temp file missing'})}\n\n" + except Exception as e: + if os.path.exists(tmp): + os.unlink(tmp) + yield f"data: {json.dumps({'error': str(e)})}\n\n" + finally: + _dict_downloading.discard(dict_id) + + return StreamingResponse(stream(), media_type='text/event-stream') + + +# --- TTS MODULE: edge-tts (local, free, low latency) --- + +@app.get("/api/tts") +async def stream_tts(text: str, voice: str = "zh-CN-XiaoxiaoNeural", rate: str = "+0%"): + """Stream TTS audio via edge-tts.""" + communicate = edge_tts.Communicate(text, voice, rate=rate) + async def generate(): + async for chunk in communicate.stream(): + if chunk["type"] == "audio": + yield chunk["data"] + return StreamingResponse(generate(), media_type="audio/mpeg") + +# --- Apple Books Integration --- + +APPLE_BOOKS_DB = os.path.expanduser( + "~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite" +) +APPLE_BOOKS_COVER_DIR = os.path.expanduser( + "~/Library/Containers/com.apple.iBooksX/Data/Library/Caches/BCCoverCache-1/BICDiskDataStore" +) + +@app.get("/api/apple-books") +async def list_apple_books(): + """List books from Apple Books library.""" + if not os.path.exists(APPLE_BOOKS_DB): + return {"books": [], "error": "Apple Books database not found"} + + def _query(): + conn = sqlite3.connect(APPLE_BOOKS_DB) + conn.row_factory = sqlite3.Row + rows = conn.execute( + "SELECT ZTITLE, ZAUTHOR, ZPATH, ZFILESIZE, ZASSETID FROM ZBKLIBRARYASSET " + "WHERE ZTITLE IS NOT NULL AND ZPATH IS NOT NULL AND ZCONTENTTYPE = 1 " + "ORDER BY ZTITLE" + ).fetchall() + conn.close() + return [dict(r) for r in rows] + + rows = await asyncio.to_thread(_query) + + # Build a set of imported book titles (extracted from directory names) for fuzzy matching + imported_titles = set() + if os.path.isdir(BOOKS_DIR): + for d in os.listdir(BOOKS_DIR): + if d.endswith("_data") and os.path.exists(os.path.join(BOOKS_DIR, d, "book.pkl")): + # "失衡的免疫 - 【法】蒙蒂·莱曼_data" → "失衡的免疫" + name = d[:-5] # strip "_data" + title_part = name.split(" - ")[0].strip() + imported_titles.add(title_part.lower()) + + def _normalize(s): + """Strip punctuation and whitespace for fuzzy title comparison.""" + return _re.sub(r'[\s\W]+', '', s).lower() + + imported_normalized = {_normalize(t): t for t in imported_titles} + + def _is_imported(r): + # 1. Check by epub filename + base_name = os.path.splitext(os.path.basename(r['ZPATH']))[0] + if os.path.exists(os.path.join(BOOKS_DIR, base_name + "_data", "book.pkl")): + return True + # 2. Check by Apple Books title + author + title_name = _safe_dirname(r['ZTITLE'], [r['ZAUTHOR']] if r['ZAUTHOR'] else None) + if os.path.exists(os.path.join(BOOKS_DIR, title_name + "_data", "book.pkl")): + return True + # 3. Fuzzy: require full normalized equality (prefix match too loose for serials) + ab_norm = _normalize(r['ZTITLE']) + if ab_norm in imported_normalized: + return True + return False + + books = [] + for r in rows: + path = r['ZPATH'] + if not path or not os.path.exists(path): + continue + books.append({ + "title": r['ZTITLE'], + "author": r['ZAUTHOR'] or '', + "path": path, + "size_mb": round((r['ZFILESIZE'] or 0) / 1048576, 1), + "imported": _is_imported(r), + "asset_id": r['ZASSETID'] or '', + }) + return {"books": books} + + +@app.get("/api/apple-books/cover/{asset_id}") +async def serve_apple_books_cover(asset_id: str): + """Serve a cover image from Apple Books cache.""" + import subprocess + safe_id = os.path.basename(asset_id) + cover_dir = os.path.join(APPLE_BOOKS_COVER_DIR, safe_id) + if not os.path.isdir(cover_dir): + raise HTTPException(status_code=404, detail="Cover not found") + # Check for cached JPEG first + jpeg_path = os.path.join(cover_dir, f"{safe_id}.jpg") + if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 0: + return FileResponse(jpeg_path, media_type="image/jpeg") + # Find largest HEIC file (best quality) + import glob as _glob + heics = sorted(_glob.glob(os.path.join(cover_dir, "*.heic")), key=os.path.getsize, reverse=True) + if not heics: + raise HTTPException(status_code=404, detail="No cover image") + # Convert HEIC to JPEG synchronously (awaited) + result = await asyncio.to_thread( + subprocess.run, + ["sips", "-s", "format", "jpeg", heics[0], "--out", jpeg_path], + capture_output=True, timeout=10 + ) + if os.path.exists(jpeg_path) and os.path.getsize(jpeg_path) > 0: + return FileResponse(jpeg_path, media_type="image/jpeg") + raise HTTPException(status_code=500, detail="Cover conversion failed") + + +@app.post("/api/import-local") +async def import_local_epub(req: dict): + """Import an EPUB or PDF from a local file path (e.g. Apple Books).""" + path = req.get("path", "") + if not path or not os.path.exists(path): + raise HTTPException(status_code=400, detail="File not found") + path_lower = path.lower() + if not (path_lower.endswith('.epub') or path_lower.endswith('.pdf')): + raise HTTPException(status_code=400, detail="Only .epub and .pdf files are supported") + + is_pdf = path_lower.endswith('.pdf') + base_name = os.path.splitext(os.path.basename(path))[0] + out_dir = os.path.join(BOOKS_DIR, base_name + "_data") + + if is_pdf: + meta_info = await asyncio.to_thread(_process_pdf, path, out_dir) + title_name = _safe_dirname(meta_info['title'], [meta_info['author']] if meta_info['author'] else None) + title_dir = os.path.join(BOOKS_DIR, title_name + "_data") + if title_name and title_dir != out_dir and not os.path.exists(title_dir): + os.rename(out_dir, title_dir) + out_dir = title_dir + book_id = os.path.basename(out_dir) + return { + "success": True, + "book_id": book_id, + "title": meta_info['title'], + "chapters": meta_info['pages'], + "has_cover": True, + } + + book_obj = await asyncio.to_thread(process_epub, path, out_dir) + await asyncio.to_thread(save_to_pickle, book_obj, out_dir) + + # Rename directory to book title if different from filename + import shutil + title_name = _safe_dirname(book_obj.metadata.title, book_obj.metadata.authors) + title_dir = os.path.join(BOOKS_DIR, title_name + "_data") + if title_name and title_dir != out_dir and not os.path.exists(title_dir): + os.rename(out_dir, title_dir) + out_dir = title_dir + book_id = os.path.basename(out_dir) + + # Keep a copy of the epub for future reprocessing + epub_copy = os.path.join(out_dir, "source.epub") + if not os.path.exists(epub_copy): + try: + shutil.copy2(path, epub_copy) + except (OSError, PermissionError): + # Apple Books sandbox may block metadata copy; fallback to content-only copy + try: + shutil.copy(path, epub_copy) + except Exception: + pass # Non-critical: source.epub is only for reprocessing + + load_book_cached.cache_clear() + + return { + "success": True, + "book_id": book_id, + "title": book_obj.metadata.title, + "chapters": len(book_obj.spine), + "has_cover": _find_cover_image(book_id) is not None, + } + + +@app.post("/api/upload") +async def upload_epub(file: UploadFile = File(...)): + """Upload and process an EPUB or PDF file.""" + if not file.filename: + raise HTTPException(status_code=400, detail="No file provided") + fname_lower = file.filename.lower() + if not (fname_lower.endswith('.epub') or fname_lower.endswith('.pdf')): + raise HTTPException(status_code=400, detail="Only .epub and .pdf files are supported") + + is_pdf = fname_lower.endswith('.pdf') + suffix = '.pdf' if is_pdf else '.epub' + + # Save to temp file + with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: + content = await file.read() + tmp.write(content) + tmp_path = tmp.name + + try: + # Determine output dir name + base_name = os.path.splitext(file.filename)[0] + out_dir = os.path.join(BOOKS_DIR, base_name + "_data") + + if is_pdf: + meta_info = await asyncio.to_thread(_process_pdf, tmp_path, out_dir) + # Rename directory to title if different from filename + title_name = _safe_dirname(meta_info['title'], [meta_info['author']] if meta_info['author'] else None) + title_dir = os.path.join(BOOKS_DIR, title_name + "_data") + if title_name and title_dir != out_dir and not os.path.exists(title_dir): + os.rename(out_dir, title_dir) + out_dir = title_dir + book_id = os.path.basename(out_dir) + return { + "success": True, + "book_id": book_id, + "title": meta_info['title'], + "chapters": meta_info['pages'], + "has_cover": True, + } + else: + # Process in thread to avoid blocking + book_obj = await asyncio.to_thread(process_epub, tmp_path, out_dir) + await asyncio.to_thread(save_to_pickle, book_obj, out_dir) + + # Rename directory to book title if different from filename + title_name = _safe_dirname(book_obj.metadata.title, book_obj.metadata.authors) + title_dir = os.path.join(BOOKS_DIR, title_name + "_data") + if title_name and title_dir != out_dir and not os.path.exists(title_dir): + os.rename(out_dir, title_dir) + out_dir = title_dir + book_id = os.path.basename(out_dir) + + # Keep a copy of the epub for future reprocessing + import shutil + shutil.copy2(tmp_path, os.path.join(out_dir, "source.epub")) + + # Clear LRU cache so new book appears + load_book_cached.cache_clear() + + return { + "success": True, + "book_id": book_id, + "title": book_obj.metadata.title, + "chapters": len(book_obj.spine), + "has_cover": _find_cover_image(book_id) is not None, + } + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to process file: {str(e)}") + finally: + os.unlink(tmp_path) + + +@app.post("/api/reprocess/{book_id}") +async def reprocess_book(book_id: str): + """Reprocess a book from its saved source.epub.""" + safe_id = os.path.basename(book_id) + book_dir = os.path.join(BOOKS_DIR, safe_id) + source_epub = os.path.join(book_dir, "source.epub") + if not os.path.exists(source_epub): + raise HTTPException(status_code=400, detail="No source epub found. Please re-upload the book.") + + # Copy source.epub to a temp file OUTSIDE book_dir, + # because process_epub will rmtree the entire book_dir + import shutil + tmp_fd, tmp_epub = tempfile.mkstemp(suffix='.epub') + os.close(tmp_fd) + shutil.copy2(source_epub, tmp_epub) + + try: + book_obj = await asyncio.to_thread(process_epub, tmp_epub, book_dir) + await asyncio.to_thread(save_to_pickle, book_obj, book_dir) + # Restore source.epub into the fresh output dir + shutil.copy2(tmp_epub, os.path.join(book_dir, "source.epub")) + load_book_cached.cache_clear() + # Clear AI analysis cache for this book so stale results aren't served + stale_keys = [k for k in _analysis_cache if k.startswith(f"{safe_id}:")] + for k in stale_keys: + del _analysis_cache[k] + return {"success": True, "title": book_obj.metadata.title} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Reprocess failed: {str(e)}") + finally: + if os.path.exists(tmp_epub): + os.unlink(tmp_epub) + + +# --- PDF Reader Routes --- + +@app.get("/read-pdf/{book_id}", response_class=HTMLResponse) +async def read_pdf(request: Request, book_id: str): + """Render PDF reader page.""" + safe_id = os.path.basename(book_id) + meta_path = os.path.join(BOOKS_DIR, safe_id, "meta.json") + if not os.path.exists(meta_path): + raise HTTPException(status_code=404, detail="PDF book not found") + with open(meta_path, 'r', encoding='utf-8') as f: + meta = json.load(f) + # Check for display_title in library index + index = _build_library_index() + display_title = index.get(safe_id, {}).get('display_title') + return templates.TemplateResponse("pdf_reader.html", { + "request": request, + "book_id": safe_id, + "title": display_title or meta.get('title', 'Untitled'), + "pages": meta.get('pages', 0), + "outline_json": json.dumps(meta.get('outline', []), ensure_ascii=False), + }) + + +@app.get("/api/pdf-file/{book_id}") +async def serve_pdf_file(book_id: str): + """Serve the PDF file for the reader.""" + safe_id = os.path.basename(book_id) + pdf_path = os.path.join(BOOKS_DIR, safe_id, "book.pdf") + if not os.path.exists(pdf_path): + raise HTTPException(status_code=404, detail="PDF file not found") + return FileResponse(pdf_path, media_type="application/pdf") + + +@app.post("/api/pdf-search/{book_id}") +async def search_pdf(book_id: str, req: dict): + """Search text in PDF file using PyMuPDF.""" + import fitz + safe_id = os.path.basename(book_id) + pdf_path = os.path.join(BOOKS_DIR, safe_id, "book.pdf") + if not os.path.exists(pdf_path): + raise HTTPException(status_code=404, detail="PDF file not found") + + query = (req.get("query") or "").strip() + if not query: + return {"results": []} + + results = [] + doc = fitz.open(pdf_path) + try: + for page_num in range(len(doc)): + page = doc[page_num] + text_instances = page.search_for(query) + if text_instances: + # Get page text for snippet extraction + page_text = page.get_text("text") + for rect in text_instances: + # Extract snippet around match + idx = page_text.lower().find(query.lower()) + if idx >= 0: + start = max(0, idx - 40) + end = min(len(page_text), idx + len(query) + 40) + snippet = page_text[start:end].replace('\n', ' ').strip() + if start > 0: + snippet = '...' + snippet + if end < len(page_text): + snippet = snippet + '...' + else: + snippet = query + results.append({ + "page": page_num + 1, + "snippet": snippet, + "rect": [rect.x0, rect.y0, rect.x1, rect.y1], + }) + if len(results) >= 200: + break + finally: + doc.close() + + return {"results": results} + + +@app.post("/api/search-cover/{book_id}") +async def search_cover_online(book_id: str, req: dict = None): + """Search for book cover online using Google Books + Douban.""" + safe_id = os.path.basename(book_id) + + # Try EPUB first, then PDF meta.json + book = load_book_cached(safe_id) + meta_path = os.path.join(BOOKS_DIR, safe_id, "meta.json") + if not book and not os.path.exists(meta_path): + raise HTTPException(status_code=404, detail="Book not found") + + # Allow custom query from user + custom_query = (req or {}).get("query", "").strip() + + if custom_query: + query = custom_query + elif book: + title = book.metadata.title + authors = ', '.join(book.metadata.authors) if book.metadata.authors else '' + query = f"{title} {authors}".strip() + else: + with open(meta_path, 'r', encoding='utf-8') as f: + pdf_meta = json.load(f) + query = f"{pdf_meta.get('title', '')} {pdf_meta.get('author', '')}".strip() + + import urllib.parse, urllib.request + + # Detect if query is likely Chinese + has_cjk = any('\u4e00' <= ch <= '\u9fff' for ch in query) + douban_covers = [] + google_covers = [] + + # --- Douban (better for Chinese books) --- + try: + dquery = custom_query if custom_query else _re.sub(r'[\\/:*?"<>|]', '', book.metadata.title).strip() + durl = f"https://book.douban.com/j/subject_suggest?q={urllib.parse.quote(dquery)}" + def _fetch_douban(): + req = urllib.request.Request(durl, headers={ + "User-Agent": "Mozilla/5.0", + "Referer": "https://book.douban.com/", + }) + with urllib.request.urlopen(req, timeout=8) as resp: + return json.loads(resp.read()) + items = await asyncio.to_thread(_fetch_douban) + for item in items[:6]: + pic = item.get("pic", "") + if pic: + img_url = pic.replace("/s/", "/l/") + douban_covers.append({ + "title": item.get("title", ""), + "authors": [item["author_name"]] if item.get("author_name") else [], + "image_url": img_url, + "source": "douban", + }) + except Exception: + pass + + # --- Google Books --- + try: + gurl = f"https://www.googleapis.com/books/v1/volumes?q={urllib.parse.quote(query)}&maxResults=12" + def _fetch_google(): + req = urllib.request.Request(gurl, headers={"User-Agent": "Reader3/1.0"}) + with urllib.request.urlopen(req, timeout=8) as resp: + return json.loads(resp.read()) + data = await asyncio.to_thread(_fetch_google) + for item in data.get("items", []): + info = item.get("volumeInfo", {}) + images = info.get("imageLinks", {}) + # Prefer highest resolution available + img_url = (images.get("extraLarge") or images.get("large") or + images.get("medium") or images.get("thumbnail")) + if img_url: + img_url = img_url.replace("http://", "https://").replace("&edge=curl", "") + # Request higher zoom level for better quality + img_url = _re.sub(r'zoom=\d', 'zoom=3', img_url) + google_covers.append({ + "title": info.get("title", ""), + "authors": info.get("authors", []), + "image_url": img_url, + }) + except Exception: + pass + + # CJK queries: Douban first; otherwise Google first + if has_cjk: + covers = douban_covers + google_covers + else: + covers = google_covers + douban_covers + + return {"covers": covers, "query": query} + + +@app.get("/api/proxy-image") +async def proxy_image(url: str): + """Proxy external images that block direct browser access (e.g. Douban).""" + import urllib.request + if "doubanio.com" not in url and "douban.com" not in url: + raise HTTPException(status_code=400, detail="Only douban images supported") + def _fetch(): + req = urllib.request.Request(url, headers={ + "User-Agent": "Mozilla/5.0", + "Referer": "https://book.douban.com/", + }) + with urllib.request.urlopen(req, timeout=10) as resp: + return resp.read(), resp.headers.get("Content-Type", "image/jpeg") + try: + data, ctype = await asyncio.to_thread(_fetch) + return Response(content=data, media_type=ctype) + except Exception: + raise HTTPException(status_code=502, detail="Failed to fetch image") + + +@app.post("/api/set-cover/{book_id}") +async def set_cover_from_url(book_id: str, req: dict): + """Download an image URL and set it as the book cover.""" + safe_id = os.path.basename(book_id) + images_dir = os.path.join(BOOKS_DIR, safe_id, "images") + os.makedirs(images_dir, exist_ok=True) + + image_url = req.get("image_url", "") + if not image_url: + raise HTTPException(status_code=400, detail="No image URL provided") + + import urllib.request + try: + def _download(): + headers = {"User-Agent": "Mozilla/5.0"} + # Douban images require Referer header + if "doubanio.com" in image_url: + headers["Referer"] = "https://book.douban.com/" + req_obj = urllib.request.Request(image_url, headers=headers) + with urllib.request.urlopen(req_obj, timeout=10) as resp: + return resp.read() + img_data = await asyncio.to_thread(_download) + + # Auto-trim white borders + from PIL import Image, ImageChops + import io + def _trim_and_save(): + img = Image.open(io.BytesIO(img_data)).convert("RGB") + # Create a white background image, diff to find content area + bg = Image.new("RGB", img.size, (255, 255, 255)) + diff = ImageChops.difference(img, bg) + # Tolerance: treat near-white (>240) as white + thresh = diff.point(lambda x: 0 if x < 15 else 255) + bbox = thresh.getbbox() + if bbox: + # Only trim if it removes meaningful border (>2% per side) + w, h = img.size + margin = 0.02 + if (bbox[0] > w * margin or bbox[1] > h * margin + or bbox[2] < w * (1 - margin) or bbox[3] < h * (1 - margin)): + img = img.crop(bbox) + img.save(cover_path, "JPEG", quality=92) + + cover_path = os.path.join(images_dir, "cover.jpg") + await asyncio.to_thread(_trim_and_save) + + # Also write marker + marker_path = os.path.join(BOOKS_DIR, safe_id, "cover_image.txt") + with open(marker_path, "w") as f: + f.write("cover.jpg") + + return {"success": True} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to download cover: {str(e)}") - return FileResponse(img_path) if __name__ == "__main__": import uvicorn - print("Starting server at http://127.0.0.1:8123") uvicorn.run(app, host="127.0.0.1", port=8123) diff --git a/templates/library.html b/templates/library.html index e7d094d3..ee77d785 100644 --- a/templates/library.html +++ b/templates/library.html @@ -3,39 +3,1288 @@ - My Library + + Library — Reader 3

-

Library

+
+
+
+ + + EN + +
+

Library

+
+
+
+ +
+
+ + Upload File +
+
+ + Apple Books +
+
+
+
+ +
+ + {% if books %} +
+
+
+ + + +
+
+
+ +
+
按标题
+
按作者
+
按添加时间
+
+
+
+
+ {% endif %} {% if not books %} -

No processed books found. Run reader3.py on an epub first.

+
+
📖
+

No books yet

+

Drop an EPUB or PDF here, or import from Apple Books

+
{% endif %}
{% for book in books %} -
-
{{ book.title }}
-
- {{ book.author }}
- {{ book.chapters }} sections + +
+ +
-
Read Book -
+
+
{{ book.title }}
+
+
{{ book.author }}
+ {{ (book.format or 'epub')|upper }} +
+
+ {% endfor %}
+ + +
+
+ + 重命名 +
+
+ + 换封面 +
+
+ + 重新处理 +
+
+ + 删除 +
+
+ + +
+
+

Select Cover

+ +
Searching...
+
+ + +
+
+
+ + +
+
+

重命名

+ +
+ + +
+
+
+ + +
+
+
+
+ +
+
正在导入...
+ +
+
+ + +
+
+

确认操作

+

您确定要执行此操作吗?

+
+ + +
+
+
+ + + + + diff --git a/templates/pdf_reader.html b/templates/pdf_reader.html new file mode 100644 index 00000000..f7100001 --- /dev/null +++ b/templates/pdf_reader.html @@ -0,0 +1,2132 @@ + + + + + + + {{ title }} — Reader 3 + + + + + + +
+ + +
+ +
+ + + + + +
+
+ + +
+
+ +
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + + + + +
+ +
+ + +
+ +
+
+ + +
+
+
Theme
+
+
+
+
Zoom
+
+ + 100% + + +
+
+
+ + +
+ +
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+ + +
+ + + + diff --git a/templates/reader.html b/templates/reader.html index c012edca..94a9f84d 100644 --- a/templates/reader.html +++ b/templates/reader.html @@ -3,152 +3,5358 @@ - {{ book.metadata.title }} + + {{ book.metadata.title }} — Reader 3 + + + + + + + - + - - -
-
-
- {{ current_chapter.content | safe }} +
+ +
+
+
+
+
{{ chapter_content | safe }}
+ {% if next_idx is not none %}
{% endif %} +
+
+
+ {% if prev_idx is not none %}PREVIOUS{% endif %} + {{ chapter_index + 1 }} / {{ book.spine|length }} + {% if next_idx is not none %}NEXT{% endif %} +
+
+
+ +
+ +
+
+
+
+ Marginia + your AI reading companion +
+
+ + + +
+
+
+
+
+
Try a quick action below
or type your own question
+
+
+
+
+ + + + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+ +
+
+
+
-
- {% if prev_idx is not none %} - ← Previous - {% else %} - ← Previous - {% endif %} - - - Section {{ chapter_index + 1 }} of {{ book.spine|length }} - - - {% if next_idx is not none %} - Next → - {% else %} - Next → - {% endif %} + + +
+
EN
+
Theme
+
Font Size
+
Line Spacing
+
Reading Width
+
Text Align
+
+
+
+
Dict Management
+
+
+
🎮 ↑↑↓↓←→←→BA
+
AI Models
+
Task Routing
+
Config Management
+ +
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+
+
+
+ + +
+ + +
+ +
+
+ + +
+
+ diff --git a/tools/_md2pdf.py b/tools/_md2pdf.py new file mode 100644 index 00000000..bedf6abd --- /dev/null +++ b/tools/_md2pdf.py @@ -0,0 +1,219 @@ +"""Convert docs/*.md to styled PDFs in package/ via HTML + Playwright (Chromium).""" +import os +import markdown +from playwright.sync_api import sync_playwright + +# docs/ 下需要导出 PDF 的文件列表 +MD_FILES = ['INTRODUCTION.md', 'GUIDE.md'] + + +def md_to_html(md_path): + with open(md_path, 'r') as f: + md_text = f.read() + + body = markdown.markdown(md_text, extensions=['tables', 'fenced_code']) + + return f''' + + + + + + +{body} + +''' + + +def convert_one(browser, md_path, pdf_path): + html_content = md_to_html(md_path) + page = browser.new_page() + page.set_content(html_content, wait_until='networkidle') + page.pdf( + path=pdf_path, + format='A4', + margin={'top': '28mm', 'right': '25mm', 'bottom': '22mm', 'left': '25mm'}, + display_header_footer=True, + header_template='', + footer_template='
', + print_background=True, + ) + page.close() + size_kb = os.path.getsize(pdf_path) / 1024 + print(f' {os.path.basename(pdf_path)} ({size_kb:.0f} KB)') + + +def main(): + root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + docs_dir = os.path.join(root, 'docs') + pkg_dir = os.path.join(root, 'package') + + print('Generating PDFs...') + with sync_playwright() as p: + browser = p.chromium.launch() + for md_name in MD_FILES: + md_path = os.path.join(docs_dir, md_name) + pdf_name = os.path.splitext(md_name)[0] + '.pdf' + pdf_path = os.path.join(pkg_dir, pdf_name) + if not os.path.exists(md_path): + print(f' [skip] {md_name} not found') + continue + convert_one(browser, md_path, pdf_path) + browser.close() + print('Done.') + + +if __name__ == '__main__': + main() diff --git a/tools/export_txt.md b/tools/export_txt.md new file mode 100644 index 00000000..76465c6f --- /dev/null +++ b/tools/export_txt.md @@ -0,0 +1,32 @@ +# export_txt.py — 电子书纯文本导出工具 + +从 Reader3 的 `book.pkl` 数据文件中提取全书文本,导出为 `.txt` 纯文本文件。 + +## 用法 + +```bash +python tools/export_txt.py +``` + +## 示例 + +```bash +# 导出单本书 +python tools/export_txt.py books/dracula_data/book.pkl + +# 输出文件自动保存为同目录下的 book.txt +# → books/dracula_data/book.txt +``` + +## 工作原理 + +1. 加载 `book.pkl`(Reader3 解析 EPUB 后的 pickle 数据) +2. 递归遍历 `book.spine` 中的所有章节对象 +3. 按优先级提取 `content` / `text` / `paragraphs` 等文本属性 +4. 拼接所有章节文本,写入 `.txt` 文件 + +## 适用场景 + +- 将电子书内容导出给 AI 做长文分析 +- 备份书籍的纯文本内容 +- 全文搜索或文本处理 diff --git a/tools/export_txt.py b/tools/export_txt.py new file mode 100644 index 00000000..afe146a8 --- /dev/null +++ b/tools/export_txt.py @@ -0,0 +1,126 @@ +import pickle +import os +import sys + +# === 空壳类名单 (保持不动,防止报错) === +class Book: pass +class BookMetadata: pass +class Chapter: pass +class ChapterContent: pass +class TOCEntry: pass +class Section: pass +class Resource: pass +class Paragraph: pass +class Text: pass +# ==================================== + +def convert_pkl_to_txt(pkl_path): + print(f"🚀 开始处理: {pkl_path}") + + if not os.path.exists(pkl_path): + print(f"❌ 错误:找不到文件 {pkl_path}") + return + + try: + print(f"📖 正在解冻数据...") + with open(pkl_path, 'rb') as f: + book_data = pickle.load(f) + + output_path = pkl_path.replace('.pkl', '.txt') + extracted_text = [] + + print("✅ 解冻成功!") + + # === 核心提取逻辑:递归挖掘机 === + def extract_text(obj, level=0): + # 1. 基础类型直接返回 + if isinstance(obj, str): return obj.strip() + if isinstance(obj, (int, float, bool)): return "" + if obj is None: return "" + + # 防止递归太深 + if level > 5: return "" + + results = [] + + # 2. 如果是列表/元组 (比如 spine 本身就是个列表) + if isinstance(obj, (list, tuple)): + for item in obj: + results.append(extract_text(item, level + 1)) + + # 3. 如果是字典 + elif isinstance(obj, dict): + for v in obj.values(): + results.append(extract_text(v, level + 1)) + + # 4. 如果是对象 (Book, Chapter 等) + elif hasattr(obj, '__dict__'): + # 优先寻找像文本的属性 + priority_attrs = ['content', 'text', 'raw_text', '_content', 'lines', 'paragraphs'] + found_text = False + + # 先看有没有直接的文本属性 + for attr in priority_attrs: + if hasattr(obj, attr): + val = getattr(obj, attr) + if val: + results.append(extract_text(val, level + 1)) + found_text = True + + # 如果没有显式文本属性,就遍历所有属性试试 + if not found_text: + for val in obj.__dict__.values(): + results.append(extract_text(val, level + 1)) + + return "\n".join(filter(None, results)) + + # === 针对你的数据结构进行提取 === + + # 1. 尝试提取书名 (metadata) + if hasattr(book_data, 'metadata'): + meta_text = extract_text(book_data.metadata) + if meta_text: + extracted_text.append(f"《书籍元数据》\n{meta_text}\n{'='*30}\n") + + # 2. 重点进攻:spine (章节列表) + if hasattr(book_data, 'spine'): + print(f"🎯 锁定目标:发现 'spine' 列表,长度: {len(book_data.spine)}") + for i, item in enumerate(book_data.spine): + # 尝试提取这一节的文本 + chapter_text = extract_text(item) + + # 如果提取到了内容,才写入 + if len(chapter_text) > 10: # 忽略太短的碎片 + extracted_text.append(f"\n\n=== 第 {i+1} 部分 ===\n\n{chapter_text}") + # 打印进度条 + if i % 5 == 0: print(f" -> 已提取第 {i+1} 部分...") + else: + print("⚠️ 奇怪,这次没找到 spine?尝试全量暴力提取...") + extracted_text.append(extract_text(book_data)) + + # === 写入文件 === + if extracted_text: + final_content = "\n".join(extracted_text) + with open(output_path, 'w', encoding='utf-8') as f_out: + f_out.write(final_content) + + print(f"\n🎉 胜利!提取完成!") + print(f"📄 文件大小: {len(final_content)} 字符") + print(f"👉 文本文件已保存至: {output_path}") + else: + print("❌ 依然没有提取到文本。这说明 spine 里的对象结构非常特殊。") + # 终极调试:打印 spine里第一个对象到底长啥样 + if hasattr(book_data, 'spine') and len(book_data.spine) > 0: + first_item = book_data.spine[0] + print(f"调试:spine[0] 的类型: {type(first_item)}") + print(f"调试:spine[0] 的属性: {dir(first_item)}") + + except Exception as e: + print(f"\n❌ 程序崩溃: {e}") + traceback.print_exc() + +if __name__ == "__main__": + if len(sys.argv) > 1: + convert_pkl_to_txt(sys.argv[1]) + else: + print("请提供 .pkl 文件路径") \ No newline at end of file diff --git a/uv.lock b/uv.lock index e2e2f808..2a4d6784 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,154 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version < '3.11'", +] + +[[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.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/d6/5aec9313ee6ea9c7cde8b891b69f4ff4001416867104580670a31daeba5b/aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7", size = 738950, upload-time = "2026-01-03T17:29:13.002Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/8fa90a7e6d11ff20a18837a8e2b5dd23db01aabc475aa9271c8ad33299f5/aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821", size = 496099, upload-time = "2026-01-03T17:29:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/b81f744d402510a8366b74eb420fc0cc1170d0c43daca12d10814df85f10/aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845", size = 491072, upload-time = "2026-01-03T17:29:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/56d1d1c0dd334cd203dd97706ce004c1aa24b34a813b0b8daf3383039706/aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af", size = 1671588, upload-time = "2026-01-03T17:29:18.539Z" }, + { url = "https://files.pythonhosted.org/packages/5f/34/8d7f962604f4bc2b4e39eb1220dac7d4e4cba91fb9ba0474b4ecd67db165/aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940", size = 1640334, upload-time = "2026-01-03T17:29:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/fcccf2c668d87337ddeef9881537baee13c58d8f01f12ba8a24215f2b804/aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160", size = 1722656, upload-time = "2026-01-03T17:29:22.531Z" }, + { url = "https://files.pythonhosted.org/packages/aa/98/c6f3b081c4c606bc1e5f2ec102e87d6411c73a9ef3616fea6f2d5c98c062/aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7", size = 1817625, upload-time = "2026-01-03T17:29:24.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c0/cfcc3d2e11b477f86e1af2863f3858c8850d751ce8dc39c4058a072c9e54/aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455", size = 1672604, upload-time = "2026-01-03T17:29:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/6b4ffcbcac4c6a5d041343a756f34a6dd26174ae07f977a64fe028dda5b0/aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279", size = 1554370, upload-time = "2026-01-03T17:29:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f0/e3ddfa93f17d689dbe014ba048f18e0c9f9b456033b70e94349a2e9048be/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e", size = 1642023, upload-time = "2026-01-03T17:29:30.002Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/c14019c9ec60a8e243d06d601b33dcc4fd92379424bde3021725859d7f99/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d", size = 1649680, upload-time = "2026-01-03T17:29:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fd/09c9451dae5aa5c5ed756df95ff9ef549d45d4be663bafd1e4954fd836f0/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808", size = 1692407, upload-time = "2026-01-03T17:29:33.392Z" }, + { url = "https://files.pythonhosted.org/packages/a6/81/938bc2ec33c10efd6637ccb3d22f9f3160d08e8f3aa2587a2c2d5ab578eb/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40", size = 1543047, upload-time = "2026-01-03T17:29:34.855Z" }, + { url = "https://files.pythonhosted.org/packages/f7/23/80488ee21c8d567c83045e412e1d9b7077d27171591a4eb7822586e8c06a/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29", size = 1715264, upload-time = "2026-01-03T17:29:36.389Z" }, + { url = "https://files.pythonhosted.org/packages/e2/83/259a8da6683182768200b368120ab3deff5370bed93880fb9a3a86299f34/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11", size = 1657275, upload-time = "2026-01-03T17:29:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4f/2c41f800a0b560785c10fb316216ac058c105f9be50bdc6a285de88db625/aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd", size = 434053, upload-time = "2026-01-03T17:29:40.074Z" }, + { url = "https://files.pythonhosted.org/packages/80/df/29cd63c7ecfdb65ccc12f7d808cac4fa2a19544660c06c61a4a48462de0c/aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c", size = 456687, upload-time = "2026-01-03T17:29:41.819Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +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" @@ -35,6 +183,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[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 = "beautifulsoup4" version = "4.14.2" @@ -48,6 +214,186 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, ] +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[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/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { 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/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { 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" @@ -69,6 +415,90 @@ 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.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, + { url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, +] + +[[package]] +name = "dashscope" +version = "1.25.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "certifi" }, + { name = "cryptography" }, + { name = "requests" }, + { name = "websocket-client" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/7a/a1a9d0d293ca2cfabf54eedbea237d4d9e233251477d65c51c77667c407c/dashscope-1.25.12-py3-none-any.whl", hash = "sha256:92e98314bac0ff45b7f32f9120946771a85e614371b397f0874c1e579de60f98", size = 1342605, upload-time = "2026-02-09T09:02:50.82Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + [[package]] name = "ebooklib" version = "0.20" @@ -82,12 +512,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/ee/aa015c5de8b0dc42a8e507eae8c2de5d1c0e068c896858fec6d502402ed6/ebooklib-0.20-py3-none-any.whl", hash = "sha256:fff5322517a37e31c972d27be7d982cc3928c16b3dcc5fd7e8f7c0f5d7bcf42b", size = 40995, upload-time = "2025-10-26T20:56:19.104Z" }, ] +[[package]] +name = "edge-tts" +version = "7.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "certifi" }, + { name = "tabulate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/d2/1ce38f6e4fe7275207f4033b0971db489a0b594340ae6bac2320127e71ee/edge_tts-7.2.7.tar.gz", hash = "sha256:0127fba57a742bc48ff0a2a3b24b8324f7859260185274c335b4e54735aff325", size = 27508, upload-time = "2025-12-12T20:54:28.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/89/92ac6b154ab87d236c15e5e0c73cb99be58efb1ea3eb9318c266bf9a36bf/edge_tts-7.2.7-py3-none-any.whl", hash = "sha256:ac11d9e834347e5ee62cbe72e8a56ffd65d3c4e795be14b1e593b72cf6480dd9", size = 30556, upload-time = "2025-12-12T20:54:26.956Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ @@ -109,6 +554,167 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/23/dfb161e91db7c92727db505dc72a384ee79681fe0603f706f9f9f52c2901/fastapi-0.121.2-py3-none-any.whl", hash = "sha256:f2d80b49a86a846b70cc3a03eb5ea6ad2939298bf6a7fe377aa9cd3dd079d358", size = 109201, upload-time = "2025-11-13T17:05:52.718Z" }, ] +[[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/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { 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 = "google-auth" +version = "2.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "google-genai" +version = "1.66.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "google-auth", extra = ["requests"] }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "sniffio" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/ba/0b343b0770d4710ad2979fd9301d7caa56c940174d5361ed4a7cc4979241/google_genai-1.66.0.tar.gz", hash = "sha256:ffc01647b65046bca6387320057aa51db0ad64bcc72c8e3e914062acfa5f7c49", size = 504386, upload-time = "2026-03-04T22:15:28.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/dd/403949d922d4e261b08b64aaa132af4e456c3b15c8e2a2d9e6ef693f66e2/google_genai-1.66.0-py3-none-any.whl", hash = "sha256:7f127a39cf695277104ce4091bb26e417c59bb46e952ff3699c3a982d9c474ee", size = 732174, upload-time = "2026-03-04T22:15:26.63Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -118,13 +724,46 @@ 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 = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "socksio" }, +] + [[package]] name = "idna" -version = "3.11" +version = "2.10" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", size = 175616, upload-time = "2020-06-27T23:45:05.21Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/a2/38/928ddce2273eaa564f6f50de919327bf3a00f091b5baba8dfa9460f3a8a8/idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0", size = 58811, upload-time = "2020-06-27T23:45:03.457Z" }, ] [[package]] @@ -348,6 +987,288 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[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/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { 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 = "pyasn1" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pydantic" version = "2.12.4" @@ -481,27 +1402,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, +] + [[package]] name = "reader3" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "beautifulsoup4" }, + { name = "dashscope" }, { name = "ebooklib" }, + { name = "edge-tts" }, { name = "fastapi" }, + { name = "google-genai" }, + { name = "httpx", extra = ["socks"] }, { name = "jinja2" }, + { name = "python-dotenv" }, + { name = "python-multipart" }, { name = "uvicorn" }, ] [package.metadata] requires-dist = [ { name = "beautifulsoup4", specifier = ">=4.14.2" }, + { name = "dashscope", specifier = ">=1.25.12" }, { name = "ebooklib", specifier = ">=0.20" }, + { name = "edge-tts", specifier = ">=7.2.7" }, { name = "fastapi", specifier = ">=0.121.2" }, + { name = "google-genai", specifier = ">=1.0.0" }, + { name = "httpx", extras = ["socks"], specifier = ">=0.28.1" }, { name = "jinja2", specifier = ">=3.1.6" }, + { name = "python-dotenv", specifier = ">=1.2.1" }, + { name = "python-multipart", specifier = ">=0.0.22" }, { name = "uvicorn", specifier = ">=0.38.0" }, ] +[[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 = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -520,6 +1498,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, +] + [[package]] name = "soupsieve" version = "2.8" @@ -542,6 +1529,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340, upload-time = "2025-11-01T15:12:24.387Z" }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -563,6 +1568,15 @@ 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.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + [[package]] name = "uvicorn" version = "0.38.0" @@ -576,3 +1590,206 @@ sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef468 wheels = [ { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, ] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, + { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, + { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[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/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, + { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, + { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, + { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, + { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, + { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, + { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, + { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { 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 a94f74f442b79f1c92ec73b73c5a9244bf550790 Mon Sep 17 00:00:00 2001 From: Haining Date: Thu, 26 Mar 2026 13:15:37 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=A4=9A=E8=AF=AD=E8=A8=80=20READM?= =?UTF-8?q?E=E3=80=81=E6=88=AA=E5=9B=BE=E4=BC=98=E5=8C=96=E3=80=81UI=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8E=E9=A1=B9=E7=9B=AE=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 8 语言 README(中/英/繁中/日/韩/西/法/意),截图按内容语境分布 - 截图从 PNG 转 JPG 并缩放(9.9MB → 1.3MB) - 修复章节切换时 AI 面板闪烁(提前恢复 sessionStorage 折叠状态) - Gemini 默认模型改为 gemini-3-flash-preview - 统一端口引用为 8123 - 移除 package/ 手动分发目录,.env.example 移至根目录 - 预置《沉思录》电子书至 assets/ Co-Authored-By: Claude Opus 4.6 (1M context) --- package/.env.example => .env.example | 0 .gitignore | 10 +- CLAUDE.md | 10 +- README.en.md | 80 ++++++ README.es.md | 80 ++++++ README.fr.md | 80 ++++++ README.it.md | 80 ++++++ README.ja.md | 80 ++++++ README.ko.md | 80 ++++++ README.md | 80 ++++++ README.zh-Hant.md | 80 ++++++ ...ns by Emperor of Rome Marcus Aurelius.epub | Bin 0 -> 279837 bytes assets/library.jpg | Bin 0 -> 583354 bytes assets/reader_AItools.jpg | Bin 0 -> 259484 bytes assets/reader_catelog.jpg | Bin 0 -> 272929 bytes assets/reader_setting.jpg | Bin 0 -> 187065 bytes docs/GUIDE.md | 228 +++--------------- docs/INTRODUCTION.md | 137 ----------- docs/Original README.md | 27 --- package/pack.sh | 52 ---- package/setup.sh | 64 ----- package/start.sh | 17 -- server.py | 35 ++- templates/library.html | 60 +++-- templates/pdf_reader.html | 90 ++++++- templates/reader.html | 63 +++-- 26 files changed, 868 insertions(+), 565 deletions(-) rename package/.env.example => .env.example (100%) create mode 100644 README.en.md create mode 100644 README.es.md create mode 100644 README.fr.md create mode 100644 README.it.md create mode 100644 README.ja.md create mode 100644 README.ko.md create mode 100644 README.md create mode 100644 README.zh-Hant.md create mode 100644 assets/Meditations by Emperor of Rome Marcus Aurelius.epub create mode 100644 assets/library.jpg create mode 100644 assets/reader_AItools.jpg create mode 100644 assets/reader_catelog.jpg create mode 100644 assets/reader_setting.jpg delete mode 100644 docs/INTRODUCTION.md delete mode 100644 docs/Original README.md delete mode 100755 package/pack.sh delete mode 100755 package/setup.sh delete mode 100755 package/start.sh diff --git a/package/.env.example b/.env.example similarity index 100% rename from package/.env.example rename to .env.example diff --git a/.gitignore b/.gitignore index fce13eb2..e3415cad 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # EPUB source files *.epub +!assets/Meditations by Emperor of Rome Marcus Aurelius.epub # Dictionary data dict/ @@ -27,14 +28,6 @@ __pycache__/ # AI config (contains API keys) ai_config.json -# Package build artifacts -package/*.pdf -package/reader3-release/ -package/reader3-release.zip -# Allow .env.example (overrides parent .env.* rule) -!package/.env.example -package/*.epub - # --- bkit / PDCA metadata --- docs/.pdca-snapshots/ docs/.pdca-status.json @@ -43,6 +36,7 @@ docs/.bkit-memory.json # 敏感环境配置文件(截获 .env) .env .env.* +!.env.example # 插件及工具生成的本地配置(截获 settings.local.json) settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md index 33d48db3..0a380310 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,17 +12,13 @@ | `reader3.py` | EPUB 解析模块 | | `templates/reader.html` | 阅读器页面(CSS + HTML + JS 单文件) | | `templates/library.html` | 图书馆页面(封面墙、上传、Apple Books 扫描) | -| `tools/_md2pdf.py` | 文档转 PDF(Playwright/Chromium),源文件在 `docs/`,输出到 `package/` | -| `package/pack.sh` | 发行版打包脚本,自动生成 PDF 并打包为 `reader3-release.zip` | +| `tools/_md2pdf.py` | 文档转 PDF(Playwright/Chromium),源文件在 `docs/` | ## 常用命令 ```bash # 启动开发服务 -uvicorn server:app --host 0.0.0.0 --port 8000 --reload - -# 打包发行版 -cd package && bash pack.sh +uvicorn server:app --host 0.0.0.0 --port 8123 --reload # 生成文档 PDF python tools/_md2pdf.py @@ -43,8 +39,8 @@ reader3/ ├── server.py / reader3.py # 后端 ├── templates/ # 前端页面 ├── docs/ # 源文档 (md) -├── package/ # 打包相关(pack.sh, setup.sh, start.sh, .env.example, 生成的 PDF) ├── tools/ # 开发工具脚本 +├── assets/ # 截图、预置电子书 ├── dict/ # 词典文件(应用内下载) └── books/ # 导入的电子书数据 ``` diff --git a/README.en.md b/README.en.md new file mode 100644 index 00000000..ec8ca8a6 --- /dev/null +++ b/README.en.md @@ -0,0 +1,80 @@ +English | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> "When technology is democratized by AI, aesthetics and human-centric design become the ultimate differentiators." + +Inspired by Andrej Karpathy's [minimalist reader prototype](https://x.com/karpathy/status/1990577951671509438), Smoothie Reader is a curated, AI-augmented environment designed for deep reading. As a contemporary art curator based in Shanghai, I've evolved the original "copy-paste to LLM" workflow into a fluid, seamless dialogue between the reader, the text, and the machine. + +🏛️ **The Inaugural Exhibit: Meditations**. To honor the spirit of focused contemplation, this repository comes pre-loaded with "Meditations" by Marcus Aurelius (via Project Gutenberg). + +
+ Library
+ Your personal library at a glance +
+ +## ✨ The Curatorial Vision + +While most readers only display text, Smoothie Reader treats every page as an exhibit, transforming reading into a holistic intellectual experience: + +- 🔍 **Intuitive Discovery**: Highlight any text to reveal the action bar. Built-in support for **ECDICT** (English) and Chinese offline dictionaries. +- 🤖 **The Augmented Dialogue**: + - **Narrative Translation**: High-quality, contextual translations elegantly embedded within the text. + - **The Digital Companion**: A sidebar AI assistant supporting streaming dialogue and multi-turn memory for deep intellectual engagement. + - **Global Connectivity**: Seamlessly integrated with 20+ providers including Gemini, OpenAI, Claude, and DeepSeek. + +
+ AI Tools
+ Selection toolbar · Inline translation · AI companion sidebar +
+ +- 🔊 **Sonic Contemplation (TTS)**: Powered by Edge-TTS, offering 90+ voices to breathe life into the written word. +- ✏️ **The Archive**: 5-color aesthetic highlighting, inline annotations, and persistent bookmarks—all preserved in your **local vault**. + +
+ Reading Layout
+ Three-column reading: TOC navigation · Immersive text · Multi-color highlights +
+ +- 🎨 **Minimalist Aesthetics**: 6 curated themes and a flexible 3-column layout, designed for focus and clarity across all devices. + +
+ Settings
+ Themes, typography, dictionaries, AI models — all in one panel +
+ +## 🚀 The Installation + +This project strictly utilizes [uv](https://docs.astral.sh/uv/) for environment orchestration, ensuring a seamless and isolated performance. + +### 1. Preparation +Ensure Python 3.9+ is available. Install the `uv` orchestrator: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. Opening the Exhibit +```bash +# Import your first piece (e.g., Dracula) +uv run reader3.py dracula.epub + +# Launch the environment +uv run server.py +``` +Access the reader at: 👉 **http://localhost:8123** + +### 3. Activating the AI +Enter the reading interface, navigate to **Settings**, and configure your **AI Providers**. Connect your essence to the machine (e.g., [get a free Gemini Key](https://aistudio.google.com/apikey)). + +> [!TIP] +> **🚀 The Secret Passage**: Anywhere on the page, perform the ritual: **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code) to unveil the **Advanced AI Routing Panel** for multi-model orchestration. + +## 🛡️ Sovereignty & Privacy +- **Local Sovereignty**: No data leaves your sanctuary unless you explicitly invoke the AI or TTS. +- **Accountless**: Your archives remain exclusively in your browser's `localStorage`. + +## 📚 Further Inquiry +For detailed orchestration instructions, consult the [Curator's Guide](docs/GUIDE.md). + +## 📄 Provenance +[MIT License](LICENSE) diff --git a/README.es.md b/README.es.md new file mode 100644 index 00000000..fd476208 --- /dev/null +++ b/README.es.md @@ -0,0 +1,80 @@ +[English](README.en.md) | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | Español | [Français](README.fr.md) | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> "Cuando la tecnología es democratizada por la IA, la estética y el diseño centrado en el ser humano se convierten en los diferenciadores definitivos." + +Inspirado por el [prototipo de lector minimalista](https://x.com/karpathy/status/1990577951671509438) de Andrej Karpathy, Smoothie Reader es un entorno curado y aumentado por IA diseñado para una lectura profunda. Como curador de arte contemporáneo establecido en Shanghái, he evolucionado el flujo de trabajo original de "copiar y pegar al LLM" hacia un diálogo fluido y sin interrupciones entre el lector, el texto y la máquina. + +🏛️ **La Exhibición Inaugural: Meditaciones**. Para honrar el espíritu de contemplación enfocada, este repositorio viene precargado con "Meditaciones" de Marco Aurelio (vía Project Gutenberg). + +
+ Library
+ Tu biblioteca personal de un vistazo +
+ +## ✨ La Visión Curatorial + +Mientras que la mayoría de los lectores solo muestran texto, Smoothie Reader trata cada página como una exhibición, transformando la lectura en una experiencia intelectual holística: + +- 🔍 **Descubrimiento Intuitivo**: Resalte cualquier texto para revelar la barra de acciones. Soporte integrado para **ECDICT** (Inglés) y diccionarios chinos offline. +- 🤖 **El Diálogo Aumentado**: + - **Traducción Narrativa**: Traducciones contextuales de alta calidad elegantemente incrustadas dentro del texto. + - **El Compañero Digital**: Un asistente de IA en la barra lateral que admite diálogos en streaming y memoria de múltiples turnos para un compromiso intelectual profundo. + - **Conectividad Global**: Integrado perfectamente con más de 20 proveedores, incluidos Gemini, OpenAI, Claude y DeepSeek. + +
+ AI Tools
+ Barra de selección · Traducción en línea · Panel lateral del compañero IA +
+ +- 🔊 **Contemplación Sonora (TTS)**: Potenciado por Edge-TTS, ofreciendo más de 90 voces para dar vida a la palabra escrita. +- ✏️ **El Archivo**: Resaltado estético de 5 colores, anotaciones en línea y marcadores persistentes—todo preservado en su **bóveda local**. + +
+ Reading Layout
+ Lectura en 3 columnas: navegación ToC · Texto inmersivo · Resaltados multicolor +
+ +- 🎨 **Estética Minimalista**: 6 temas curados y un diseño flexible de 3 columnas, diseñado para el enfoque y la claridad en todos los dispositivos. + +
+ Settings
+ Temas, tipografía, diccionarios, modelos IA — configuración todo en uno +
+ +## 🚀 La Instalación + +Este proyecto utiliza estrictamente [uv](https://docs.astral.sh/uv/) para la orquestación del entorno, asegurando un rendimiento fluido y aislado. + +### 1. Preparación +Asegúrese de tener Python 3.9+ disponible. Instale el orquestador `uv`: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. Apertura de la Exhibición +```bash +# Importe su primera pieza (ej., Dracula) +uv run reader3.py dracula.epub + +# Inicie el entorno +uv run server.py +``` +Acceda al lector en: 👉 **http://localhost:8123** + +### 3. Activación de la IA +Ingrese a la interfaz de lectura, navegue a **Configuración (Settings)** y configure sus **Proveedores de IA**. Conecte su esencia a la máquina (ej., [obtenga una Gemini Key gratis](https://aistudio.google.com/apikey)). + +> [!TIP] +> **🚀 El Pasaje Secreto**: En cualquier lugar de la página, realice el ritual: **`↑ ↑ ↓ ↓ ← → ← → B A`** (Código Konami) para desvelar el **Panel Avanzado de Enrutamiento de IA** para la orquestación multimodelo. + +## 🛡️ Soberanía y Privacidad +- **Soberanía Local**: Ningún dato sale de su santuario a menos que invoque explícitamente la IA o el TTS. +- **Sin Cuenta**: Sus archivos permanecen exclusivamente en el `localStorage` de su navegador. + +## 📚 Consulta Adicional +Para instrucciones detalladas de orquestación, consulte la [Guía del Curador](docs/GUIDE.md). + +## 📄 Procedencia +[MIT License](LICENSE) diff --git a/README.fr.md b/README.fr.md new file mode 100644 index 00000000..51838ecc --- /dev/null +++ b/README.fr.md @@ -0,0 +1,80 @@ +[English](README.en.md) | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | Français | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> "Lorsque la technologie est démocratisée par l'IA, l'esthétique et le design centré sur l'humain deviennent les ultimes différenciateurs." + +Inspiré par le [prototype de lecteur minimaliste](https://x.com/karpathy/status/1990577951671509438) d'Andrej Karpathy, Smoothie Reader est un environnement commissarié et augmenté par l'IA, conçu pour une lecture profonde. En tant que commissaire d'exposition d'art contemporain basé à Shanghai, j'ai fait évoluer le flux de travail original « copier-coller vers le LLM » en un dialogue fluide et transparent entre le lecteur, le texte et la machine. + +🏛️ **L'Exposition Inaugurale : Méditations**. Pour honorer l'esprit de contemplation focalisée, ce dépôt est pré-chargé avec les « Méditations » de Marc Aurèle (via Project Gutenberg). + +
+ Library
+ Votre bibliothèque personnelle en un coup d'œil +
+ +## ✨ La Vision du Commissaire + +Alors que la plupart des lecteurs n'affichent que du texte, Smoothie Reader traite chaque page comme une pièce d'exposition, transformant la lecture en une expérience intellectuelle holistique : + +- 🔍 **Découverte Intuitive** : Surlignez n'importe quel texte pour révéler la barre d'action. Prise en charge intégrée d'**ECDICT** (Anglais) et de dictionnaires chinois hors ligne. +- 🤖 **Le Dialogue Augmenté** : + - **Traduction Narrative** : Traductions contextuelles de haute qualité élégamment intégrées au texte. + - **Le Compagnon Numérique** : Un assistant IA en barre latérale prenant en charge le dialogue en streaming et la mémoire multi-tours pour un engagement intellectuel profond. + - **Connectivité Mondiale** : Intégration transparente avec plus de 20 fournisseurs, dont Gemini, OpenAI, Claude et DeepSeek. + +
+ AI Tools
+ Barre de sélection · Traduction en ligne · Panneau latéral du compagnon IA +
+ +- 🔊 **Contemplation Sonore (TTS)** : Propulsé par Edge-TTS, offrant plus de 90 voix pour insuffler la vie au mot écrit. +- ✏️ **L'Archive** : Surlignage esthétique en 5 couleurs, annotations en ligne et signets persistants—le tout préservé dans votre **coffre-fort local**. + +
+ Reading Layout
+ Lecture en 3 colonnes : navigation ToC · Texte immersif · Surlignages multicolores +
+ +- 🎨 **Esthétique Minimaliste** : 6 thèmes sélectionnés et une mise en page flexible à 3 colonnes, conçue pour la concentration et la clarté sur tous les appareils. + +
+ Settings
+ Thèmes, typographie, dictionnaires, modèles IA — configuration tout-en-un +
+ +## 🚀 L'Installation + +Ce projet utilise strictement [uv](https://docs.astral.sh/uv/) pour l'orchestration de l'environnement, garantissant une performance fluide et isolée. + +### 1. Préparation +Assurez-vous que Python 3.9+ est disponible. Installez l'orchestrateur `uv` : +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. Ouverture de l'Exposition +```bash +# Importez votre première pièce (ex. Dracula) +uv run reader3.py dracula.epub + +# Lancez l'environnement +uv run server.py +``` +Accédez au lecteur sur : 👉 **http://localhost:8123** + +### 3. Activation de l'IA +Entrez dans l'interface de lecture, naviguez vers **Paramètres (Settings)** et configurez vos **Fournisseurs d'IA**. Connectez votre essence à la machine (ex. [obtenez une clé Gemini gratuite](https://aistudio.google.com/apikey)). + +> [!TIP] +> **🚀 Le Passage Secret** : N'importe où sur la page, effectuez le rituel : **`↑ ↑ ↓ ↓ ← → ← → B A`** (Code Konami) pour dévoiler le **Panneau de Routage IA Avancé** pour l'orchestration multi-modèles. + +## 🛡️ Souveraineté et Confidentialité +- **Souveraineté Locale** : Aucune donnée ne quitte votre sanctuaire, sauf si vous invoquez explicitement l'IA ou le TTS. +- **Sans Compte** : Vos archives restent exclusivement dans le `localStorage` de votre navigateur. + +## 📚 Enquête Complémentaire +Pour des instructions d'orchestration détaillées, consultez le [Guide du Commissaire](docs/GUIDE.md). + +## 📄 Provenance +[MIT License](LICENSE) diff --git a/README.it.md b/README.it.md new file mode 100644 index 00000000..d342d67e --- /dev/null +++ b/README.it.md @@ -0,0 +1,80 @@ +[English](README.en.md) | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | Italiano + +# 🧊 Smoothie Reader (Reader3) + +> "Quando la tecnologia viene democratizzata dall'IA, l'estetica e il design incentrato sull'uomo diventano i differenziatori finali." + +Ispirato al [prototipo di lettore minimalista](https://x.com/karpathy/status/1990577951671509438) di Andrej Karpathy, Smoothie Reader è un ambiente curato e aumentato dall'IA progettato per la lettura profonda. Come curatore d'arte contemporanea con base a Shanghai, ho evoluto il flusso di lavoro originale "copia-incolla verso l'LLM" in un dialogo fluido e senza soluzione di continuità tra il lettore, il testo e la macchina. + +🏛️ **La Mostra Inaugurale: Meditazioni**. Per onorare lo spirito di contemplazione focalizzata, questo repository include pre-caricato "Meditazioni" di Marco Aurelio (via Project Gutenberg). + +
+ Library
+ La tua biblioteca personale a colpo d'occhio +
+ +## ✨ La Visione del Curatore + +Mentre la maggior parte dei lettori visualizza solo il testo, Smoothie Reader tratta ogni pagina come un'esposizione, trasformando la lettura in un'esperienza intellettuale olistica: + +- 🔍 **Scoperta Intuitiva**: Evidenzia qualsiasi testo per rivelare la barra delle azioni. Supporto integrato per **ECDICT** (Inglese) e dizionari cinesi offline. +- 🤖 **Il Dialogo Aumentato**: + - **Traduzione Narrativa**: Traduzioni contestuali di alta qualità elegantemente incorporate nel testo. + - **Il Compagno Digitale**: Un assistente IA nella barra laterale che supporta il dialogo in streaming e la memoria multi-turno per un profondo coinvolgimento intellettuale. + - **Connettività Globale**: Perfettamente integrato con oltre 20 provider tra cui Gemini, OpenAI, Claude e DeepSeek. + +
+ AI Tools
+ Barra di selezione · Traduzione in linea · Pannello laterale del compagno IA +
+ +- 🔊 **Contemplazione Sonora (TTS)**: Alimentato da Edge-TTS, offre oltre 90 voci per dare vita alla parola scritta. +- ✏️ **L'Archivio**: Evidenziazione estetica in 5 colori, annotazioni in linea e segnalibri persistenti—tutto conservato nel tuo **caveau locale**. + +
+ Reading Layout
+ Lettura a 3 colonne: navigazione ToC · Testo immersivo · Evidenziazioni multicolore +
+ +- 🎨 **Estetica Minimalista**: 6 temi curati e un layout flessibile a 3 colonne, progettato per la concentrazione e la chiarezza su tutti i dispositivi. + +
+ Settings
+ Temi, tipografia, dizionari, modelli IA — configurazione tutto in uno +
+ +## 🚀 L'Installazione + +Questo progetto utilizza rigorosamente [uv](https://docs.astral.sh/uv/) per l'orchestrazione dell'ambiente, garantendo prestazioni fluide e isolate. + +### 1. Preparazione +Assicurati che Python 3.9+ sia disponibile. Installa l'orchestratore `uv`: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. Apertura della Mostra +```bash +# Importa il tuo primo pezzo (es. Dracula) +uv run reader3.py dracula.epub + +# Avvia l'ambiente +uv run server.py +``` +Accedi al lettore su: 👉 **http://localhost:8123** + +### 3. Attivazione dell'IA +Entra nell'interfaccia di lettura, vai su **Impostazioni (Settings)** e configura i tuoi **AI Provider**. Connetti la tua essenza alla macchina (es. [ottieni una Gemini Key gratuita](https://aistudio.google.com/apikey)). + +> [!TIP] +> **🚀 Il Passaggio Segreto**: In qualsiasi punto della pagina, esegui il rito: **`↑ ↑ ↓ ↓ ← → ← → B A`** (Codice Konami) per svelare il **Pannello Avanzato di Routing IA** per l'orchestrazione multi-modello. + +## 🛡️ Sovranità e Privacy +- **Sovranità Locale**: Nessun dato lascia il tuo santuario a meno che tu non invochi esplicitamente l'IA o il TTS. +- **Senza Account**: I tuoi archivi rimangono esclusivamente nel `localStorage` del tuo browser. + +## 📚 Ulteriori Informazioni +Per istruzioni dettagliate sull'orchestrazione, consulta la [Guida del Curatore](docs/GUIDE.md). + +## 📄 Provenienza +[MIT License](LICENSE) diff --git a/README.ja.md b/README.ja.md new file mode 100644 index 00000000..c359842e --- /dev/null +++ b/README.ja.md @@ -0,0 +1,80 @@ +[English](README.en.md) | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | 日本語 | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> 「AIによって技術が民主化されるとき、美学と人間中心のデザインこそが究極の差別化要因となる。」 + +Andrej Karpathyの[ミニマリスト・リーダー・プロトタイプ](https://x.com/karpathy/status/1990577951671509438)に触発されたSmoothie Readerは、深い読書のために設計された、AI拡張型のキュレーション環境です。上海を拠点とする現代アートキュレーターとして、私は従来の「LLMへのコピペ」というワークフローを、読者、テキスト、そしてマシンの間の流動的でシームレスな対話へと進化させました。 + +🏛️ **初回展示:自省録**。集中した沈思の精神に敬意を表し、本リポジトリにはマルクス・アウレリウスの『自省録』(Project Gutenberg提供)があらかじめロードされています。 + +
+ Library
+ パーソナルライブラリを一望 +
+ +## ✨ キュレーションのビジョン + +多くのリーダーがテキストを表示するだけであるのに対し、Smoothie Readerはすべてのページを一つの展示物として扱い、読書を総合的な知的体験へと変容させます。 + +- 🔍 **直感的な発見**:テキストを選択するだけでアクションバーが表示されます。**ECDICT (英語)** および中国語のオフライン辞書を内蔵。 +- 🤖 **拡張された対話**: + - **ナラティブ翻訳**:文脈に応じた高品質な翻訳を、原文の下にエレガントに埋め込みます。 + - **デジタル・コンパニオン**:ストリーミング対話と多重文脈の記憶をサポートするサイドバーAIアシスタントが、深い知の探求を支えます。 + - **グローバルな接続性**:Gemini, OpenAI, Claude, DeepSeekなど、20以上の主要プロバイダーとシームレスに統合。 + +
+ AI Tools
+ 選択ツールバー · インライン翻訳 · AIコンパニオンサイドバー +
+ +- 🔊 **音の瞑想 (TTS)**:Edge-TTSを搭載し、90種類以上の高品質な音声で書かれた言葉に命を吹き込みます。 +- ✏️ **アーカイブ**:5色の美的ハイライト、インライン注釈、永続的なブックマーク。データはすべて**ローカルの保管庫**に保存されます。 + +
+ Reading Layout
+ 3カラム閲覧:目次ナビ · 没入型テキスト · マルチカラーハイライト +
+ +- 🎨 **ミニマリズムの美学**:厳選された6つのテーマと柔軟な3カラムレイアウトにより、あらゆるデバイスで明晰な集中を。 + +
+ Settings
+ テーマ、タイポグラフィ、辞書、AIモデル — ワンストップ設定 +
+ +## 🚀 インストール + +本プロジェクトは、高速かつ隔離された実行環境を保証するため、最新の [uv](https://docs.astral.sh/uv/) を環境構築ツールとして採用しています。 + +### 1. 準備 +Python 3.9以上がインストールされていることを確認してください。`uv` オーケストレーターをインストールします: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. 展示の開始 +```bash +# 最初の作品をインポート (例: ドラキュラ) +uv run reader3.py dracula.epub + +# 環境を起動 +uv run server.py +``` +ブラウザでアクセス:👉 **http://localhost:8123** + +### 3. AIの起動 +読書インターフェースに入り、**設定 (Settings)** から **AIプロバイダー** を構成します。あなたの感性をマシンと接続してください(例:[Gemini Keyを無料で取得](https://aistudio.google.com/apikey))。 + +> [!TIP] +> **🚀 秘密の通路 (裏技)**:ページ上のどこでも、儀式 **`↑ ↑ ↓ ↓ ← → ← → B A`** (コナミコマンド) を実行すると、マルチモデル運用のための **高度なAIルーティングパネル** が現れます。 + +## 🛡️ 主権とプライバシー +- **ローカルの主権**:AIやTTSを明示的に呼び出さない限り、データがあなたの聖域を離れることはありません。 +- **アカウント不要**:アーカイブはブラウザの `localStorage` のみに保持されます。 + +## 📚 さらなる探求 +詳細な設定手順については、[キュレーターズ・ガイド](docs/GUIDE.md) を参照してください。 + +## 📄 来歴 +[MIT License](LICENSE) diff --git a/README.ko.md b/README.ko.md new file mode 100644 index 00000000..d3335700 --- /dev/null +++ b/README.ko.md @@ -0,0 +1,80 @@ +[English](README.en.md) | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | [日本語](README.ja.md) | 한국어 | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> "기술이 AI에 의해 민주화될 때, 미학과 인간 중심의 디자인이 궁극적인 차별점이 됩니다." + +Andrej Karpathy의 [미니멀리스트 리더 프로토타입](https://x.com/karpathy/status/1990577951671509438)에서 영감을 받은 Smoothie Reader는 깊은 독서를 위해 설계된 AI 증강 큐레이션 환경입니다. 상하이를 기반으로 활동하는 현대 미술 큐레이터로서, 저는 기존의 "LLM에 복사-붙여넣기" 워크플로우를 독자, 텍스트, 그리고 기계 사이의 유동적이고 매끄러운 대화로 진화시켰습니다. + +🏛️ **개막 전시: 명상록**. 집중된 명상의 정신을 기리기 위해, 이 저장소에는 마르쿠스 아우렐리우스의 "명상록"(Project Gutenberg 제공)이 사전 로드되어 있습니다. + +
+ Library
+ 개인 서재를 한눈에 +
+ +## ✨ 큐레이션의 비전 + +대부분의 독서기가 텍스트를 표시하는 데 그치는 반면, Smoothie Reader는 모든 페이지를 하나의 전시물로 대하며 독서를 종합적인 지적 경험으로 변화시킵니다. + +- 🔍 **직관적 탐색**: 텍스트를 선택하기만 하면 액션 바가 나타납니다. **ECDICT (영어)** 및 중국어 오프라인 사전 내장. +- 🤖 **증강된 대화**: + - **내러티브 번역**: 문맥을 반영한 고품질 번역이 원문 아래에 우아하게 삽입됩니다. + - **디지털 동반자**: 스트리밍 대화와 다중 문맥 기억을 지원하는 사이드바 AI 어시스턴트가 깊은 지적 몰입을 돕습니다. + - **글로벌 연결성**: Gemini, OpenAI, Claude, DeepSeek 등 20개 이상의 주요 제공업체와 원활하게 통합됩니다. + +
+ AI Tools
+ 선택 도구 모음 · 인라인 번역 · AI 동반자 사이드바 +
+ +- 🔊 **소리 명상 (TTS)**: Edge-TTS를 통해 90개 이상의 목소리로 텍스트에 생명력을 불어넣습니다. +- ✏️ **아카이브**: 5가지 미학적 하이라이트, 인라인 주석, 영구 북마크. 모든 데이터는 **로컬 저장소**에 보관됩니다. + +
+ Reading Layout
+ 3단 독서: 목차 탐색 · 몰입형 텍스트 · 멀티 컬러 하이라이트 +
+ +- 🎨 **미니멀리즘 미학**: 엄선된 6가지 테마와 유연한 3단 레이아웃으로 모든 기기에서 명료한 집중을 제공합니다. + +
+ Settings
+ 테마, 타이포그래피, 사전, AI 모델 — 올인원 설정 +
+ +## 🚀 설치 및 시작 + +이 프로젝트는 최신 [uv](https://docs.astral.sh/uv/)를 환경 구축 도구로 사용하여 빠르고 격리된 실행 환경을 보장합니다. + +### 1. 준비 +Python 3.9 이상이 필요합니다. `uv` 오케스트레이터를 설치하세요: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. 전시 개시 +```bash +# 첫 번째 작품 가져오기 (예: 드라큘라) +uv run reader3.py dracula.epub + +# 환경 실행 +uv run server.py +``` +브라우저에서 접속: 👉 **http://localhost:8123** + +### 3. AI 활성화 +독서 인터페이스에서 **설정 (Settings)**으로 이동하여 **AI 제공업체**를 구성하세요. 당신의 영감을 기계와 연결하세요 (예: [무료 Gemini 키 받기](https://aistudio.google.com/apikey)). + +> [!TIP] +> **🚀 비밀 통로 (치트키)**: 페이지 어디에서나 **`↑ ↑ ↓ ↓ ← → ← → B A`** (코나미 커맨드)를 입력하면 멀티 모델 운용을 위한 **고급 AI 라우팅 패널**이 나타납니다. + +## 🛡️ 주권과 개인정보 보호 +- **로컬 주권**: AI나 TTS를 명시적으로 호출하지 않는 한 데이터가 당신의 공간을 떠나지 않습니다. +- **계정 불필요**: 아카이브는 브라우저의 `localStorage`에만 보관됩니다. + +## 📚 추가 문의 +상세한 구성 지침은 [큐레이터 가이드](docs/GUIDE.md)를 참조하세요. + +## 📄 출처 +[MIT License](LICENSE) diff --git a/README.md b/README.md new file mode 100644 index 00000000..6808eebb --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +[English](README.en.md) | 简体中文 | [繁體中文](README.zh-Hant.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> "当技术被 AI 民主化,审美与以人为本的设计便成了最终的护城河。" + +灵感源自 Andrej Karpathy 的 [极简阅读器原型](https://x.com/karpathy/status/1990577951671509438),Smoothie Reader 是一个为深度阅读而生的、经 AI 增强的策展式环境。作为一名常驻上海的当代艺术策展人,我将最初的"复制粘贴给大模型"工作流,进化为一场读者、文本与机器之间流动的、无缝的对话。 + +🏛️ **开幕展:沉思录**。为了向专注思考的精神致敬,本仓库预置了马可·奥勒留的《沉思录》(来源于 Project Gutenberg)。 + +
+ Library
+ 个人藏书馆:封面墙一览无余 +
+ +## ✨ 策展愿景 + +大多数阅读器只负责显示文字,而 Smoothie Reader 将每一页视为一个展位,将阅读转化为一场完整的智力体验: + +- 🔍 **直觉探索**:选中文字秒开工具栏。内置支持 **ECDICT 英文词典** 和 **中文离线词典**。 +- 🤖 **增强对话**: + - **叙事级翻译**:一键获取高质量上下文翻译,优雅地内嵌于原文下方。 + - **数字伴读**:侧边栏 AI 助手支持流式输出和多轮记忆,随时进行深度的智力碰撞。 + - **全球连接**:完美支持 Gemini, OpenAI, Claude, DeepSeek 等 20+ 主流模型。 + +
+ AI Tools
+ 划词工具栏 · 行内翻译 · AI 伴读侧栏 +
+ +- 🔊 **声音冥想 (TTS)**:基于 Edge-TTS,提供 90+ 种高质量语音,赋予文字真实的生命力。 +- ✏️ **档案系统**:5 色美学高亮、行内笔记、书签,所有数据全部存储在您的**本地保险库**。 + +
+ Reading Layout
+ 三栏阅读:目录导航 · 沉浸正文 · 多色高亮 +
+ +- 🎨 **极简审美**:6 种精选主题、灵活的三栏布局(目录/正文/AI),适配所有设备的极致视觉。 + +
+ Settings
+ 主题、排版、词典、AI 模型 —— 一站式配置 +
+ +## 🚀 部署安装 + +本项目强制使用现代化的 [uv](https://docs.astral.sh/uv/) 作为环境编排工具,确保极速与隔离的运行表现。 + +### 1. 准备工作 +确保系统已安装 Python 3.9+。安装 `uv` 编排器: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. 开启展览 +```bash +# 导入您的第一件藏品 (以 Dracula 为例) +uv run reader3.py dracula.epub + +# 启动环境 +uv run server.py +``` +然后打开浏览器访问:👉 **http://localhost:8123** + +### 3. 激活 AI +进入阅读页面,点击左上角 **设置 (Settings)**,配置您的 **AI 提供商**,将您的灵性与机器相连(例如,[免费获取 Gemini Key](https://aistudio.google.com/apikey))。 + +> [!TIP] +> **🚀 秘密通道 (秘籍)**:在页面任何地方,依次按下 **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code),即可解锁隐藏的 **高级 AI 路由管理面板**。 + +## 🛡️ 主权与隐私 +- **本地主权**:除了您主动触发的 AI 或 TTS 请求外,没有任何数据会离开您的避风港。 +- **无需账号**:您的档案仅保存在浏览器 `localStorage` 中。 + +## 📚 进阶向导 +详细的配置说明(如离线词典下载、多设备访问、端口修改),请参阅 [策展人指南](docs/GUIDE.md)。 + +## 📄 许可证 +[MIT License](LICENSE) diff --git a/README.zh-Hant.md b/README.zh-Hant.md new file mode 100644 index 00000000..e5e8a42c --- /dev/null +++ b/README.zh-Hant.md @@ -0,0 +1,80 @@ +[English](README.en.md) | [简体中文](README.md) | 繁體中文 | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) + +# 🧊 Smoothie Reader (Reader3) + +> 「當技術被 AI 民主化,審美與以人為本的設計便成了最終的護城河。」 + +靈感源自 Andrej Karpathy 的 [極簡閱讀器原型](https://x.com/karpathy/status/1990577951671509438),Smoothie Reader 是一個為深度閱讀而生的、經 AI 增強的策展式環境。作為一名常駐上海的當代藝術策展人,我將最初的「複製粘貼給大模型」工作流,進化為一場讀者、文本與機器之間流動的、無縫的對話。 + +🏛️ **開幕展:沉思錄**。為了向專注思考的精神致敬,本倉庫預置了馬可·奧勒留的《沉思錄》(來源於 Project Gutenberg)。 + +
+ Library
+ 個人藏書館:封面牆一覽無餘 +
+ +## ✨ 策展願景 + +大多數閱讀器只負責顯示文字,而 Smoothie Reader 將每一頁視為一個展位,將閱讀轉化為一場完整的智力體驗: + +- 🔍 **直覺探索**:選中文字秒開工具欄。內置支持 **ECDICT 英文詞典** 和 **中文離線詞典**。 +- 🤖 **增強對話**: + - **敘事級翻譯**:一鍵獲取高質量上下文翻譯,優雅地內嵌於原文下方。 + - **數位伴讀**:側邊欄 AI 助手支持流式輸出和多輪記憶,隨時進行深度的智力碰撞。 + - **全球連接**:完美支持 Gemini, OpenAI, Claude, DeepSeek 等 20+ 主流模型。 + +
+ AI Tools
+ 劃詞工具欄 · 行內翻譯 · AI 伴讀側欄 +
+ +- 🔊 **聲音冥想 (TTS)**:基於 Edge-TTS,提供 90+ 種高質量語音,賦予文字真實的生命力。 +- ✏️ **檔案系統**:5 色美學高亮、行內筆記、書簽,所有數據全部存儲在您的**本地保險庫**。 + +
+ Reading Layout
+ 三欄閱讀:目錄導航 · 沉浸正文 · 多色高亮 +
+ +- 🎨 **極簡審美**:6 種精選主題、靈活的三欄佈局(目錄/正文/AI),適配所有設備的極致視覺。 + +
+ Settings
+ 主題、排版、詞典、AI 模型 —— 一站式配置 +
+ +## 🚀 部署安裝 + +本項目強制使用現代化的 [uv](https://docs.astral.sh/uv/) 作為環境編排工具,確保極速與隔離的運行表現。 + +### 1. 準備工作 +確保系統已安裝 Python 3.9+。安裝 `uv` 編排器: +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### 2. 開啟展覽 +```bash +# 導入您的第一件藏品 (以 Dracula 為例) +uv run reader3.py dracula.epub + +# 啟動環境 +uv run server.py +``` +然後打開瀏覽器訪問:👉 **http://localhost:8123** + +### 3. 激活 AI +進入閱讀頁面,點擊左上角 **設置 (Settings)**,配置您的 **AI 提供商**,將您的靈性與機器相連(例如,[免費獲取 Gemini Key](https://aistudio.google.com/apikey))。 + +> [!TIP] +> **🚀 秘密通道 (秘籍)**:在頁面任何地方,依次按下 **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code),即可解鎖隱藏的 **高級 AI 路由管理面板**。 + +## 🛡️ 主權與隱私 +- **本地主權**:除了您主動觸發的 AI 或 TTS 請求外,沒有任何數據會離開您的避風港。 +- **無需帳號**:您的檔案僅保存在瀏覽器 `localStorage` 中。 + +## 📚 進階向導 +詳細的配置說明(如離線詞典下載、多設備訪問、端口修改),請參閱 [策展人指南](docs/GUIDE.md)。 + +## 📄 許可證 +[MIT License](LICENSE) diff --git a/assets/Meditations by Emperor of Rome Marcus Aurelius.epub b/assets/Meditations by Emperor of Rome Marcus Aurelius.epub new file mode 100644 index 0000000000000000000000000000000000000000..ffa271998c636c689761ac4eb5773dca92208bf0 GIT binary patch literal 279837 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
@@ -737,7 +737,15 @@

'sort-author': '按作者', 'sort-mtime': '按添加时间', 'sort-asc': '正序 A→Z', - 'sort-desc': '倒序 Z→A' + 'sort-desc': '倒序 Z→A', + 'cover-search-placeholder': '修改搜索关键词...', + 'search': '搜索', + 'ab-select-all': '全选', + 'empty-title': '暂无书籍', + 'empty-hint1': '将 EPUB 或 PDF 拖拽至此,或从 ', + 'ab-title-link': 'Apple 图书导入', + 'imported': '已导入', + 'unknown-author': '未知作者' }, en: { 'importing': 'Importing...', @@ -782,7 +790,15 @@

'sort-author': 'By Author', 'sort-mtime': 'By Date Added', 'sort-asc': 'Ascending A→Z', - 'sort-desc': 'Descending Z→A' + 'sort-desc': 'Descending Z→A', + 'cover-search-placeholder': 'Modify search query...', + 'search': 'Search', + 'ab-select-all': 'Select all', + 'empty-title': 'No books yet', + 'empty-hint1': 'Drop an EPUB or PDF here, or import from ', + 'ab-title-link': 'Apple Books', + 'imported': 'Imported', + 'unknown-author': 'Unknown' } }; @@ -1107,9 +1123,9 @@

- ${b.imported?'Imported':''} + ${b.imported?`${t('imported')}`:''}

`).join(''); updateSelectedCount(); diff --git a/templates/pdf_reader.html b/templates/pdf_reader.html index f7100001..c2ead3b0 100644 --- a/templates/pdf_reader.html +++ b/templates/pdf_reader.html @@ -86,7 +86,8 @@ .pdf-search-hl { position: absolute; background: rgba(250, 204, 21, 0.4); z-index: 1; pointer-events: none; border-radius: 1px; } .pdf-search-hl.active { background: rgba(249, 115, 22, 0.5); } - .page-loading { display: flex; justify-content: center; align-items: center; color: var(--secondary); font-size: 0.85rem; min-height: 400px; opacity: 0.5; } + .page-loading { display: flex; justify-content: center; align-items: center; color: var(--secondary); font-size: 0.85rem; min-height: 400px; opacity: 0.5; animation: loadPulse 1.5s ease-in-out infinite; } + @keyframes loadPulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.8; } } /* Ghost scrollbars */ #sidebar, #pdf-viewer, .ai-content { scrollbar-width: none; -ms-overflow-style: none; } @@ -392,12 +393,12 @@ /* --- Mobile Responsive --- */ @media (max-width: 768px) { - #sidebar { position: fixed; top: 0; left: 0; z-index: 4000; width: 80vw !important; max-width: 300px; height: 100vh; background: var(--surface); transform: translateX(-100%); transition: transform 0.3s var(--transition); } + #sidebar { position: fixed; top: 0; left: 0; z-index: 4000; width: 85vw !important; max-width: 320px; height: 100vh; height: 100dvh; background: var(--surface); transform: translateX(-100%); transition: transform 0.3s var(--transition); } #sidebar.mobile-open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.15); } - #sidebar.collapsed { width: 80vw !important; max-width: 300px; transform: translateX(-100%); } - #panels-container { position: fixed; top: 0; right: 0; z-index: 4000; width: 100vw !important; max-width: 400px; height: 100vh; height: 100dvh; background: var(--surface); border-left: 1px solid var(--outline); transform: translateX(100%); transition: transform 0.3s var(--transition); } + #sidebar.collapsed { width: 85vw !important; max-width: 320px; transform: translateX(-100%); } + #panels-container { position: fixed; top: 0; right: 0; z-index: 4000; width: 85vw !important; max-width: 400px; height: 100vh; height: 100dvh; background: var(--surface); border-left: 1px solid var(--outline); transform: translateX(100%); transition: transform 0.3s var(--transition); } #panels-container.mobile-open { transform: translateX(0); box-shadow: -4px 0 24px rgba(0,0,0,0.15); } - #panels-container.collapsed { width: 100vw !important; max-width: 400px; transform: translateX(100%); } + #panels-container.collapsed { width: 85vw !important; max-width: 400px; transform: translateX(100%); } #panels-container.collapsed.mobile-open { transform: translateX(0); } .resizer { display: none; } .mobile-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 3999; } @@ -657,6 +658,24 @@ let currentPage = 1; let zoomLevel = parseInt(localStorage.getItem(`pdf_zoom_${BOOK_ID}`)) || 0; // 0 = fit width + /* ========== Render Queue (concurrency limiter) ========== */ + const RENDER_CONCURRENCY = 3; + let _renderQueue = []; + let _activeRenders = 0; + function enqueueRender(pageNum) { + if (pageRendered[pageNum - 1] || renderingPages.has(pageNum) || _renderQueue.includes(pageNum)) return; + _renderQueue.push(pageNum); + _flushQueue(); + } + function _flushQueue() { + while (_renderQueue.length > 0 && _activeRenders < RENDER_CONCURRENCY) { + const pn = _renderQueue.shift(); + if (pageRendered[pn - 1] || renderingPages.has(pn)) continue; + _activeRenders++; + renderPage(pn).finally(() => { _activeRenders--; _flushQueue(); }); + } + } + /* ========== Zoom Module ========== */ const Zoom = { _pct: 100, @@ -678,6 +697,8 @@ _rerender() { pageRendered.fill(false); renderingPages.clear(); + _renderQueue = []; + _activeRenders = 0; pageContainers.forEach((c, i) => { const w = this._getWidth(); c.style.width = w + 'px'; @@ -687,7 +708,7 @@ // Re-observe to trigger rendering of visible pages const obs = new IntersectionObserver((entries) => { for (const entry of entries) { - if (entry.isIntersecting) renderPage(parseInt(entry.target.dataset.pageNum)); + if (entry.isIntersecting) enqueueRender(parseInt(entry.target.dataset.pageNum)); } }, { root: viewer, rootMargin: '800px 0px' }); pageContainers.forEach(c => obs.observe(c)); @@ -820,11 +841,21 @@ /* ========== PDF Loading ========== */ async function init() { + // Loading indicator + const loadingEl = document.createElement('div'); + loadingEl.className = 'page-loading'; + loadingEl.id = 'pdf-loading'; + loadingEl.textContent = _lang === 'zh' ? '正在加载文档…' : 'Loading document…'; + viewer.appendChild(loadingEl); pageIndicator.textContent = '...'; + try { pdfDoc = await getDocument(PDF_URL).promise; } catch (e) { - viewer.innerHTML = `
加载失败: ${e.message}
`; + loadingEl.style.color = '#EF4444'; + loadingEl.textContent = (_lang === 'zh' ? '加载失败: ' : 'Load failed: ') + e.message; return; } + + loadingEl.textContent = _lang === 'zh' ? '正在初始化页面…' : 'Initializing pages…'; const totalPages = pdfDoc.numPages; // Get first page to determine base width const firstPage = await pdfDoc.getPage(1); @@ -845,13 +876,28 @@ pageHeights.push(w * (vp0.height / vp0.width)); } + // Render observer — uses enqueueRender for concurrency control const observer = new IntersectionObserver((entries) => { for (const entry of entries) { - if (entry.isIntersecting) renderPage(parseInt(entry.target.dataset.pageNum)); + if (entry.isIntersecting) enqueueRender(parseInt(entry.target.dataset.pageNum)); } }, { root: viewer, rootMargin: '800px 0px' }); pageContainers.forEach(c => observer.observe(c)); + // Cleanup observer — reclaim memory for pages far off-screen + const cleanupObserver = new IntersectionObserver((entries) => { + for (const entry of entries) { + const pn = parseInt(entry.target.dataset.pageNum); + if (!entry.isIntersecting && pageRendered[pn - 1]) { + // Page scrolled far away — release canvas memory + const canvas = entry.target.querySelector('canvas'); + if (canvas) { canvas.width = 0; canvas.height = 0; } + pageRendered[pn - 1] = false; + } + } + }, { root: viewer, rootMargin: '2000px 0px' }); + pageContainers.forEach(c => cleanupObserver.observe(c)); + viewer.addEventListener('scroll', onScroll, { passive: true }); // Restore position @@ -863,6 +909,13 @@ } catch(e) {} } + // Remove loading indicator after first render completes + loadingEl.textContent = _lang === 'zh' ? '正在渲染首屏…' : 'Rendering…'; + await renderPage(1); + loadingEl.style.transition = 'opacity 0.4s ease'; + loadingEl.style.opacity = '0'; + setTimeout(() => loadingEl.remove(), 500); + updatePageIndicator(); TOC.init(); } @@ -2043,6 +2096,27 @@ document.getElementById('mobile-overlay').classList.remove('visible'); }); + // Touch swipe to close on mobile + if ('ontouchstart' in window) { + let touchStartX = 0; + document.addEventListener('touchstart', e => { touchStartX = e.touches[0].clientX; }, { passive: true }); + document.addEventListener('touchend', e => { + if (window.innerWidth > 768) return; + const deltaX = e.changedTouches[0].clientX - touchStartX; + const sidebar = document.getElementById('sidebar'); + const panels = document.getElementById('panels-container'); + const overlay = document.getElementById('mobile-overlay'); + if (sidebar && sidebar.classList.contains('mobile-open') && deltaX < -50) { + sidebar.classList.remove('mobile-open'); + overlay.classList.remove('visible'); + } + if (panels && panels.classList.contains('mobile-open') && deltaX > 50) { + panels.classList.remove('mobile-open'); + overlay.classList.remove('visible'); + } + }, { passive: true }); + } + // Auto-resize textarea document.getElementById('ai-input').addEventListener('input', function() { this.style.height = 'auto'; diff --git a/templates/reader.html b/templates/reader.html index 94a9f84d..18d1884e 100644 --- a/templates/reader.html +++ b/templates/reader.html @@ -69,21 +69,13 @@ #notebook-panel::-webkit-scrollbar-thumb, #search-panel .sp-results::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 3px; } /* --- 3. Satellite Controls & Audio Floating Console --- */ - .toggle-btn { - position: fixed; width: 30px; height: 30px; - background: color-mix(in srgb, var(--surface) 95%, transparent); backdrop-filter: blur(12px); - border: 1.2px solid var(--outline); border-radius: 12px; - display: flex; align-items: center; justify-content: center; - cursor: pointer; z-index: 2000; color: var(--secondary); - box-shadow: 0 4px 16px rgba(0,0,0,0.06); - transition: all 0.3s var(--transition), border-color 0.2s; - } + .toggle-group { position: fixed; top: 20px; left: 24px; z-index: 2000; display: flex; gap: 8px; transition: left 0.3s var(--transition); } + #sidebar:not(.collapsed) ~ .toggle-group { left: calc(var(--sidebar-width) + 24px); } + .toggle-btn { width: 34px; height: 34px; border-radius: 12px; background: color-mix(in srgb, var(--surface) 90%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1.2px solid var(--outline); display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--secondary); transition: all 0.3s var(--transition), border-color 0.2s; box-shadow: 0 4px 16px rgba(0,0,0,0.06); fill: none; stroke: currentColor; stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round; } .toggle-btn:hover { border-color: var(--primary); color: var(--primary); transform: translateY(-1px); } - .toggle-btn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round; } - - /* Top Satellites */ - #toggle-right { top: 24px; right: calc(var(--panels-width) + 24px); } - #panels-container.collapsed ~ #toggle-right { right: 24px; } + .toggle-btn svg { width: 18px; height: 18px; } + #toggle-right { position: fixed; top: 20px; right: 24px; z-index: 2000; transition: transform 0.3s ease, right 0.3s var(--transition); } + #panels-container:not(.collapsed) ~ #toggle-right { right: calc(var(--panels-width) + 24px); } /* Left button group: menu + sub-buttons */ .toggle-group { @@ -1171,26 +1163,26 @@ @media (max-width: 768px) { /* Sidebar: off-screen left drawer */ #sidebar { - position: fixed; top: 0; left: 0; width: 280px !important; + position: fixed; top: 0; left: 0; width: 85vw !important; max-width: 320px; height: 100vh; height: 100dvh; z-index: 4000; background: var(--surface); border-right: 1px solid var(--outline); transform: translateX(-100%); transition: transform 0.3s var(--transition); padding-top: 60px; } #sidebar.mobile-open { transform: translateX(0); box-shadow: 4px 0 24px rgba(0,0,0,0.15); } - #sidebar.collapsed { width: 280px !important; transform: translateX(-100%); } + #sidebar.collapsed { width: 85vw !important; max-width: 320px; transform: translateX(-100%); } #sidebar.collapsed.mobile-open { transform: translateX(0); } /* Panels: off-screen right drawer */ #panels-container { position: fixed; top: 0; right: 0; z-index: 4000; - width: 100vw !important; max-width: 400px; + width: 85vw !important; max-width: 400px; height: 100vh; height: 100dvh; background: var(--surface); border-left: 1px solid var(--outline); transform: translateX(100%); transition: transform 0.3s var(--transition); } #panels-container.mobile-open { transform: translateX(0); box-shadow: -4px 0 24px rgba(0,0,0,0.15); } - #panels-container.collapsed { width: 100vw !important; max-width: 400px; transform: translateX(100%); } + #panels-container.collapsed { width: 85vw !important; max-width: 400px; transform: translateX(100%); } #panels-container.collapsed.mobile-open { transform: translateX(0); } /* Overlay backdrop */ @@ -1371,6 +1363,15 @@
+
@@ -4663,6 +4664,26 @@ _closeAllPanels(); _overlay.classList.remove('visible'); }); + + // Touch swipe to close on mobile + if ('ontouchstart' in window) { + let touchStartX = 0; + document.addEventListener('touchstart', e => { touchStartX = e.touches[0].clientX; }, { passive: true }); + document.addEventListener('touchend', e => { + if (!_isMobile()) return; + const deltaX = e.changedTouches[0].clientX - touchStartX; + const sidebar = document.getElementById('sidebar'); + const panels = document.getElementById('panels-container'); + if (sidebar && sidebar.classList.contains('mobile-open') && deltaX < -50) { + sidebar.classList.remove('mobile-open'); + _overlay.classList.remove('visible'); + } + if (panels && panels.classList.contains('mobile-open') && deltaX > 50) { + panels.classList.remove('mobile-open'); + _overlay.classList.remove('visible'); + } + }, { passive: true }); + } window.toggleLeft = () => { if (_isMobile()) { const sb = document.getElementById('sidebar'); @@ -5219,12 +5240,8 @@ const savedScroll = sessionStorage.getItem('sidebar_scroll'); if (savedScroll) document.getElementById('sidebar').scrollTop = parseInt(savedScroll); - // Restore panel collapsed states - if (sessionStorage.getItem('sidebar_collapsed') === '1') { - document.getElementById('sidebar').classList.add('collapsed'); - } + // Restore panel collapsed states (toggle-right rotated state) if (sessionStorage.getItem('panels_collapsed') === '1') { - document.getElementById('panels-container').classList.add('collapsed'); document.getElementById('toggle-right').classList.add('rotated'); } function _getParas() { From 8ca9eb8114aae7a11b79471665dd6f23920caef7 Mon Sep 17 00:00:00 2001 From: Haining Date: Thu, 26 Mar 2026 16:13:19 +0800 Subject: [PATCH 3/3] =?UTF-8?q?Docs:=20=E5=AE=8C=E5=96=84=E7=9F=A5?= =?UTF-8?q?=E7=99=BD=E4=B8=8E=20Marginia=20=E7=9A=84=E5=8F=8C=E7=94=9F?= =?UTF-8?q?=E5=8C=96=E8=BA=AB=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 58 +++++++++++++++++++++++------------------------ README.es.md | 56 ++++++++++++++++++++++----------------------- README.fr.md | 56 ++++++++++++++++++++++----------------------- README.it.md | 56 ++++++++++++++++++++++----------------------- README.ja.md | 58 +++++++++++++++++++++++------------------------ README.ko.md | 56 ++++++++++++++++++++++----------------------- README.md | 38 +++++++++++++++---------------- README.zh-Hant.md | 52 ++++++++++++++++++++---------------------- 8 files changed, 207 insertions(+), 223 deletions(-) diff --git a/README.en.md b/README.en.md index ec8ca8a6..7d114891 100644 --- a/README.en.md +++ b/README.en.md @@ -4,77 +4,75 @@ English | [简体中文](README.md) | [繁體中文](README.zh-Hant.md) | [日 > "When technology is democratized by AI, aesthetics and human-centric design become the ultimate differentiators." -Inspired by Andrej Karpathy's [minimalist reader prototype](https://x.com/karpathy/status/1990577951671509438), Smoothie Reader is a curated, AI-augmented environment designed for deep reading. As a contemporary art curator based in Shanghai, I've evolved the original "copy-paste to LLM" workflow into a fluid, seamless dialogue between the reader, the text, and the machine. +Inspired by Andrej Karpathy's [minimalist reader prototype](https://x.com/karpathy/status/1990577951671509438), Smoothie Reader is a locally deployed AI-powered e-book reader with built-in word lookup, AI translation & chat, TTS reading, and highlight notes — designed for deep reading. -🏛️ **The Inaugural Exhibit: Meditations**. To honor the spirit of focused contemplation, this repository comes pre-loaded with "Meditations" by Marcus Aurelius (via Project Gutenberg). +📖 **Built-in sample book**: The repository includes "Meditations" by Marcus Aurelius (via Project Gutenberg), ready to explore after cloning.
Library
Your personal library at a glance
-## ✨ The Curatorial Vision - -While most readers only display text, Smoothie Reader treats every page as an exhibit, transforming reading into a holistic intellectual experience: +## ✨ Key Features - 🔍 **Intuitive Discovery**: Highlight any text to reveal the action bar. Built-in support for **ECDICT** (English) and Chinese offline dictionaries. -- 🤖 **The Augmented Dialogue**: - - **Narrative Translation**: High-quality, contextual translations elegantly embedded within the text. - - **The Digital Companion**: A sidebar AI assistant supporting streaming dialogue and multi-turn memory for deep intellectual engagement. - - **Global Connectivity**: Seamlessly integrated with 20+ providers including Gemini, OpenAI, Claude, and DeepSeek. +- 🤖 **AI-Powered Reading**: + - **Inline Translation**: High-quality, contextual translations elegantly embedded below the original text. + - **AI Companion**: A sidebar AI assistant supporting streaming dialogue and multi-turn memory for deep engagement. + - **Broad Compatibility**: Built-in support for OpenAI, Anthropic, Gemini, DeepSeek, Grok, Alibaba Cloud Bailian, Volcengine, Tencent Hunyuan, MiniMax, Moonshot, SiliconFlow, Cerebras, SambaNova, Groq, Mistral, DeepInfra, Together AI, OpenRouter, Zhipu AI, and ModelScope — 20 AI providers in total, plus custom OpenAI-compatible endpoints.
AI Tools
Selection toolbar · Inline translation · AI companion sidebar
-- 🔊 **Sonic Contemplation (TTS)**: Powered by Edge-TTS, offering 90+ voices to breathe life into the written word. -- ✏️ **The Archive**: 5-color aesthetic highlighting, inline annotations, and persistent bookmarks—all preserved in your **local vault**. +- 🔊 **TTS Reading**: Powered by Edge-TTS with multiple high-quality Chinese and English voices. +- ✏️ **Highlights & Notes**: 5-color highlighting, inline annotations, and bookmarks — all stored in your browser's **localStorage**.
Reading Layout
Three-column reading: TOC navigation · Immersive text · Multi-color highlights
-- 🎨 **Minimalist Aesthetics**: 6 curated themes and a flexible 3-column layout, designed for focus and clarity across all devices. +- 🎨 **Minimalist Aesthetics**: 6 curated themes and a flexible 3-column layout (TOC / Content / AI), optimized for all devices.
Settings
Themes, typography, dictionaries, AI models — all in one panel
-## 🚀 The Installation +## 🚀 Quick Start -This project strictly utilizes [uv](https://docs.astral.sh/uv/) for environment orchestration, ensuring a seamless and isolated performance. +This project uses [uv](https://docs.astral.sh/uv/) to manage the Python environment and dependencies. -### 1. Preparation -Ensure Python 3.9+ is available. Install the `uv` orchestrator: +### 1. Install uv +Ensure Python 3.10+ is available, then install `uv`: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. Opening the Exhibit +### 2. Import a Book & Launch ```bash -# Import your first piece (e.g., Dracula) -uv run reader3.py dracula.epub +# Import an EPUB e-book +uv run reader3.py your_book.epub -# Launch the environment +# Start the server uv run server.py ``` -Access the reader at: 👉 **http://localhost:8123** +Open your browser at: 👉 **http://localhost:8123** -### 3. Activating the AI -Enter the reading interface, navigate to **Settings**, and configure your **AI Providers**. Connect your essence to the machine (e.g., [get a free Gemini Key](https://aistudio.google.com/apikey)). +### 3. Configure AI +Enter the reading interface, click **Settings** in the top-left corner, and configure your **AI Provider** and API Key (e.g., [get a free Gemini Key](https://aistudio.google.com/apikey)). > [!TIP] -> **🚀 The Secret Passage**: Anywhere on the page, perform the ritual: **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code) to unveil the **Advanced AI Routing Panel** for multi-model orchestration. +> **🚀 Easter Egg**: Anywhere on the page, enter **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code) to unlock the hidden **Advanced AI Routing Panel**. -## 🛡️ Sovereignty & Privacy -- **Local Sovereignty**: No data leaves your sanctuary unless you explicitly invoke the AI or TTS. -- **Accountless**: Your archives remain exclusively in your browser's `localStorage`. +## 🛡️ Privacy +- **Local First**: No data leaves your device unless you explicitly trigger an AI or TTS request. +- **No Account Required**: Your data is stored only in your browser's `localStorage`. -## 📚 Further Inquiry -For detailed orchestration instructions, consult the [Curator's Guide](docs/GUIDE.md). +## 📚 User Guide +For detailed configuration (offline dictionaries, multi-device access, port settings), see the [User Guide](docs/GUIDE.md). -## 📄 Provenance +## 📄 License [MIT License](LICENSE) diff --git a/README.es.md b/README.es.md index fd476208..a24cd5d6 100644 --- a/README.es.md +++ b/README.es.md @@ -4,77 +4,75 @@ > "Cuando la tecnología es democratizada por la IA, la estética y el diseño centrado en el ser humano se convierten en los diferenciadores definitivos." -Inspirado por el [prototipo de lector minimalista](https://x.com/karpathy/status/1990577951671509438) de Andrej Karpathy, Smoothie Reader es un entorno curado y aumentado por IA diseñado para una lectura profunda. Como curador de arte contemporáneo establecido en Shanghái, he evolucionado el flujo de trabajo original de "copiar y pegar al LLM" hacia un diálogo fluido y sin interrupciones entre el lector, el texto y la máquina. +Inspirado por el [prototipo de lector minimalista](https://x.com/karpathy/status/1990577951671509438) de Andrej Karpathy, Smoothie Reader es un lector de libros electrónicos con IA que se ejecuta localmente. Integra búsqueda de palabras, traducción y diálogo con IA, lectura TTS y notas de resaltado, diseñado para una lectura profunda. -🏛️ **La Exhibición Inaugural: Meditaciones**. Para honrar el espíritu de contemplación enfocada, este repositorio viene precargado con "Meditaciones" de Marco Aurelio (vía Project Gutenberg). +📖 **Libro de ejemplo incluido**: El repositorio incluye "Meditaciones" de Marco Aurelio (vía Project Gutenberg), listo para explorar tras clonar.
Library
Tu biblioteca personal de un vistazo
-## ✨ La Visión Curatorial - -Mientras que la mayoría de los lectores solo muestran texto, Smoothie Reader trata cada página como una exhibición, transformando la lectura en una experiencia intelectual holística: +## ✨ Características Principales - 🔍 **Descubrimiento Intuitivo**: Resalte cualquier texto para revelar la barra de acciones. Soporte integrado para **ECDICT** (Inglés) y diccionarios chinos offline. -- 🤖 **El Diálogo Aumentado**: - - **Traducción Narrativa**: Traducciones contextuales de alta calidad elegantemente incrustadas dentro del texto. - - **El Compañero Digital**: Un asistente de IA en la barra lateral que admite diálogos en streaming y memoria de múltiples turnos para un compromiso intelectual profundo. - - **Conectividad Global**: Integrado perfectamente con más de 20 proveedores, incluidos Gemini, OpenAI, Claude y DeepSeek. +- 🤖 **Lectura Potenciada por IA**: + - **Traducción en Línea**: Traducciones contextuales de alta calidad elegantemente incrustadas bajo el texto original. + - **Compañero IA**: Un asistente de IA en la barra lateral que admite diálogos en streaming y memoria de múltiples turnos. + - **Amplia Compatibilidad**: Soporte integrado para OpenAI, Anthropic, Gemini, DeepSeek, Grok, Alibaba Cloud Bailian, Volcengine, Tencent Hunyuan, MiniMax, Moonshot, SiliconFlow, Cerebras, SambaNova, Groq, Mistral, DeepInfra, Together AI, OpenRouter, Zhipu AI y ModelScope — 20 proveedores de IA en total, más endpoints personalizados compatibles con OpenAI.
AI Tools
Barra de selección · Traducción en línea · Panel lateral del compañero IA
-- 🔊 **Contemplación Sonora (TTS)**: Potenciado por Edge-TTS, ofreciendo más de 90 voces para dar vida a la palabra escrita. -- ✏️ **El Archivo**: Resaltado estético de 5 colores, anotaciones en línea y marcadores persistentes—todo preservado en su **bóveda local**. +- 🔊 **Lectura TTS**: Potenciado por Edge-TTS con múltiples voces de alta calidad en chino e inglés. +- ✏️ **Resaltados y Notas**: Resaltado de 5 colores, anotaciones en línea y marcadores — todo almacenado en el **localStorage** de tu navegador.
Reading Layout
Lectura en 3 columnas: navegación ToC · Texto inmersivo · Resaltados multicolor
-- 🎨 **Estética Minimalista**: 6 temas curados y un diseño flexible de 3 columnas, diseñado para el enfoque y la claridad en todos los dispositivos. +- 🎨 **Estética Minimalista**: 6 temas curados y un diseño flexible de 3 columnas (ToC/Contenido/IA), optimizado para todos los dispositivos.
Settings
Temas, tipografía, diccionarios, modelos IA — configuración todo en uno
-## 🚀 La Instalación +## 🚀 Inicio Rápido -Este proyecto utiliza estrictamente [uv](https://docs.astral.sh/uv/) para la orquestación del entorno, asegurando un rendimiento fluido y aislado. +Este proyecto utiliza [uv](https://docs.astral.sh/uv/) para gestionar el entorno Python y las dependencias. -### 1. Preparación -Asegúrese de tener Python 3.9+ disponible. Instale el orquestador `uv`: +### 1. Instalar uv +Asegúrese de tener Python 3.10+ disponible. Instale `uv`: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. Apertura de la Exhibición +### 2. Importar un Libro e Iniciar ```bash -# Importe su primera pieza (ej., Dracula) -uv run reader3.py dracula.epub +# Importar un libro electrónico EPUB +uv run reader3.py your_book.epub -# Inicie el entorno +# Iniciar el servidor uv run server.py ``` Acceda al lector en: 👉 **http://localhost:8123** -### 3. Activación de la IA -Ingrese a la interfaz de lectura, navegue a **Configuración (Settings)** y configure sus **Proveedores de IA**. Conecte su esencia a la máquina (ej., [obtenga una Gemini Key gratis](https://aistudio.google.com/apikey)). +### 3. Configurar IA +Entre en la interfaz de lectura, haga clic en **Configuración (Settings)** en la esquina superior izquierda y configure su **Proveedor de IA** y clave API (ej., [obtenga una Gemini Key gratis](https://aistudio.google.com/apikey)). > [!TIP] -> **🚀 El Pasaje Secreto**: En cualquier lugar de la página, realice el ritual: **`↑ ↑ ↓ ↓ ← → ← → B A`** (Código Konami) para desvelar el **Panel Avanzado de Enrutamiento de IA** para la orquestación multimodelo. +> **🚀 Huevo de Pascua**: En cualquier lugar de la página, ingrese **`↑ ↑ ↓ ↓ ← → ← → B A`** (Código Konami) para desbloquear el **Panel Avanzado de Enrutamiento de IA** oculto. -## 🛡️ Soberanía y Privacidad -- **Soberanía Local**: Ningún dato sale de su santuario a menos que invoque explícitamente la IA o el TTS. -- **Sin Cuenta**: Sus archivos permanecen exclusivamente en el `localStorage` de su navegador. +## 🛡️ Privacidad +- **Local Primero**: Ningún dato sale de su dispositivo a menos que active explícitamente una solicitud de IA o TTS. +- **Sin Cuenta**: Sus datos se almacenan solo en el `localStorage` de su navegador. -## 📚 Consulta Adicional -Para instrucciones detalladas de orquestación, consulte la [Guía del Curador](docs/GUIDE.md). +## 📚 Guía de Usuario +Para configuraciones detalladas (diccionarios offline, acceso multidispositivo, configuración de puertos), consulte la [Guía de Usuario](docs/GUIDE.md). -## 📄 Procedencia +## 📄 Licencia [MIT License](LICENSE) diff --git a/README.fr.md b/README.fr.md index 51838ecc..e30d5e0b 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,77 +4,75 @@ > "Lorsque la technologie est démocratisée par l'IA, l'esthétique et le design centré sur l'humain deviennent les ultimes différenciateurs." -Inspiré par le [prototype de lecteur minimaliste](https://x.com/karpathy/status/1990577951671509438) d'Andrej Karpathy, Smoothie Reader est un environnement commissarié et augmenté par l'IA, conçu pour une lecture profonde. En tant que commissaire d'exposition d'art contemporain basé à Shanghai, j'ai fait évoluer le flux de travail original « copier-coller vers le LLM » en un dialogue fluide et transparent entre le lecteur, le texte et la machine. +Inspiré par le [prototype de lecteur minimaliste](https://x.com/karpathy/status/1990577951671509438) d'Andrej Karpathy, Smoothie Reader est un lecteur de livres numériques alimenté par l'IA qui fonctionne localement. Il intègre la recherche de mots, la traduction et le dialogue IA, la lecture TTS et les notes surlignées, conçu pour une lecture approfondie. -🏛️ **L'Exposition Inaugurale : Méditations**. Pour honorer l'esprit de contemplation focalisée, ce dépôt est pré-chargé avec les « Méditations » de Marc Aurèle (via Project Gutenberg). +📖 **Livre exemple inclus** : Le dépôt contient les « Méditations » de Marc Aurèle (via Project Gutenberg), prêt à explorer après le clonage.
Library
Votre bibliothèque personnelle en un coup d'œil
-## ✨ La Vision du Commissaire - -Alors que la plupart des lecteurs n'affichent que du texte, Smoothie Reader traite chaque page comme une pièce d'exposition, transformant la lecture en une expérience intellectuelle holistique : +## ✨ Fonctionnalités Principales - 🔍 **Découverte Intuitive** : Surlignez n'importe quel texte pour révéler la barre d'action. Prise en charge intégrée d'**ECDICT** (Anglais) et de dictionnaires chinois hors ligne. -- 🤖 **Le Dialogue Augmenté** : - - **Traduction Narrative** : Traductions contextuelles de haute qualité élégamment intégrées au texte. - - **Le Compagnon Numérique** : Un assistant IA en barre latérale prenant en charge le dialogue en streaming et la mémoire multi-tours pour un engagement intellectuel profond. - - **Connectivité Mondiale** : Intégration transparente avec plus de 20 fournisseurs, dont Gemini, OpenAI, Claude et DeepSeek. +- 🤖 **Lecture Augmentée par l'IA** : + - **Traduction en Ligne** : Traductions contextuelles de haute qualité élégamment intégrées sous le texte original. + - **Compagnon IA** : Un assistant IA en barre latérale prenant en charge le dialogue en streaming et la mémoire multi-tours. + - **Large Compatibilité** : Prise en charge intégrée d'OpenAI, Anthropic, Gemini, DeepSeek, Grok, Alibaba Cloud Bailian, Volcengine, Tencent Hunyuan, MiniMax, Moonshot, SiliconFlow, Cerebras, SambaNova, Groq, Mistral, DeepInfra, Together AI, OpenRouter, Zhipu AI et ModelScope — 20 fournisseurs d'IA au total, plus des endpoints personnalisés compatibles OpenAI.
AI Tools
Barre de sélection · Traduction en ligne · Panneau latéral du compagnon IA
-- 🔊 **Contemplation Sonore (TTS)** : Propulsé par Edge-TTS, offrant plus de 90 voix pour insuffler la vie au mot écrit. -- ✏️ **L'Archive** : Surlignage esthétique en 5 couleurs, annotations en ligne et signets persistants—le tout préservé dans votre **coffre-fort local**. +- 🔊 **Lecture TTS** : Propulsé par Edge-TTS avec plusieurs voix de haute qualité en chinois et en anglais. +- ✏️ **Surlignages et Notes** : Surlignage en 5 couleurs, annotations en ligne et signets — le tout stocké dans le **localStorage** de votre navigateur.
Reading Layout
Lecture en 3 colonnes : navigation ToC · Texte immersif · Surlignages multicolores
-- 🎨 **Esthétique Minimaliste** : 6 thèmes sélectionnés et une mise en page flexible à 3 colonnes, conçue pour la concentration et la clarté sur tous les appareils. +- 🎨 **Esthétique Minimaliste** : 6 thèmes sélectionnés et une mise en page flexible à 3 colonnes (ToC/Contenu/IA), optimisée pour tous les appareils.
Settings
Thèmes, typographie, dictionnaires, modèles IA — configuration tout-en-un
-## 🚀 L'Installation +## 🚀 Démarrage Rapide -Ce projet utilise strictement [uv](https://docs.astral.sh/uv/) pour l'orchestration de l'environnement, garantissant une performance fluide et isolée. +Ce projet utilise [uv](https://docs.astral.sh/uv/) pour gérer l'environnement Python et les dépendances. -### 1. Préparation -Assurez-vous que Python 3.9+ est disponible. Installez l'orchestrateur `uv` : +### 1. Installer uv +Assurez-vous que Python 3.10+ est disponible. Installez `uv` : ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. Ouverture de l'Exposition +### 2. Importer un Livre et Lancer ```bash -# Importez votre première pièce (ex. Dracula) -uv run reader3.py dracula.epub +# Importer un livre EPUB +uv run reader3.py your_book.epub -# Lancez l'environnement +# Démarrer le serveur uv run server.py ``` Accédez au lecteur sur : 👉 **http://localhost:8123** -### 3. Activation de l'IA -Entrez dans l'interface de lecture, naviguez vers **Paramètres (Settings)** et configurez vos **Fournisseurs d'IA**. Connectez votre essence à la machine (ex. [obtenez une clé Gemini gratuite](https://aistudio.google.com/apikey)). +### 3. Configurer l'IA +Entrez dans l'interface de lecture, cliquez sur **Paramètres (Settings)** en haut à gauche et configurez votre **Fournisseur d'IA** et clé API (ex. [obtenez une clé Gemini gratuite](https://aistudio.google.com/apikey)). > [!TIP] -> **🚀 Le Passage Secret** : N'importe où sur la page, effectuez le rituel : **`↑ ↑ ↓ ↓ ← → ← → B A`** (Code Konami) pour dévoiler le **Panneau de Routage IA Avancé** pour l'orchestration multi-modèles. +> **🚀 Œuf de Pâques** : N'importe où sur la page, entrez **`↑ ↑ ↓ ↓ ← → ← → B A`** (Code Konami) pour déverrouiller le **Panneau de Routage IA Avancé** caché. -## 🛡️ Souveraineté et Confidentialité -- **Souveraineté Locale** : Aucune donnée ne quitte votre sanctuaire, sauf si vous invoquez explicitement l'IA ou le TTS. -- **Sans Compte** : Vos archives restent exclusivement dans le `localStorage` de votre navigateur. +## 🛡️ Confidentialité +- **Local d'Abord** : Aucune donnée ne quitte votre appareil sauf si vous déclenchez explicitement une requête IA ou TTS. +- **Sans Compte** : Vos données restent exclusivement dans le `localStorage` de votre navigateur. -## 📚 Enquête Complémentaire -Pour des instructions d'orchestration détaillées, consultez le [Guide du Commissaire](docs/GUIDE.md). +## 📚 Guide Utilisateur +Pour des configurations détaillées (dictionnaires hors ligne, accès multi-appareils, paramètres de port), consultez le [Guide Utilisateur](docs/GUIDE.md). -## 📄 Provenance +## 📄 Licence [MIT License](LICENSE) diff --git a/README.it.md b/README.it.md index d342d67e..08d1f904 100644 --- a/README.it.md +++ b/README.it.md @@ -4,77 +4,75 @@ > "Quando la tecnologia viene democratizzata dall'IA, l'estetica e il design incentrato sull'uomo diventano i differenziatori finali." -Ispirato al [prototipo di lettore minimalista](https://x.com/karpathy/status/1990577951671509438) di Andrej Karpathy, Smoothie Reader è un ambiente curato e aumentato dall'IA progettato per la lettura profonda. Come curatore d'arte contemporanea con base a Shanghai, ho evoluto il flusso di lavoro originale "copia-incolla verso l'LLM" in un dialogo fluido e senza soluzione di continuità tra il lettore, il testo e la macchina. +Ispirato al [prototipo di lettore minimalista](https://x.com/karpathy/status/1990577951671509438) di Andrej Karpathy, Smoothie Reader è un lettore di e-book alimentato dall'IA che funziona localmente. Integra ricerca di parole, traduzione e dialogo IA, lettura TTS e note evidenziate, progettato per la lettura profonda. -🏛️ **La Mostra Inaugurale: Meditazioni**. Per onorare lo spirito di contemplazione focalizzata, questo repository include pre-caricato "Meditazioni" di Marco Aurelio (via Project Gutenberg). +📖 **Libro di esempio incluso**: Il repository include "Meditazioni" di Marco Aurelio (via Project Gutenberg), pronto da esplorare dopo il clone.
Library
La tua biblioteca personale a colpo d'occhio
-## ✨ La Visione del Curatore - -Mentre la maggior parte dei lettori visualizza solo il testo, Smoothie Reader tratta ogni pagina come un'esposizione, trasformando la lettura in un'esperienza intellettuale olistica: +## ✨ Funzionalità Principali - 🔍 **Scoperta Intuitiva**: Evidenzia qualsiasi testo per rivelare la barra delle azioni. Supporto integrato per **ECDICT** (Inglese) e dizionari cinesi offline. -- 🤖 **Il Dialogo Aumentato**: - - **Traduzione Narrativa**: Traduzioni contestuali di alta qualità elegantemente incorporate nel testo. - - **Il Compagno Digitale**: Un assistente IA nella barra laterale che supporta il dialogo in streaming e la memoria multi-turno per un profondo coinvolgimento intellettuale. - - **Connettività Globale**: Perfettamente integrato con oltre 20 provider tra cui Gemini, OpenAI, Claude e DeepSeek. +- 🤖 **Lettura Potenziata dall'IA**: + - **Traduzione in Linea**: Traduzioni contestuali di alta qualità elegantemente incorporate sotto il testo originale. + - **Compagno IA**: Un assistente IA nella barra laterale che supporta il dialogo in streaming e la memoria multi-turno. + - **Ampia Compatibilità**: Supporto integrato per OpenAI, Anthropic, Gemini, DeepSeek, Grok, Alibaba Cloud Bailian, Volcengine, Tencent Hunyuan, MiniMax, Moonshot, SiliconFlow, Cerebras, SambaNova, Groq, Mistral, DeepInfra, Together AI, OpenRouter, Zhipu AI e ModelScope — 20 provider IA in totale, più endpoint personalizzati compatibili con OpenAI.
AI Tools
Barra di selezione · Traduzione in linea · Pannello laterale del compagno IA
-- 🔊 **Contemplazione Sonora (TTS)**: Alimentato da Edge-TTS, offre oltre 90 voci per dare vita alla parola scritta. -- ✏️ **L'Archivio**: Evidenziazione estetica in 5 colori, annotazioni in linea e segnalibri persistenti—tutto conservato nel tuo **caveau locale**. +- 🔊 **Lettura TTS**: Alimentato da Edge-TTS con molteplici voci di alta qualità in cinese e inglese. +- ✏️ **Evidenziazioni e Note**: Evidenziazione in 5 colori, annotazioni in linea e segnalibri — tutto conservato nel **localStorage** del tuo browser.
Reading Layout
Lettura a 3 colonne: navigazione ToC · Testo immersivo · Evidenziazioni multicolore
-- 🎨 **Estetica Minimalista**: 6 temi curati e un layout flessibile a 3 colonne, progettato per la concentrazione e la chiarezza su tutti i dispositivi. +- 🎨 **Estetica Minimalista**: 6 temi curati e un layout flessibile a 3 colonne (ToC/Contenuto/IA), ottimizzato per tutti i dispositivi.
Settings
Temi, tipografia, dizionari, modelli IA — configurazione tutto in uno
-## 🚀 L'Installazione +## 🚀 Avvio Rapido -Questo progetto utilizza rigorosamente [uv](https://docs.astral.sh/uv/) per l'orchestrazione dell'ambiente, garantendo prestazioni fluide e isolate. +Questo progetto utilizza [uv](https://docs.astral.sh/uv/) per gestire l'ambiente Python e le dipendenze. -### 1. Preparazione -Assicurati che Python 3.9+ sia disponibile. Installa l'orchestratore `uv`: +### 1. Installare uv +Assicurati che Python 3.10+ sia disponibile. Installa `uv`: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. Apertura della Mostra +### 2. Importare un Libro e Avviare ```bash -# Importa il tuo primo pezzo (es. Dracula) -uv run reader3.py dracula.epub +# Importa un e-book EPUB +uv run reader3.py your_book.epub -# Avvia l'ambiente +# Avvia il server uv run server.py ``` Accedi al lettore su: 👉 **http://localhost:8123** -### 3. Attivazione dell'IA -Entra nell'interfaccia di lettura, vai su **Impostazioni (Settings)** e configura i tuoi **AI Provider**. Connetti la tua essenza alla macchina (es. [ottieni una Gemini Key gratuita](https://aistudio.google.com/apikey)). +### 3. Configurare l'IA +Entra nell'interfaccia di lettura, clicca su **Impostazioni (Settings)** in alto a sinistra e configura il tuo **Provider IA** e chiave API (es. [ottieni una Gemini Key gratuita](https://aistudio.google.com/apikey)). > [!TIP] -> **🚀 Il Passaggio Segreto**: In qualsiasi punto della pagina, esegui il rito: **`↑ ↑ ↓ ↓ ← → ← → B A`** (Codice Konami) per svelare il **Pannello Avanzato di Routing IA** per l'orchestrazione multi-modello. +> **🚀 Easter Egg**: In qualsiasi punto della pagina, inserisci **`↑ ↑ ↓ ↓ ← → ← → B A`** (Codice Konami) per sbloccare il **Pannello Avanzato di Routing IA** nascosto. -## 🛡️ Sovranità e Privacy -- **Sovranità Locale**: Nessun dato lascia il tuo santuario a meno che tu non invochi esplicitamente l'IA o il TTS. -- **Senza Account**: I tuoi archivi rimangono esclusivamente nel `localStorage` del tuo browser. +## 🛡️ Privacy +- **Locale Prima di Tutto**: Nessun dato lascia il tuo dispositivo a meno che tu non attivi esplicitamente una richiesta IA o TTS. +- **Senza Account**: I tuoi dati rimangono esclusivamente nel `localStorage` del tuo browser. -## 📚 Ulteriori Informazioni -Per istruzioni dettagliate sull'orchestrazione, consulta la [Guida del Curatore](docs/GUIDE.md). +## 📚 Guida Utente +Per configurazioni dettagliate (dizionari offline, accesso multi-dispositivo, impostazioni porta), consulta la [Guida Utente](docs/GUIDE.md). -## 📄 Provenienza +## 📄 Licenza [MIT License](LICENSE) diff --git a/README.ja.md b/README.ja.md index c359842e..d816bdfa 100644 --- a/README.ja.md +++ b/README.ja.md @@ -4,77 +4,75 @@ > 「AIによって技術が民主化されるとき、美学と人間中心のデザインこそが究極の差別化要因となる。」 -Andrej Karpathyの[ミニマリスト・リーダー・プロトタイプ](https://x.com/karpathy/status/1990577951671509438)に触発されたSmoothie Readerは、深い読書のために設計された、AI拡張型のキュレーション環境です。上海を拠点とする現代アートキュレーターとして、私は従来の「LLMへのコピペ」というワークフローを、読者、テキスト、そしてマシンの間の流動的でシームレスな対話へと進化させました。 +Andrej Karpathyの[ミニマリスト・リーダー・プロトタイプ](https://x.com/karpathy/status/1990577951671509438)に触発されたSmoothie Readerは、ローカルで動作するAI搭載電子書籍リーダーです。ワンタップ辞書検索、AI翻訳・対話、TTS読み上げ、ハイライトメモなどの機能を統合し、深い読書のために設計されています。 -🏛️ **初回展示:自省録**。集中した沈思の精神に敬意を表し、本リポジトリにはマルクス・アウレリウスの『自省録』(Project Gutenberg提供)があらかじめロードされています。 +📖 **内蔵サンプル書籍**:リポジトリにはマルクス・アウレリウスの『自省録』(Project Gutenberg提供)が含まれており、クローン後すぐに体験できます。
Library
パーソナルライブラリを一望
-## ✨ キュレーションのビジョン +## ✨ 主な機能 -多くのリーダーがテキストを表示するだけであるのに対し、Smoothie Readerはすべてのページを一つの展示物として扱い、読書を総合的な知的体験へと変容させます。 - -- 🔍 **直感的な発見**:テキストを選択するだけでアクションバーが表示されます。**ECDICT (英語)** および中国語のオフライン辞書を内蔵。 -- 🤖 **拡張された対話**: - - **ナラティブ翻訳**:文脈に応じた高品質な翻訳を、原文の下にエレガントに埋め込みます。 - - **デジタル・コンパニオン**:ストリーミング対話と多重文脈の記憶をサポートするサイドバーAIアシスタントが、深い知の探求を支えます。 - - **グローバルな接続性**:Gemini, OpenAI, Claude, DeepSeekなど、20以上の主要プロバイダーとシームレスに統合。 +- 🔍 **直感的な検索**:テキストを選択するだけでアクションバーが表示されます。**ECDICT(英語)** および中国語のオフライン辞書を内蔵。 +- 🤖 **AI強化リーディング**: + - **インライン翻訳**:文脈に応じた高品質な翻訳を、原文の下にエレガントに埋め込みます。 + - **AIコンパニオン**:ストリーミング対話とマルチターン記憶をサポートするサイドバーAIアシスタント。 + - **幅広い互換性**:OpenAI、Anthropic、Gemini、DeepSeek、Grok、阿里雲百煉、火山引擎、騰訊混元、MiniMax、月之暗面、硅基流動、Cerebras、SambaNova、Groq、Mistral、DeepInfra、Together AI、OpenRouter、智譜AI、ModelScope の計20社のAIプロバイダーを内蔵し、任意のOpenAI互換エンドポイントのカスタム接続もサポート。
AI Tools
選択ツールバー · インライン翻訳 · AIコンパニオンサイドバー
-- 🔊 **音の瞑想 (TTS)**:Edge-TTSを搭載し、90種類以上の高品質な音声で書かれた言葉に命を吹き込みます。 -- ✏️ **アーカイブ**:5色の美的ハイライト、インライン注釈、永続的なブックマーク。データはすべて**ローカルの保管庫**に保存されます。 +- 🔊 **TTS読み上げ**:Edge-TTSを搭載し、中国語・英語の高品質な音声を複数提供。 +- ✏️ **ハイライト&メモ**:5色ハイライト、インライン注釈、ブックマーク。データはすべてブラウザの**localStorage**に保存されます。
Reading Layout
3カラム閲覧:目次ナビ · 没入型テキスト · マルチカラーハイライト
-- 🎨 **ミニマリズムの美学**:厳選された6つのテーマと柔軟な3カラムレイアウトにより、あらゆるデバイスで明晰な集中を。 +- 🎨 **ミニマルな美学**:厳選された6つのテーマと柔軟な3カラムレイアウト(目次/本文/AI)、あらゆるデバイスに対応。
Settings
テーマ、タイポグラフィ、辞書、AIモデル — ワンストップ設定
-## 🚀 インストール +## 🚀 クイックスタート -本プロジェクトは、高速かつ隔離された実行環境を保証するため、最新の [uv](https://docs.astral.sh/uv/) を環境構築ツールとして採用しています。 +本プロジェクトは [uv](https://docs.astral.sh/uv/) でPython環境と依存関係を管理しています。 -### 1. 準備 -Python 3.9以上がインストールされていることを確認してください。`uv` オーケストレーターをインストールします: +### 1. uvのインストール +Python 3.10以上がインストールされていることを確認し、`uv`をインストールします: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. 展示の開始 +### 2. 電子書籍のインポートと起動 ```bash -# 最初の作品をインポート (例: ドラキュラ) -uv run reader3.py dracula.epub +# EPUB電子書籍をインポート +uv run reader3.py your_book.epub -# 環境を起動 +# サーバーを起動 uv run server.py ``` ブラウザでアクセス:👉 **http://localhost:8123** -### 3. AIの起動 -読書インターフェースに入り、**設定 (Settings)** から **AIプロバイダー** を構成します。あなたの感性をマシンと接続してください(例:[Gemini Keyを無料で取得](https://aistudio.google.com/apikey))。 +### 3. AIの設定 +読書インターフェースに入り、左上の**設定 (Settings)** から**AIプロバイダー**とAPIキーを構成します(例:[Gemini Keyを無料で取得](https://aistudio.google.com/apikey))。 > [!TIP] -> **🚀 秘密の通路 (裏技)**:ページ上のどこでも、儀式 **`↑ ↑ ↓ ↓ ← → ← → B A`** (コナミコマンド) を実行すると、マルチモデル運用のための **高度なAIルーティングパネル** が現れます。 +> **🚀 イースターエッグ**:ページ上のどこでも **`↑ ↑ ↓ ↓ ← → ← → B A`**(コナミコマンド)を入力すると、隠された**高度なAIルーティングパネル**がアンロックされます。 -## 🛡️ 主権とプライバシー -- **ローカルの主権**:AIやTTSを明示的に呼び出さない限り、データがあなたの聖域を離れることはありません。 -- **アカウント不要**:アーカイブはブラウザの `localStorage` のみに保持されます。 +## 🛡️ プライバシー +- **ローカル優先**:AIやTTSを明示的にトリガーしない限り、データがデバイスを離れることはありません。 +- **アカウント不要**:データはブラウザの`localStorage`のみに保持されます。 -## 📚 さらなる探求 -詳細な設定手順については、[キュレーターズ・ガイド](docs/GUIDE.md) を参照してください。 +## 📚 ユーザーガイド +詳細な設定手順(オフライン辞書のダウンロード、マルチデバイスアクセス、ポート設定)については、[ユーザーガイド](docs/GUIDE.md)を参照してください。 -## 📄 来歴 +## 📄 ライセンス [MIT License](LICENSE) diff --git a/README.ko.md b/README.ko.md index d3335700..ff079c59 100644 --- a/README.ko.md +++ b/README.ko.md @@ -4,77 +4,75 @@ > "기술이 AI에 의해 민주화될 때, 미학과 인간 중심의 디자인이 궁극적인 차별점이 됩니다." -Andrej Karpathy의 [미니멀리스트 리더 프로토타입](https://x.com/karpathy/status/1990577951671509438)에서 영감을 받은 Smoothie Reader는 깊은 독서를 위해 설계된 AI 증강 큐레이션 환경입니다. 상하이를 기반으로 활동하는 현대 미술 큐레이터로서, 저는 기존의 "LLM에 복사-붙여넣기" 워크플로우를 독자, 텍스트, 그리고 기계 사이의 유동적이고 매끄러운 대화로 진화시켰습니다. +Andrej Karpathy의 [미니멀리스트 리더 프로토타입](https://x.com/karpathy/status/1990577951671509438)에서 영감을 받은 Smoothie Reader는 로컬에서 실행되는 AI 기반 전자책 리더기입니다. 원터치 사전 검색, AI 번역 및 대화, TTS 읽기, 하이라이트 노트 등의 기능을 통합하여 깊은 독서를 위해 설계되었습니다. -🏛️ **개막 전시: 명상록**. 집중된 명상의 정신을 기리기 위해, 이 저장소에는 마르쿠스 아우렐리우스의 "명상록"(Project Gutenberg 제공)이 사전 로드되어 있습니다. +📖 **내장 샘플 도서**: 저장소에는 마르쿠스 아우렐리우스의 "명상록" (Project Gutenberg 제공)이 포함되어 있어, 클론 후 바로 체험할 수 있습니다.
Library
개인 서재를 한눈에
-## ✨ 큐레이션의 비전 - -대부분의 독서기가 텍스트를 표시하는 데 그치는 반면, Smoothie Reader는 모든 페이지를 하나의 전시물로 대하며 독서를 종합적인 지적 경험으로 변화시킵니다. +## ✨ 핵심 기능 - 🔍 **직관적 탐색**: 텍스트를 선택하기만 하면 액션 바가 나타납니다. **ECDICT (영어)** 및 중국어 오프라인 사전 내장. -- 🤖 **증강된 대화**: - - **내러티브 번역**: 문맥을 반영한 고품질 번역이 원문 아래에 우아하게 삽입됩니다. - - **디지털 동반자**: 스트리밍 대화와 다중 문맥 기억을 지원하는 사이드바 AI 어시스턴트가 깊은 지적 몰입을 돕습니다. - - **글로벌 연결성**: Gemini, OpenAI, Claude, DeepSeek 등 20개 이상의 주요 제공업체와 원활하게 통합됩니다. +- 🤖 **AI 강화 독서**: + - **인라인 번역**: 문맥을 반영한 고품질 번역이 원문 아래에 우아하게 삽입됩니다. + - **AI 동반자**: 스트리밍 대화와 다중 턴 기억을 지원하는 사이드바 AI 어시스턴트. + - **폭넓은 호환성**: OpenAI, Anthropic, Gemini, DeepSeek, Grok, 阿里云百炼, 火山引擎, 腾讯混元, MiniMax, Moonshot, SiliconFlow, Cerebras, SambaNova, Groq, Mistral, DeepInfra, Together AI, OpenRouter, 智谱AI, ModelScope 총 20개 AI 서비스 제공업체를 내장하고, 임의의 OpenAI 호환 엔드포인트 커스텀 연결도 지원.
AI Tools
선택 도구 모음 · 인라인 번역 · AI 동반자 사이드바
-- 🔊 **소리 명상 (TTS)**: Edge-TTS를 통해 90개 이상의 목소리로 텍스트에 생명력을 불어넣습니다. -- ✏️ **아카이브**: 5가지 미학적 하이라이트, 인라인 주석, 영구 북마크. 모든 데이터는 **로컬 저장소**에 보관됩니다. +- 🔊 **TTS 읽기**: Edge-TTS 기반으로 중국어·영어 고품질 음성을 다수 제공. +- ✏️ **하이라이트 및 노트**: 5가지 색상 하이라이트, 인라인 주석, 북마크. 모든 데이터는 브라우저의 **localStorage**에 저장됩니다.
Reading Layout
3단 독서: 목차 탐색 · 몰입형 텍스트 · 멀티 컬러 하이라이트
-- 🎨 **미니멀리즘 미학**: 엄선된 6가지 테마와 유연한 3단 레이아웃으로 모든 기기에서 명료한 집중을 제공합니다. +- 🎨 **미니멀리즘 미학**: 엄선된 6가지 테마와 유연한 3단 레이아웃 (목차/본문/AI), 모든 기기에 최적화.
Settings
테마, 타이포그래피, 사전, AI 모델 — 올인원 설정
-## 🚀 설치 및 시작 +## 🚀 빠른 시작 -이 프로젝트는 최신 [uv](https://docs.astral.sh/uv/)를 환경 구축 도구로 사용하여 빠르고 격리된 실행 환경을 보장합니다. +이 프로젝트는 [uv](https://docs.astral.sh/uv/)를 사용하여 Python 환경과 의존성을 관리합니다. -### 1. 준비 -Python 3.9 이상이 필요합니다. `uv` 오케스트레이터를 설치하세요: +### 1. uv 설치 +Python 3.10 이상이 필요합니다. `uv`를 설치하세요: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. 전시 개시 +### 2. 전자책 가져오기 및 실행 ```bash -# 첫 번째 작품 가져오기 (예: 드라큘라) -uv run reader3.py dracula.epub +# EPUB 전자책 가져오기 +uv run reader3.py your_book.epub -# 환경 실행 +# 서버 시작 uv run server.py ``` 브라우저에서 접속: 👉 **http://localhost:8123** -### 3. AI 활성화 -독서 인터페이스에서 **설정 (Settings)**으로 이동하여 **AI 제공업체**를 구성하세요. 당신의 영감을 기계와 연결하세요 (예: [무료 Gemini 키 받기](https://aistudio.google.com/apikey)). +### 3. AI 설정 +독서 인터페이스에서 왼쪽 상단의 **설정 (Settings)**을 클릭하여 **AI 제공업체**와 API 키를 구성하세요 (예: [무료 Gemini 키 받기](https://aistudio.google.com/apikey)). > [!TIP] -> **🚀 비밀 통로 (치트키)**: 페이지 어디에서나 **`↑ ↑ ↓ ↓ ← → ← → B A`** (코나미 커맨드)를 입력하면 멀티 모델 운용을 위한 **고급 AI 라우팅 패널**이 나타납니다. +> **🚀 이스터 에그**: 페이지 어디에서나 **`↑ ↑ ↓ ↓ ← → ← → B A`** (코나미 커맨드)를 입력하면 숨겨진 **고급 AI 라우팅 패널**이 잠금 해제됩니다. -## 🛡️ 주권과 개인정보 보호 -- **로컬 주권**: AI나 TTS를 명시적으로 호출하지 않는 한 데이터가 당신의 공간을 떠나지 않습니다. -- **계정 불필요**: 아카이브는 브라우저의 `localStorage`에만 보관됩니다. +## 🛡️ 개인정보 보호 +- **로컬 우선**: AI나 TTS를 명시적으로 호출하지 않는 한 데이터가 기기를 떠나지 않습니다. +- **계정 불필요**: 데이터는 브라우저의 `localStorage`에만 보관됩니다. -## 📚 추가 문의 -상세한 구성 지침은 [큐레이터 가이드](docs/GUIDE.md)를 참조하세요. +## 📚 사용 가이드 +상세한 구성 지침 (오프라인 사전 다운로드, 다중 기기 접속, 포트 설정)은 [사용 가이드](docs/GUIDE.md)를 참조하세요. -## 📄 출처 +## 📄 라이선스 [MIT License](LICENSE) diff --git a/README.md b/README.md index 6808eebb..62e4fe0a 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,30 @@ > "当技术被 AI 民主化,审美与以人为本的设计便成了最终的护城河。" -灵感源自 Andrej Karpathy 的 [极简阅读器原型](https://x.com/karpathy/status/1990577951671509438),Smoothie Reader 是一个为深度阅读而生的、经 AI 增强的策展式环境。作为一名常驻上海的当代艺术策展人,我将最初的"复制粘贴给大模型"工作流,进化为一场读者、文本与机器之间流动的、无缝的对话。 +灵感源自 Andrej Karpathy 的 [极简阅读器原型](https://x.com/karpathy/status/1990577951671509438),Smoothie Reader 是一个本地部署的 AI 智能电子书阅读器,集成划词查词、AI 翻译与对话、TTS 朗读、高亮笔记等功能,为深度阅读而生。 -🏛️ **开幕展:沉思录**。为了向专注思考的精神致敬,本仓库预置了马可·奥勒留的《沉思录》(来源于 Project Gutenberg)。 +📖 **内置示例书籍**:仓库预置了马可·奥勒留的《沉思录》(来源于 Project Gutenberg),克隆后即可体验。
Library
个人藏书馆:封面墙一览无余
-## ✨ 策展愿景 - -大多数阅读器只负责显示文字,而 Smoothie Reader 将每一页视为一个展位,将阅读转化为一场完整的智力体验: +## ✨ 核心功能 - 🔍 **直觉探索**:选中文字秒开工具栏。内置支持 **ECDICT 英文词典** 和 **中文离线词典**。 - 🤖 **增强对话**: - **叙事级翻译**:一键获取高质量上下文翻译,优雅地内嵌于原文下方。 - **数字伴读**:侧边栏 AI 助手支持流式输出和多轮记忆,随时进行深度的智力碰撞。 - - **全球连接**:完美支持 Gemini, OpenAI, Claude, DeepSeek 等 20+ 主流模型。 + - **广泛兼容**:内置 OpenAI、Anthropic、Gemini、DeepSeek、Grok、阿里云百炼、火山引擎、腾讯混元、MiniMax、月之暗面、硅基流动、Cerebras、SambaNova、Groq、Mistral、DeepInfra、Together AI、OpenRouter、智谱AI、ModelScope 共 20 家 AI 服务商,并支持任意 OpenAI 兼容接口自定义接入。
AI Tools
划词工具栏 · 行内翻译 · AI 伴读侧栏
-- 🔊 **声音冥想 (TTS)**:基于 Edge-TTS,提供 90+ 种高质量语音,赋予文字真实的生命力。 -- ✏️ **档案系统**:5 色美学高亮、行内笔记、书签,所有数据全部存储在您的**本地保险库**。 +- 🔊 **TTS 朗读**:基于 Edge-TTS,提供中英文多种高质量语音,解放双眼。 +- ✏️ **高亮笔记**:5 色高亮、行内笔记、书签,所有数据存储在浏览器**本地 localStorage** 中。
Reading Layout
@@ -43,38 +41,38 @@ 主题、排版、词典、AI 模型 —— 一站式配置
-## 🚀 部署安装 +## 🚀 快速开始 -本项目强制使用现代化的 [uv](https://docs.astral.sh/uv/) 作为环境编排工具,确保极速与隔离的运行表现。 +本项目使用 [uv](https://docs.astral.sh/uv/) 管理 Python 环境和依赖。 -### 1. 准备工作 -确保系统已安装 Python 3.9+。安装 `uv` 编排器: +### 1. 安装 uv +确保系统已安装 Python 3.10+,然后安装 `uv`: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. 开启展览 +### 2. 导入电子书并启动 ```bash -# 导入您的第一件藏品 (以 Dracula 为例) -uv run reader3.py dracula.epub +# 导入一本 EPUB 电子书 +uv run reader3.py your_book.epub -# 启动环境 +# 启动服务 uv run server.py ``` 然后打开浏览器访问:👉 **http://localhost:8123** -### 3. 激活 AI -进入阅读页面,点击左上角 **设置 (Settings)**,配置您的 **AI 提供商**,将您的灵性与机器相连(例如,[免费获取 Gemini Key](https://aistudio.google.com/apikey))。 +### 3. 配置 AI +进入阅读页面,点击左上角 **设置 (Settings)**,配置您的 **AI 提供商**和 API Key(例如,[免费获取 Gemini Key](https://aistudio.google.com/apikey))。 > [!TIP] > **🚀 秘密通道 (秘籍)**:在页面任何地方,依次按下 **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code),即可解锁隐藏的 **高级 AI 路由管理面板**。 ## 🛡️ 主权与隐私 -- **本地主权**:除了您主动触发的 AI 或 TTS 请求外,没有任何数据会离开您的避风港。 +- **本地优先**:除了您主动触发的 AI 或 TTS 请求外,没有任何数据会离开您的设备。 - **无需账号**:您的档案仅保存在浏览器 `localStorage` 中。 ## 📚 进阶向导 -详细的配置说明(如离线词典下载、多设备访问、端口修改),请参阅 [策展人指南](docs/GUIDE.md)。 +详细的配置说明(如离线词典下载、多设备访问、端口修改),请参阅 [使用指南](docs/GUIDE.md)。 ## 📄 许可证 [MIT License](LICENSE) diff --git a/README.zh-Hant.md b/README.zh-Hant.md index e5e8a42c..a6acfe57 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -4,77 +4,75 @@ > 「當技術被 AI 民主化,審美與以人為本的設計便成了最終的護城河。」 -靈感源自 Andrej Karpathy 的 [極簡閱讀器原型](https://x.com/karpathy/status/1990577951671509438),Smoothie Reader 是一個為深度閱讀而生的、經 AI 增強的策展式環境。作為一名常駐上海的當代藝術策展人,我將最初的「複製粘貼給大模型」工作流,進化為一場讀者、文本與機器之間流動的、無縫的對話。 +靈感源自 Andrej Karpathy 的 [極簡閱讀器原型](https://x.com/karpathy/status/1990577951671509438),Smoothie Reader 是一個本地部署的 AI 智能電子書閱讀器,集成劃詞查詞、AI 翻譯與對話、TTS 朗讀、高亮筆記等功能,為深度閱讀而生。 -🏛️ **開幕展:沉思錄**。為了向專注思考的精神致敬,本倉庫預置了馬可·奧勒留的《沉思錄》(來源於 Project Gutenberg)。 +📖 **內置示例書籍**:倉庫預置了馬可·奧勒留的《沉思錄》(來源於 Project Gutenberg),克隆後即可體驗。
Library
個人藏書館:封面牆一覽無餘
-## ✨ 策展願景 - -大多數閱讀器只負責顯示文字,而 Smoothie Reader 將每一頁視為一個展位,將閱讀轉化為一場完整的智力體驗: +## ✨ 核心功能 - 🔍 **直覺探索**:選中文字秒開工具欄。內置支持 **ECDICT 英文詞典** 和 **中文離線詞典**。 -- 🤖 **增強對話**: - - **敘事級翻譯**:一鍵獲取高質量上下文翻譯,優雅地內嵌於原文下方。 - - **數位伴讀**:側邊欄 AI 助手支持流式輸出和多輪記憶,隨時進行深度的智力碰撞。 - - **全球連接**:完美支持 Gemini, OpenAI, Claude, DeepSeek 等 20+ 主流模型。 +- 🤖 **AI 增強閱讀**: + - **行內翻譯**:一鍵獲取高質量上下文翻譯,優雅地內嵌於原文下方。 + - **AI 伴讀**:側邊欄 AI 助手支持流式輸出和多輪記憶,隨時進行深度對話。 + - **廣泛兼容**:內置 OpenAI、Anthropic、Gemini、DeepSeek、Grok、阿里雲百煉、火山引擎、騰訊混元、MiniMax、月之暗面、矽基流動、Cerebras、SambaNova、Groq、Mistral、DeepInfra、Together AI、OpenRouter、智譜AI、ModelScope 共 20 家 AI 服務商,並支持任意 OpenAI 兼容接口自定義接入。
AI Tools
劃詞工具欄 · 行內翻譯 · AI 伴讀側欄
-- 🔊 **聲音冥想 (TTS)**:基於 Edge-TTS,提供 90+ 種高質量語音,賦予文字真實的生命力。 -- ✏️ **檔案系統**:5 色美學高亮、行內筆記、書簽,所有數據全部存儲在您的**本地保險庫**。 +- 🔊 **TTS 朗讀**:基於 Edge-TTS,提供中英文多種高質量語音,解放雙眼。 +- ✏️ **高亮筆記**:5 色高亮、行內筆記、書籤,所有數據存儲在瀏覽器**本地 localStorage** 中。
Reading Layout
三欄閱讀:目錄導航 · 沉浸正文 · 多色高亮
-- 🎨 **極簡審美**:6 種精選主題、靈活的三欄佈局(目錄/正文/AI),適配所有設備的極致視覺。 +- 🎨 **極簡審美**:6 種精選主題、靈活的三欄佈局(目錄/正文/AI),適配所有設備。
Settings
主題、排版、詞典、AI 模型 —— 一站式配置
-## 🚀 部署安裝 +## 🚀 快速開始 -本項目強制使用現代化的 [uv](https://docs.astral.sh/uv/) 作為環境編排工具,確保極速與隔離的運行表現。 +本項目使用 [uv](https://docs.astral.sh/uv/) 管理 Python 環境和依賴。 -### 1. 準備工作 -確保系統已安裝 Python 3.9+。安裝 `uv` 編排器: +### 1. 安裝 uv +確保系統已安裝 Python 3.10+,然後安裝 `uv`: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ``` -### 2. 開啟展覽 +### 2. 導入電子書並啟動 ```bash -# 導入您的第一件藏品 (以 Dracula 為例) -uv run reader3.py dracula.epub +# 導入一本 EPUB 電子書 +uv run reader3.py your_book.epub -# 啟動環境 +# 啟動服務 uv run server.py ``` 然後打開瀏覽器訪問:👉 **http://localhost:8123** -### 3. 激活 AI -進入閱讀頁面,點擊左上角 **設置 (Settings)**,配置您的 **AI 提供商**,將您的靈性與機器相連(例如,[免費獲取 Gemini Key](https://aistudio.google.com/apikey))。 +### 3. 配置 AI +進入閱讀頁面,點擊左上角 **設置 (Settings)**,配置您的 **AI 提供商**和 API Key(例如,[免費獲取 Gemini Key](https://aistudio.google.com/apikey))。 > [!TIP] > **🚀 秘密通道 (秘籍)**:在頁面任何地方,依次按下 **`↑ ↑ ↓ ↓ ← → ← → B A`** (Konami Code),即可解鎖隱藏的 **高級 AI 路由管理面板**。 -## 🛡️ 主權與隱私 -- **本地主權**:除了您主動觸發的 AI 或 TTS 請求外,沒有任何數據會離開您的避風港。 -- **無需帳號**:您的檔案僅保存在瀏覽器 `localStorage` 中。 +## 🛡️ 隱私保護 +- **本地優先**:除了您主動觸發的 AI 或 TTS 請求外,沒有任何數據會離開您的設備。 +- **無需帳號**:您的數據僅保存在瀏覽器 `localStorage` 中。 -## 📚 進階向導 -詳細的配置說明(如離線詞典下載、多設備訪問、端口修改),請參閱 [策展人指南](docs/GUIDE.md)。 +## 📚 使用指南 +詳細的配置說明(如離線詞典下載、多設備訪問、端口修改),請參閱 [使用指南](docs/GUIDE.md)。 ## 📄 許可證 [MIT License](LICENSE)