diff --git a/.dockerignore b/.dockerignore index 5d0f2bf2..c5602018 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,11 @@ .devcontainer/ .github/ .vscode/ -assets/ +.idea/ +.pytest_cache/ +.ruff_cache/ +.venv/ +docs_image/ k8s/ tests/ .dockerignore @@ -9,6 +13,7 @@ tests/ .gitignore .pre-commit-config.yaml .prettier* +.env.dev docker-compose.yml Dockerfile LICENSE diff --git a/.env.dev b/.env.dev index 127b4891..3e1059c2 100644 --- a/.env.dev +++ b/.env.dev @@ -1,5 +1,3 @@ - - SUPERUSERS=[""] COMMAND_START=[""] @@ -8,10 +6,29 @@ SESSION_RUNNING_EXPRESSION="别急呀,小真寻要宕机了!QAQ" NICKNAME=["真寻", "小真寻", "绪山真寻", "小寻子"] -SESSION_EXPIRE_TIMEOUT=30 +SESSION_EXPIRE_TIMEOUT=00:00:30 + +ALCONNA_USE_COMMAND_START=True # 全局图片统一使用bytes发送,当真寻与协议端不在同一服务器上时为True -IMAGE_TO_BYTES = False +IMAGE_TO_BYTES = True + +# 回复消息时自称 +SELF_NICKNAME="小真寻" + +# 官bot appid:bot账号 +QBOT_ID_DATA = '{ + +}' + +# 数据库配置 +# 示例: "postgres://user:password@127.0.0.1:5432/database" +# 示例: "mysql://user:password@127.0.0.1:3306/database" +# 示例: "sqlite:data/db/zhenxun.db" 在data目录下建立db文件夹 +DB_URL = "" + +# 系统代理 +# SYSTEM_PROXY = "http://127.0.0.1:7890" PLATFORM_SUPERUSERS = ' { @@ -22,6 +39,12 @@ PLATFORM_SUPERUSERS = ' DRIVER=~fastapi+~httpx+~websockets + +# LOG_LEVEL=DEBUG +# 服务器和端口 +HOST = 127.0.0.1 +PORT = 8080 + # kook adapter toekn # kaiheila_bots =[{"token": ""}] @@ -51,11 +74,4 @@ DRIVER=~fastapi+~httpx+~websockets # ' # application_commands的{"*": ["*"]}代表将全部应用命令注册为全局应用命令 -# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册 - -# LOG_LEVEL=DEBUG -# 服务器和端口 -HOST = 127.0.0.1 -PORT = 8080 - - \ No newline at end of file +# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..8de371d5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,85 @@ +name: Bug 反馈 +title: "Bug: 出现异常" +description: 提交 Bug 反馈以帮助我们改进代码 +labels: ["bug"] +body: + - type: dropdown + id: env-os + attributes: + label: 操作系统 + description: 选择运行 zhenxun_bot 的系统 + options: + - Windows + - MacOS + - Linux + - Other + validations: + required: true + + - type: input + id: env-python-ver + attributes: + label: Python 版本 + description: 填写运行 zhenxun_bot 的 Python 版本 + placeholder: e.g. 3.11.0 + validations: + required: true + + - type: input + id: env-zhenxun-ver + attributes: + label: zhenxun_bot 版本 + description: 填写 zhenxun_bot 版本 + placeholder: e.g. 0.1.0 + validations: + required: true + + - type: input + id: env-adapter + attributes: + label: 适配器 + description: 填写使用的适配器以及版本 + placeholder: e.g. OneBot v11 2.2.2 + validations: + required: true + + - type: input + id: env-protocol + attributes: + label: 协议端 + description: 填写连接 zhenxun_bot 的协议端及版本 + placeholder: e.g. NapCat V4.0.3 + validations: + required: true + + - type: textarea + id: describe + attributes: + label: 描述问题 + description: 清晰简洁地说明问题是什么 + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: 复现步骤 + description: 提供能复现此问题的详细操作步骤 + placeholder: | + 1. 首先…… + 2. 然后…… + 3. 发生…… + validations: + required: true + + - type: textarea + id: expected + attributes: + label: 期望的结果 + description: 清晰简洁地描述你期望发生的事情 + + - type: textarea + id: logs + attributes: + label: 截图或日志(请勿包含敏感信息如密码、令牌等) + description: 提供有助于诊断问题的任何日志和截图 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..ec4bb386 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/document.yml b/.github/ISSUE_TEMPLATE/document.yml new file mode 100644 index 00000000..352a13b2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/document.yml @@ -0,0 +1,18 @@ +name: 文档改进 +title: "Docs: 描述" +description: 文档错误及改进意见反馈 +labels: ["documentation"] +body: + - type: textarea + id: problem + attributes: + label: 描述问题或主题 + validations: + required: true + + - type: textarea + id: improve + attributes: + label: 需做出的修改 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..4f2e79f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,20 @@ +name: 功能建议 +title: "Feature: 功能描述" +description: 提出关于项目新功能的想法 +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: 希望能解决的问题 + description: 在使用中遇到什么问题而需要新的功能? + validations: + required: true + + - type: textarea + id: feature + attributes: + label: 描述所需要的功能 + description: 请说明需要的功能或解决方法 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/issue-template.md b/.github/ISSUE_TEMPLATE/issue-template.md deleted file mode 100644 index 087dc60a..00000000 --- a/.github/ISSUE_TEMPLATE/issue-template.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Issue template -about: " issue template's purpose here." -title: '' -labels: '' -assignees: '' - ---- - -### 系统版本:Ubuntu 20.04 -### 真寻版本:0.1.5.3 - -### 错误截图 - -[img] - -### 日志截图 - -[img] - -### 错误说明 - -发生了xx错误... diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..14adddbb --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,77 @@ +template: $CHANGES +name-template: "v$RESOLVED_VERSION" +tag-template: "v$RESOLVED_VERSION" +exclude-labels: + - reverted + - no-changelog + - skip-changelog + - invalid +autolabeler: + - label: "bug" + title: + - "/:bug:.+/" + - "/🐛.+/" + - label: "enhancement" + title: + - "/:sparkles:.+/" + - "/✨.+/" + - label: "ci" + files: + - .github/**/* + - label: "breaking-change" + title: + - "/.+!:.+/" + - label: "documentation" + files: + - "*.md" + - label: "dependencies" + files: + - "pyproject.toml" + - "requirements.txt" + - "poetry.lock" + title: + - "/:wrench:.+/" + - "/🔧.+/" + - label: "resources" + files: + - resources/**/* +categories: + - title: 💥 破坏性变更 + labels: + - breaking-change + - title: 🚀 新功能 + labels: + - enhancement + - title: 🐛 Bug 修复 + labels: + - bug + - title: 📝 文档更新 + labels: + - documentation + - title: 👻 自动化程序 + labels: + - chore + - internal + - maintenance + - title: 🚦 测试 + labels: + - test + - tests + - title: 📦 依赖更新 + labels: + - dependencies + collapse-after: 15 + - title: 💫 杂项 +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - "major" + minor: + labels: + - "minor" + patch: + labels: + - "patch" + default: patch diff --git a/.github/workflows/ISSUE_TEMPLATE.md b/.github/workflows/ISSUE_TEMPLATE.md deleted file mode 100644 index 00f57290..00000000 --- a/.github/workflows/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,9 +0,0 @@ -### 系统版本:Ubuntu 20.04 -### 真寻版本:0.1.5.3 - -### 错误截图 - -[img] - -### 错误说明 -发生了xx错误... diff --git a/.github/workflows/bot_check.yml b/.github/workflows/bot_check.yml new file mode 100644 index 00000000..3731ded8 --- /dev/null +++ b/.github/workflows/bot_check.yml @@ -0,0 +1,84 @@ +name: 检查bot是否运行正常 + +on: + push: + branches: ["main"] + paths: + - zhenxun/** + - tests/** + - .github/workflows/bot_check.yml + - bot.py + pull_request: + branches: ["main"] + paths: + - zhenxun/** + - tests/** + - .github/workflows/bot_check.yml + - bot.py + +jobs: + bot-check: + runs-on: ubuntu-latest + name: bot check + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + id: setup_python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install Poetry + run: pip install poetry + + # Poetry cache depends on OS, Python version and Poetry version. + - name: Cache Poetry cache + id: cache-poetry + uses: actions/cache@v3 + with: + path: ~/.cache/pypoetry + key: poetry-cache-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }}-${{ hashFiles('pyproject.toml') }} + + - name: Cache playwright cache + id: cache-playwright + uses: actions/cache@v3 + with: + path: ~/.cache/ms-playwright + key: playwright-cache-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }} + + - name: Cache Data cache + uses: actions/cache@v3 + with: + path: data + key: data-cache-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }} + + - name: Install dependencies + if: steps.cache-poetry.outputs.cache-hit != 'true' + run: | + rm -rf poetry.lock + poetry source remove aliyun + poetry install --no-root + + - name: Install playwright + if: steps.cache-playwright.outputs.cache-hit != 'true' + run: | + poetry run sudo apt-get update + poetry run sudo apt-get install -y libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav flite x264 libx264-dev + poetry run pip install playwright + poetry run playwright install-deps + poetry run playwright install + + - name: Run tests + run: poetry run pytest --cov=zhenxun --cov-report xml + + - name: Check bot run + id: bot_check_run + run: | + mv scripts/bot_check.py bot_check.py + sed -i "s|^.*\?DB_URL.*|DB_URL=\"${{ env.DB_URL }}\"|g" .env.dev + sed -i "s/^.*\?LOG_LEVEL.*/LOG_LEVEL=${{ env.LOG_LEVEL }}/g" .env.dev + poetry run python3 bot_check.py + env: + DB_URL: "sqlite://:memory:" + LOG_LEVEL: DEBUG diff --git a/.github/workflows/release_draft.yml b/.github/workflows/release_draft.yml new file mode 100644 index 00000000..31a9f843 --- /dev/null +++ b/.github/workflows/release_draft.yml @@ -0,0 +1,18 @@ +name: Release Drafter + +on: + push: + branches: + - main + - dev + pull_request: + types: [opened, reopened, synchronize] + +jobs: + update_release_draft: + name: Update Release Draft + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/update_version_pr.yml b/.github/workflows/update_version_pr.yml new file mode 100644 index 00000000..6f650aa2 --- /dev/null +++ b/.github/workflows/update_version_pr.yml @@ -0,0 +1,73 @@ +name: Update Version + +on: + push: + paths: + - .github/workflows/update_version_pr.yml + - zhenxun/** + - resources/** + - bot.py + branches: + - main + - dev + +jobs: + update-version: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_TOKEN }} + + - name: Read current version + id: read_version + run: | + version_line=$(grep '__version__' __version__) + version=$(echo $version_line | sed -E 's/__version__:\s*v([0-9]+\.[0-9]+\.[0-9]+)(-.+)?/\1/') + echo "Current version: $version" + echo "current_version=$version" >> $GITHUB_OUTPUT + + - name: Check for version file changes + id: check_diff + run: | + if git diff --name-only HEAD~1 HEAD | grep -q '__version__'; then + echo "Version file has changes" + echo "version_changed=true" >> $GITHUB_OUTPUT + else + echo "Version file has no changes" + echo "version_changed=false" >> $GITHUB_OUTPUT + fi + + - name: Get commit hash + id: get_commit_hash + run: echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Update version file + id: update_version + if: steps.check_diff.outputs.version_changed == 'false' + run: | + current_version="${{ steps.read_version.outputs.current_version }}" + commit_hash="${{ steps.get_commit_hash.outputs.commit_hash }}" + new_version="v${current_version}-${commit_hash}" + echo "new_version=$new_version" >> $GITHUB_OUTPUT + echo "Updating version to: $new_version" + echo "__version__: $new_version" > __version__ + + - name: Check updated version + if: steps.check_diff.outputs.version_changed == 'false' + run: cat __version__ + + - name: Create or update PR + if: steps.check_diff.outputs.version_changed == 'false' + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GH_TOKEN }} + branch: create-pr/update_version + title: ":tada: chore(version): 自动更新版本到 ${{ steps.update_version.outputs.new_version }}" + body: "This PR updates the version file." + commit-message: ":tada: chore(version): Update version to ${{ steps.update_version.outputs.new_version }}" + add-paths: __version__ + author: "AkashiCoin " + committer: "${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>" + labels: automated-update diff --git a/.gitignore b/.gitignore index 64932223..09193394 100644 --- a/.gitignore +++ b/.gitignore @@ -113,6 +113,7 @@ venv/ ENV/ env.bak/ venv.bak/ +.env.dev # Spyder project settings .spyderproject @@ -151,34 +152,9 @@ backup/ extensive_plugin/ test/ bot.py -data/ -.env -.env.dev -/resources/text/ -# /resources/image/ -/resources/temp/ -/resources/image/genshin/ -/resources/image/draw_card/ -/resources/image/card/ -/resources/image/temp/ -/resources/image/sign/today_card/ -/resources/image/image_management/ -/resources/image/bilibili_sub/ -/resources/image/other/ -/resources/image/_setu/ -/resources/image/_r18/ -/resources/image/csgo_cases/ -!/resources/image/csgo_cases/_background/ -/resources/image/superuser_help.png -/resources/image/update_img_help.png -/resources/image/prts/ +.idea/ +resources/ /configs/config.py configs/config.yaml -./.env -./.env.dev -plugins/csgo_server/ -plugins/activity/ -!/resources/image/genshin/alc/back.png -!/data/genshin_alc/ .vscode/launch.json -/resources/template/my_info \ No newline at end of file +plugins_/ \ No newline at end of file diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..d2c1095d --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,4 @@ +MD013: false +MD024: # 重复标题 + siblings_only: true +MD033: false # 允许 html \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..2cc3299b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +default_install_hook_types: [pre-commit] +ci: + autofix_commit_msg: ":rotating_light: auto fix by pre-commit hooks" + autofix_prs: true + autoupdate_branch: main + autoupdate_schedule: monthly + autoupdate_commit_msg: ":arrow_up: auto update by pre-commit hooks" +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.2 + hooks: + - id: ruff + args: [--fix] + stages: [pre-commit] + - id: ruff-format + stages: [pre-commit] diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..78547a79 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "charliermarsh.ruff", + "esbenp.prettier-vscode", + "ms-python.black-formatter", + "ms-python.isort", + "ms-python.python", + "ms-python.vscode-pylance" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index dc0ad84c..bc89a074 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,17 +13,49 @@ "getbbox", "hibiapi", "httpx", + "jsdelivr", "kaiheila", "lolicon", "nonebot", "onebot", "pixiv", + "qbot", "Setu", "tobytes", "ujson", "unban", "userinfo", - "zhenxun" + "zhenxun", + "jsdelivr" ], - "python.analysis.autoImportCompletions": true + "python.analysis.autoImportCompletions": true, + "python.testing.pytestArgs": ["tests"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", // 默认使用 Ruff 格式化 + "editor.wordBasedSuggestions": "allDocuments", + "editor.formatOnType": true, + "editor.formatOnSave": true, // 保存时自动格式化 + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "explicit", + "source.organizeImports": "explicit" + } + }, + "ruff.format.preview": false, + "isort.check": true, + "ruff.importStrategy": "useBundled", + "ruff.organizeImports": false, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..570303f6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,87 @@ +# zhenxun_bot 贡献者公约 + +## 我们的承诺 + +身为社区成员、贡献者和负责人,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。 + +我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。 + +## 我们的准则 + +有助于为我们的社区创造积极环境的行为例子包括但不限于: + +* 表现出对他人的同情和善意 +* 尊重不同的主张、观点和感受 +* 提出和大方接受建设性意见 +* 承担责任并向受我们错误影响的人道歉 +* 注重社区共同诉求,而非个人得失 + +不当行为例子包括: + +* 使用情色化的语言或图像,及性引诱或挑逗 +* 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击 +* 公开或私下的骚扰行为 +* 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址 +* 其他有理由认定为违反职业操守的不当行为 + +## 责任和权力 + +社区负责人有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。 + +社区负责人有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论(comment)、提交(commits)、代码、维基(wiki)编辑、议题(issues)或其他贡献,并在适当时机知采取措施的理由。 + +## 适用范围 + +本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。 + +代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。 + +## 监督 + +辱骂、骚扰或其他不可接受的行为可通过 775757368@qq.com 向负责监督的社区负责人报告。 +所有投诉都将得到及时和公平的审查和调查。 + +所有社区负责人都有义务尊重任何事件报告者的隐私和安全。 + +## 处理方针 + +社区负责人将遵循下列社区处理方针来明确他们所认定违反本行为准则的行为的处理方式: + +### 1. 纠正 + +**社区影响**:使用不恰当的语言或其他在社区中被认定为不符合职业道德或不受欢迎的行为。 + +**处理意见**:由社区负责人发出非公开的书面警告,明确说明违规行为的性质,并解释举止如何不妥。或将要求公开道歉。 + +### 2. 警告 + +**社区影响**:单个或一系列违规行为。 + +**处理意见**:警告并对连续性行为进行处理。在指定时间内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区场所和外部渠道中的互动。违反这些条款可能会导致临时或永久封禁。 + +### 3. 临时封禁 + +**社区影响**: 严重违反社区准则,包括持续的不当行为。 + +**处理意见**: 在指定时间内,暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与行为准则执行者互动。违反这些条款可能会导致永久封禁。 + +### 4. 永久封禁 + +**社区影响**:行为模式表现出违反社区准则,包括持续的不当行为、骚扰个人或攻击或贬低某个类别的个体。 + +**处理意见**:永久禁止在社区内进行任何形式的公开互动。 + +## 参见 + +本行为准则改编自 [Contributor Covenant][homepage] 2.1 版, 参见 [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]。 + +社区处理方针灵感来源于 [Mozilla's code of conduct enforcement ladder][Mozilla CoC]。 + +有关本行为准则的常见问题的答案,参见 [https://www.contributor-covenant.org/faq][FAQ]。 +其他语言翻译参见 [https://www.contributor-covenant.org/translations][translations]。 + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..0c3fb236 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,95 @@ +# zhenxun_bot 贡献指南 + +首先,感谢你愿意为 zhenxun_bot 贡献自己的一份力量! + +本指南旨在引导你更规范地向 zhenxun_bot 提交贡献,请务必认真阅读。 + +## 提交 Issue + +在提交 Issue 前,我们建议你先查看 [已有的 Issues](https://github.com/HibiKier/zhenxun_bot/issues),以防重复提交。 + +### 报告问题、故障与漏洞 + +如果你在使用过程中发现问题并确信是由 zhenxun_bot 引起的,欢迎提交 Issue。 + +请使用我们提供的 **Bug 反馈** 模板,并尽可能详细地描述: + +- 问题描述 +- 重现步骤 +- 你的环境信息(如操作系统、依赖版本等) + +### 建议功能 + +如果你有新的功能需求或改进建议,欢迎提出。 + +请使用 **功能建议** 模板,并详细描述你所需要的特性,可能的话可以提出你认为可行的解决方案。 + +### 文档相关 + +如果你觉得文档有误或缺乏更新,欢迎提出。 + +请使用 **文档改进** 模板,并详细描述问题或主题,希望我们做出的修改 + +## Pull Request + +### 分支管理 + +请从 `main` 分支创建新功能分支,例如: + +- 新功能:`feature/功能描述` +- 问题修复:`bugfix/问题描述` + +### 代码风格 + +zhenxun_bot 使用 `pre-commit` 进行代码格式化和检查,请在提交前确保代码通过检查。 + +```bash +# 在安装项目依赖后安装 pre-commit 钩子 +pre-commit install +``` + +> 未通过 `pre-commit` 检查的代码将无法合并。 + +### Commit 规范 + +请确保你的每一个 commit 都能清晰地描述其意图,一个 commit 尽量只有一个目的。 + +我们建议遵循 [gitmoji](https://gitmoji.dev/) 的 commit message 格式,在创建 commit 时请牢记这一点。 + +### 工作流程概述 + +`main` 分支为 zhenxun_bot 的主分支,在任何情况下都请不要直接修改 `main` 分支,而是创建一个目标分支为 `main` 的 Pull Request 来提交修改。Pull Request 标题请尽量清晰,以便维护者进行审核。 + +如果你不是 zhenxun_bot 团队的成员,可在 fork 本仓库后,向本仓库的 `main` 分支发起 Pull Request,注意遵循先前提到的 commit message 规范创建 commit。我们将在 code review 通过后合并你的贡献。 + +### 撰写文档 + +如果你对文档有改进建议,欢迎提交 Pull Request 或者 Issue。 + +[//]: # (我们使用 Markdown 编写文档,建议遵循以下规范:) + +[//]: # () +[//]: # (1. 中文与英文、数字、半角符号之间需要有空格。例:`zhenxun_bot 是一个高效的聊天机器人。`) + +[//]: # (2. 若非英文整句,使用全角标点符号。例:`现在你可以看到机器人回复你:“Hello,世界!”。`) + +[//]: # (3. 直引号`「」`和弯引号`“”`都可接受,但同一份文件里应使用同种引号。) + +[//]: # (4. **不要使用斜体**,你不需要一种与粗体不同的强调。) + +[//]: # (5. 文档中应以“我们”指代开发者,以“用户”指代机器人的使用者。) + +[//]: # () +[//]: # (如果你需要编辑器检查 Markdown 规范,可以在 VSCode 中安装 `markdownlint` 扩展。) + +### 参与开发 + +zhenxun_bot 的代码风格遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 与 [PEP 484](https://www.python.org/dev/peps/pep-0484/) 规范,请确保你的代码风格和项目已有的代码保持一致,变量命名清晰,有适当的注释与测试代码。 + +> 暂未搭建测试框架,因此暂不要求添加测试代码。 + +## 项目沟通 + +如有关于贡献流程的疑问或需要进一步指导,请通过 [QQ群](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) 联系我们。 + +再次感谢你的贡献! \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 18776e5a..acdbd5fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,67 @@ -FROM python:3.11-slim-bookworm +FROM python:3.11-bookworm AS requirements-stage -EXPOSE 8080 +WORKDIR /tmp + +ENV POETRY_HOME="/opt/poetry" PATH="${PATH}:/opt/poetry/bin" + +RUN curl -sSL https://install.python-poetry.org | python - -y && \ + poetry self add poetry-plugin-export + +COPY ./pyproject.toml ./poetry.lock* /tmp/ + +RUN poetry export \ + -f requirements.txt \ + --output requirements.txt \ + --without-hashes \ + --without-urls + +FROM python:3.11-bookworm AS build-stage + +WORKDIR /wheel + +COPY --from=requirements-stage /tmp/requirements.txt /wheel/requirements.txt + +# RUN python3 -m pip config set global.index-url https://mirrors.aliyun.com/pypi/simple + +RUN pip wheel --wheel-dir=/wheel --no-cache-dir --requirement /wheel/requirements.txt + +FROM python:3.11-bookworm AS metadata-stage + +WORKDIR /tmp + +RUN --mount=type=bind,source=./.git/,target=/tmp/.git/ \ + git describe --tags --exact-match > /tmp/VERSION 2>/dev/null \ + || git rev-parse --short HEAD > /tmp/VERSION \ + && echo "Building version: $(cat /tmp/VERSION)" + +FROM python:3.11-slim-bookworm WORKDIR /app/zhenxun -COPY . /app/zhenxun +ENV TZ=Asia/Shanghai PYTHONUNBUFFERED=1 +#COPY ./scripts/docker/start.sh /start.sh +#RUN chmod +x /start.sh -RUN pip install poetry -i https://mirrors.aliyun.com/pypi/simple/ +EXPOSE 8080 -RUN poetry install +RUN apt update && \ + apt install -y --no-install-recommends curl fontconfig fonts-noto-color-emoji \ + && apt clean \ + && fc-cache -fv \ + && apt-get purge -y --auto-remove curl \ + && rm -rf /var/lib/apt/lists/* -VOLUME /app/zhenxun/data /app/zhenxun/data +# 复制依赖项和应用代码 +COPY --from=build-stage /wheel /wheel +COPY . . -VOLUME /app/zhenxun/resources /app/zhenxun/resources +RUN pip install --no-cache-dir --no-index --find-links=/wheel -r /wheel/requirements.txt && rm -rf /wheel -RUN poetry run playwright install --with-deps chromium +RUN playwright install --with-deps chromium \ + && rm -rf /var/lib/apt/lists/* /tmp/* -CMD ["poetry", "run", "python", "bot.py"] \ No newline at end of file +COPY --from=metadata-stage /tmp/VERSION /app/VERSION + +VOLUME ["/app/zhenxun/data", "/app/zhenxun/resources", "/app/zhenxun/log"] + +CMD ["python", "bot.py"] diff --git a/README.md b/README.md index 3a223929..59db485c 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,45 @@ +
- +zhenxun_bot
- -![python](https://img.shields.io/badge/python-v3.9%2B-blue) -![nonebot](https://img.shields.io/badge/nonebot-v2.1.3-yellow) -![onebot](https://img.shields.io/badge/onebot-v11-black) - + + license + + + python + + + nonebot + + + onebot + + + onebot + + + QQ + + + black + + + pyright + + + ruff +
- -[![license](https://img.shields.io/badge/license-AGPL3.0-FE7D37)](https://github.com/HibiKier/zhenxun_bot/blob/main/LICENSE) -[![tencent-qq](https://img.shields.io/badge/%E7%BE%A4-是真寻酱哒-red?style=logo=tencent-qq)](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) +[![tencent-qq](https://img.shields.io/badge/%E7%BE%A4-是真寻酱哒-red?style=logo=tencent-qq)](https://qm.qq.com/q/mRNtLSl6uc) [![tencent-qq](https://img.shields.io/badge/%E7%BE%A4-真寻的技术群-c73e7e?style=logo=tencent-qq)](https://qm.qq.com/q/YYYt5rkMYc) -
@@ -30,49 +50,50 @@
-## 绪山真寻Bot +## 绪山真寻 Bot
-
“真寻是[椛椛](https://github.com/FloatTech/ZeroBot-Plugin)的好朋友!” -:tada:喜欢真寻,于是真寻就来了!:tada: +🎉喜欢真寻,于是真寻就来了!🎉 本项目符合 [OneBot](https://github.com/howmanybots/onebot) 标准,可基于以下项目与机器人框架/平台进行交互 -| 项目地址 | 平台 | 核心作者 | 备注 | -| :---: | :---: | :---: | :---: | -| [LLOneBot](https://github.com/LLOneBot/LLOneBot) | NTQQ | linyuchen | 可用 | -| [Napcat](https://github.com/NapNeko/NapCatQQ) | NTQQ | NapNeko | 可用 | -| [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) | ? | LagrangeDev/Linwenxuan04 | 可用 + +| 项目地址 | 平台 | 核心作者 | 备注 | +| :-----------------------------------------------------------: | :--: | :----------------------: | :--: | +| [LLOneBot](https://github.com/LLOneBot/LLOneBot) | NTQQ | linyuchen | 可用 | +| [Napcat](https://github.com/NapNeko/NapCatQQ) | NTQQ | NapNeko | 可用 | +| [Lagrange.Core](https://github.com/LagrangeDev/Lagrange.Core) | NTQQ | LagrangeDev/Linwenxuan04 | 可用 |
-![Star Trend](https://api.star-history.com/svg?repos=HibiKier/zhenxun_bot&type=Timeline) +Star Trend
-## 真寻觉得你需要帮助 +## 🤝 帮助页面 -
+
+点击展开查看图片 + zhenxun_help + html_help + help +
- - - -
- -## 这是一份扩展 +## 📦 这是一份扩展 ### 1. 体验一下? -这是一个免费的,版本为dev的zhenxun,你可以通过napcat或拉格朗日等直接连接用于体验与测试 +这是一个免费的,版本为 dev 的 zhenxun,你可以通过 [napcat](https://github.com/NapNeko/NapCatQQ) 或 [拉格朗日](https://github.com/LagrangeDev/Lagrange.Core) 以及 [matcha](https://github.com/A-kirami/matcha) 等直接连接用于体验与测试 (球球了测试君!) -``` -Url: 43.143.112.57:11451/onebot/v11/ws + +```text +Url: ws://test.zhenxun.org:8080/onebot/v11/ws AccessToken: PUBLIC_ZHENXUN_TEST 注:你无法获得超级用户权限 @@ -82,45 +103,30 @@ AccessToken: PUBLIC_ZHENXUN_TEST
-“不要害怕,你的背后还有千千万万的 伙伴 啊!” +“不要害怕,你的背后还有千千万万的 伙伴 啊!” -| 项目名称 | 主要用途 | 仓库作者 | 备注 | -| :---: | :---: | :---: | :---: | -| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻WebApi的webui实现 -| [一键安装](https://github.com/zhenxun-org/zhenxun_bot-deploy) | 安装 | [AkashiCoin](https://github.com/AkashiCoin) | 新版本未测试 -| [Docker单机版](https://github.com/Sakuracio/zhenxun_bot_docker) | 安装 | [zhenxun-org](https://github.com/zhenxun-org) | 新版本未测试 -| [Docker全量版](https://shields.io/badge/GITHUB-SinKy--Yan-4476AF?logo=github&style=for-the-badge) | 安装 | [zhenxun-org](https://github.com/zhenxun-org) | 包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等(新版本未测试) - -PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能无法正常启动全量版容器** - -
- WebUI 后台示例图 - -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui1.png) -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui2.png) -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui3.png) -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui4.png) -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui5.png) -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui6.png) -![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/webui7.png) - -
- -
+| 项目名称 | 主要用途 | 仓库作者 | 备注 | +| :--------------------------------------------------------------------: | :------: | :-------------------------------------------------: | :---------------------------: | +| [插件库](https://github.com/zhenxun-org/zhenxun_bot_plugins) | 插件 | [zhenxun-org](https://github.com/zhenxun-org) | 原 plugins 文件夹插件 | +| [插件索引库](https://github.com/zhenxun-org/zhenxun_bot_plugins_index) | 插件 | [zhenxun-org](https://github.com/zhenxun-org) | 扩展插件索引库 | +| [一键安装](https://github.com/soloxiaoye2022/zhenxun_bot-deploy) | 安装 | [soloxiaoye2022](https://github.com/soloxiaoye2022) | 第三方 | +| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 [预览](#-webui界面展示) | +| [安卓 app(WebUi)](https://github.com/YuS1aN/zhenxun_bot_android_ui) | 安装 | [YuS1aN](https://github.com/YuS1aN) | 第三方 |
-## ~~来点优点?~~ 可爱难道还不够吗 +## 🥰 ~~来点优点?~~ 可爱难道还不够吗 -* 实现了许多功能,且提供了大量功能管理命令 -* 通过Config配置项将所有插件配置统计保存至config.yaml,利于统一用户修改 -* 方便增删插件,原生nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息` -* 提供了cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等 -* **..... 更多详细请通过`传送门`查看文档!** +- 实现了许多功能,且提供了大量功能管理命令,进行了多平台适配,兼容 nb2 商店插件 +- 拥有完善可用的 webui +- 通过 Config 配置项将所有插件配置统计保存至 config.yaml,利于统一用户修改 +- 方便增删插件,原生 nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息` +- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等 +- **更多详细请通过 [传送门](https://hibikier.github.io/zhenxun_bot/) 查看文档!** -## 简单部署 +## 🛠️ 简单部署 -``` +```bash # 获取代码 git clone https://github.com/HibiKier/zhenxun_bot.git @@ -133,715 +139,220 @@ poetry install # 安装依赖 # 开始运行 poetry shell # 进入虚拟环境 -python bot.py - -# 在Linux系统,你可能还需要运行此命令安装playwright依赖 -playwright install-deps - -# 首次后会在data目录下生成database.json和config.yaml文件 -# database.json用户配置数据库信息 -# config.yaml用户配置插件 +python bot.py # 运行机器人 ``` -## 简单配置 +## 📝 简单配置 -``` -1.在.env.dev文件中 +> [!TIP] +> config.yaml 需要启动一次 Bot 后生成 - SUPERUSERS = [""] # 填写你的QQ +1.在 .env.dev 文件中填写你的机器人配置项 - PLATFORM_SUPERUSERS = ' - { - "qq": [""], # 在此处填写你的qq - "dodo": [], - "kaiheila": [], - "discord": [] - } -' - -2.在data/database.json文件中修改数据库配置 -{ - "bind": "", - "sql_name": "postgres", - "user": "", # 用户们 - "password": "", # 密码 - "address": "", # 数据库地址ip - "port": "", # 数据库端口 - "database": "" # 数据库名称 -} - -3.在configs/config.yaml文件中 # 该文件需要启动一次后生成 - * 修改插件配置项 - -``` - -## 功能列表 (旧版列表) +2.在 configs/config.yaml 文件中修改你需要修改的插件配置项
-已实现的功能 +数据库地址(DB_URL)配置说明 -### 已实现的常用功能 +DB_URL 是基于 Tortoise ORM 的数据库连接字符串,用于指定项目所使用的数据库。以下是 DB_URL 的组成部分以及示例: -* [x] 昵称系统(群与群与私聊分开.) +格式为: ```<数据库类型>://<用户名>:<密码>@<主机>:<端口>/<数据库名>?<参数>``` -* [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味 -* [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置) -* [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸) -* [x] 色图(这不是基础功能嘛喂) -* [x] coser -* [x] 黑白草图生成器 -* [x] 鸡汤/语录 -* [x] 骂我(钉宫语音) -* [x] 戳一戳(概率发送美图,钉宫语音或者戳回去) -* [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要session,可能需要代理,阿里云服务器等ip也许已经被ban了(我无代理访问失败),如果访问太多账号API调用可能被禁止访问api!) -* [x] 鲁迅说过 -* [x] 构造假消息(自定义的分享链接) -* [x] 商店/我的金币/购买道具/使用道具 -* [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)) -* [x] 我有一个朋友想问问..(借鉴pcrbot插件) -* [x] 原神黄历 -* [x] 原神今日素材 -* [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件) -* [x] 原神便笺查询 -* [x] 原神玩家查询 -* [x] 原神树脂提醒 -* [x] 原神签到/自动签到 -* [x] 金币红包 -* [x] 微博热搜 -* [x] B站主播/UP/番剧订阅 - -* [x] pil对图片的一些操作 -* [x] BUFF饰品底价查询(需要session) -* [x] 天气查询 -* [x] 疫情查询 -* [x] bt磁力搜索(咳咳,这功能我想dddd) -* [x] reimu搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件) -* [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件) -* [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件) -* [x] 搜番 -* [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论) -* [x] epic免费游戏 -* [x] p站排行榜 -* [x] p站搜图 -* [x] 翻译(日英韩) -* [x] pix图库(一个自己的图库,含有增删查改,黑名单等命令) - -* [x] 查看当前群欢迎消息 -* [x] 查看该群自己的权限 -* [x] 我的信息(只是为了看看什么时候入群) -* [x] 更新信息(如果继续更新的话) -* [x] go-cqhttp最新版下载和上传(不需要请删除) -* [x] 撤回 -* [x] 滴滴滴-(用户对超级用户发送消息) -* [x] 金币红包/金币排行 -* [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行 -* [x] 网易云热评 -* [x] 念首古诗 -* [x] 获取b站视频封面 -* [x] 通过PID获取图片 -* [x] 功能统计可视化 -* [x] 词云 -* [x] 关于 - -### 已实现的管理员功能 - -* [x] 更新群组成员信息 - -* [x] 95%的群功能开关 -* [x] 查看群内被动技能状态 -* [x] 自定义群欢迎消息(是真寻的不是管家的!) -* [x] .ban/.unban(支持设置ban时长)= 黑白名单 -* [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数 -* [x] 上传图片/连续上传图片 (上传图片至指定图库) -* [x] 移动图片 (同上) -* [x] 删除图片 (同上) -* [x] 群内B站订阅 -* [x] 词条设置 -* [x] 休息吧/醒来 - -### 已实现的超级用户功能 - -* [x] 添加/删除权限(是真寻的管理员权限,不是群管理员) - -* [x] 开启/关闭指定群的广播通知 -* [x] 广播 -* [x] 自检(检查系统状态) -* [x] 所有群组/所有好友 -* [x] 退出指定群 -* [x] 更新好友信息/更新群信息 -* [x] /t(对用户进行回复或发送消息) -* [x] 上传/删除/修改商品(需要编写对应的商品功能) -* [x] 节日红包发送 -* [x] 修改群权限 -* [x] ban -* [x] 更新色图 -* [x] 更新价格/更加图片(csgo开箱) -* [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池 -* [x] 更新原神今日素材/更新原神资源信息 -* [x] PIX相关操作 -* [x] 检查更新真寻 -* [x] 重启 -* [x] 添加/删除/查看群白名单 -* [x] 功能开关(更多设置) -* [x] 功能状态 -* [x] b了 -* [x] 执行sql -* [x] 重载配置 -* [x] 清理临时数据 -* [x] 增删群认证 -* [x] 同意/拒绝好友/群聊请求 -* [x] 配置重载 - -#### 超级用户的被动技能 - -* [x] 邀请入群提醒(别人邀请真寻入群) - -* [x] 添加好友提醒(别人添加真寻好友) - -### 已实现的被动技能 - -* [x] 进群欢迎消息 - -* [x] 群早晚安 -* [x] 每日开箱重置提醒 -* [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv),且5分钟内不解析相同url -* [x] 丢人爬(爬表情包) -* [x] epic通知(每日发送epic免费游戏链接) -* [x] 原神黄历提醒 -* [x] 复读 - -### 已实现的看不见的技能 - -* [x] 刷屏禁言检测 - -* [x] 功能调用统计 -* [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban) -* [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等 -* [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能)) -* [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限 -* [x] 群权限系统 -* [x] 定时更新权限 -* [x] 自动配置重载 +- 数据库类型:表示数据库类型,例如 postgres、mysql、sqlite 等。 +- 用户名:数据库的用户名,例如 root。 +- 密码:数据库的密码,例如 123456。 +- 主机:数据库的主机地址,例如 127.0.0.1(本地)或远程服务器 IP。 +- 端口:数据库的端口号,例如:PostgreSQL:5432, MySQL:3306 +- 数据库名:指定要使用的数据库名称,例如 zhenxun。 +- 参数(可选):用于传递额外的配置,例如字符集设置。
-## [爱发电](https://afdian.net/@HibiKier) +## 📋 功能列表 + +> [!NOTE] +> 真寻原 `plugins` 插件文件夹已迁移至 [插件仓库](https://github.com/zhenxun-org/zhenxun_bot_plugins) ,现在本体仅保留核心功能
-爱发电 以及 感谢投喂 - +内置功能 -### 感谢名单 +### 🔧 基础功能 -(可以告诉我你的 **github** 地址,我偷偷换掉0v|) +- 昵称系统(群与群与私聊分开) +- 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置) +- 商店/我的金币/购买道具/使用道具/金币排行(完整的商店添加/购买/使用流程) +- 查看当前群欢迎消息 +- 个人信息查看(群组内权限,聊天频率等) +- 消息撤回 +- 功能统计可视化 +- 关于 +- 三种样式的帮助菜单 -[shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377) -[A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377) -[疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377) -[投冥](https://afdian.net/a/144514mm) -[茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377) -[AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377) -[爱发电用户_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377) -[qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377) -[溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377) -[伝木](https://afdian.net/u/246b80683f9511edba7552540025c377) -[阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377) -[醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377) -[Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377) -[本喵无敌哒](https://afdian.net/u/dffaa9005bc911ebb69b52540025c377) -[椎名冬羽](https://afdian.net/u/ca1ebd64395e11ed81b452540025c377) -[kaito](https://afdian.net/u/a055e20a498811eab1f052540025c377) -[笑柒XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) -[请问一份爱多少钱](https://afdian.net/u/f57ef6602dbd11ed977f52540025c377) -[咸鱼鱼鱼鱼](https://afdian.net/u/8e39b9a400e011ed9f4a52540025c377) -[Kafka](https://afdian.net/u/41d66798ef6911ecbc5952540025c377) -[墨然](https://afdian.net/u/8aa5874a644d11eb8a6752540025c377) -[爱发电用户_T9e4](https://afdian.net/u/2ad1bb82f3a711eca22852540025c377) -[笑柒XIAO_Q7](https://afdian.net/u/4696db5c529111ec84ea52540025c377) -[noahzark](https://afdian.net/a/noahzark) -[腊条](https://afdian.net/u/f739c4d69eca11eba94b52540025c377) -[ze roller](https://afdian.net/u/0e599e96257211ed805152540025c377) -[爱发电用户_4jrf](https://afdian.net/u/6b2cdcc817c611ed949152540025c377) -[爱发电用户_TBsd](https://afdian.net/u/db638b60217911ed9efd52540025c377) -[烟寒若雨](https://afdian.net/u/067bd2161eec11eda62b52540025c377) -[ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377) -[爱发电用户_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377) -[爱发电用户_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377) -[爱发电用户_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377) -[MangataAkihi](https://github.com/Sakuracio) -[炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377) -[爱发电用户_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377) -[大魔王](https://github.com/xipesoy) -[CopilotLaLaLa](https://github.com/CopilotLaLaLa) -[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377) -[回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377) -[十年くん](https://github.com/shinianj) -[哇](https://afdian.net/u/9b266244f23911eca19052540025c377) -[yajiwa](https://github.com/yajiwa) -[爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377) +### 🛠️ 管理员功能 + +- 管理员帮助 +- 更新群组成员信息 +- 95%的群功能开关 +- 查看群内被动技能状态 +- 自定义群欢迎消息(是真寻的不是管家的!) +- ban/unban(支持设置 ban 时长)= 群组及用户的黑名单 +- 休息吧/醒来(群组内真寻状态) + +### 🧑‍💼 超级用户功能 + +- 超级用户帮助 +- 添加/删除权限(是真寻的管理员权限,不是群管理员) +- 群组管理,退群指令等 +- 广播 +- 自检(检查系统状态) +- 所有群组/所有好友 +- 退出指定群 +- 更新好友信息/更新群信息 +- 修改群权限 +- 检查更新 +- 重启 +- 添加/删除/查看群白名单 +- 功能开关(更多设置) +- 功能状态 +- 执行 SQL +- 重载配置 +- 清理临时数据 +- 增删群认证 +- 同意/拒绝好友/群聊请求 +- 添加/移除/更新插件/插件商店(plugins 库以及扩展库) +- WebUI API(对真寻前端的支持) + +#### 🛡️ 超级用户的被动技能 + +- 邀请入群提醒(别人邀请真寻入群,可配置自动同意) + +- 添加好友提醒(别人添加真寻好友,可配置自动同意) + +### 🤖 被动技能 + +- 群早晚安 + +### 👻 看不见的技能 + +- 功能调用统计 +- 聊天记录统计 +- 检测恶意触发命令(将被最高权限 ban 掉 30 分钟,只有最高权限(9 级)可以进行 unban) +- 自动同意好友/群组请求,加群请求将会提醒管理员,退群提示,加群欢迎等等 +- 群聊时间检测(当群聊最后一人发言时间大于当前 48 小时后将关闭该群所有通知(即被动技能)) +- 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限 +- 群权限系统 +- 定时更新权限 +- 自动配置重载 +- 强制入群保护 +- 自定备份(可配置) +- 笨蛋检测(当使用功能名称当指令时真寻会跳出来狠狠嘲笑并帮助)
-## 更新 +## 💖 赞助 -### 2024/8/11 +
+爱发电 + + + +
-* 更新dev +### 赞助名单 - - -
- -**..... 更多更新信息请查看文档** - -## Todo - -* [x] web管理 - -## 感谢 +## 🙏 感谢 [botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议 -[Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp的golang实现,轻量、原生跨平台. -[nonebot / nonebot2](https://github.com/nonebot/nonebot2) :跨平台Python异步机器人框架 -[Angel-Hair / XUN_Bot](https://github.com/Angel-Hair/XUN_Bot) :一个基于NoneBot和酷Q的功能性QQ机器人 -[pcrbot / cappuccilo_plugins](https://github.com/pcrbot/cappuccilo_plugins) :hoshino插件合集 +[Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp 的 golang 实现,轻量、原生跨平台. +[nonebot / nonebot2](https://github.com/nonebot/nonebot2) :跨平台 Python 异步机器人框架 +[Angel-Hair / XUN_Bot](https://github.com/Angel-Hair/XUN_Bot) :一个基于 NoneBot 和酷 Q 的功能性 QQ 机器人 +[pcrbot / cappuccilo_plugins](https://github.com/pcrbot/cappuccilo_plugins) :hoshino 插件合集 [MeetWq /nonebot-plugin-withdraw](https://github.com/MeetWq/nonebot-plugin-withdraw) :A simple withdraw plugin for Nonebot2 -[maxesisn / nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2) :适用于nonebot2的点歌插件 +[maxesisn / nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2) :适用于 nonebot2 的点歌插件 [nonepkg / nonebot-plugin-manager](https://github.com/nonepkg/nonebot-plugin-manager) :Nonebot Plugin Manager base on import hook -[H-K-Y / Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot) :原神bot,这是一个基于nonebot和HoshinoBot的原神娱乐及信息查询插件 -[NothAmor / nonebot2_luxun_says](https://github.com/NothAmor/nonebot2_luxun_says) :基于nonebot2机器人框架的鲁迅说插件 -[Kyomotoi / AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus) :一个~~特二刺螈~~(文爱)的适用于任何bot的词库 -[Ailitonia / omega-miya](https://github.com/Ailitonia/omega-miya) :基于nonebot2的qq机器人 -[KimigaiiWuyi / GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) :一个基于HoshinoBot/NoneBot2的原神UID查询插件 +[H-K-Y / Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot) :原神 bot,这是一个基于 nonebot 和 HoshinoBot 的原神娱乐及信息查询插件 +[NothAmor / nonebot2_luxun_says](https://github.com/NothAmor/nonebot2_luxun_says) :基于 nonebot2 机器人框架的鲁迅说插件 +[Kyomotoi / AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus) :一个~~特二刺螈~~(文爱)的适用于任何 bot 的词库 +[Ailitonia / omega-miya](https://github.com/Ailitonia/omega-miya) :基于 nonebot2 的 qq 机器人 +[KimigaiiWuyi / GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID) :一个基于 HoshinoBot/NoneBot2 的原神 UID 查询插件 + +## 📊 统计与活跃贡献者 + + + + + Performance Stats of HibiKier/zhenxun_bot - Last 28 days + + + + + + Active Contributors of HibiKier/zhenxun_bot - Last 28 days + + + +## 👨‍💻 开发者 + +感谢以下开发者对 绪山真寻 Bot 作出的贡献: + + + contributors + + +## 📸 WebUI界面展示 + +
+
+ webui00 +
+
+ webui01 +
+ +
+ webui02 +
+
+ webui03 +
+ +
+ webui04 +
+
+ webui05 +
+ +
+ webui06 +
+
+ webui07 +
+
diff --git a/__version__ b/__version__ index 1bb73be0..61c23b31 100644 --- a/__version__ +++ b/__version__ @@ -1 +1 @@ -__version__: v0.2 +__version__: v0.2.4-2c97eea diff --git a/bot.py b/bot.py index b3009b5c..52cd29fc 100644 --- a/bot.py +++ b/bot.py @@ -1,24 +1,25 @@ import nonebot # from nonebot.adapters.discord import Adapter as DiscordAdapter -from nonebot.adapters.dodo import Adapter as DoDoAdapter -from nonebot.adapters.kaiheila import Adapter as KaiheilaAdapter +# from nonebot.adapters.dodo import Adapter as DoDoAdapter +# from nonebot.adapters.kaiheila import Adapter as KaiheilaAdapter from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter -from zhenxun.services.db_context import disconnect, init - nonebot.init() + + driver = nonebot.get_driver() driver.register_adapter(OneBotV11Adapter) -driver.register_adapter(KaiheilaAdapter) -driver.register_adapter(DoDoAdapter) +# driver.register_adapter(KaiheilaAdapter) +# driver.register_adapter(DoDoAdapter) # driver.register_adapter(DiscordAdapter) +from zhenxun.services.db_context import disconnect, init driver.on_startup(init) driver.on_shutdown(disconnect) -nonebot.load_builtin_plugins("echo") # 内置插件 +# nonebot.load_builtin_plugins("echo") nonebot.load_plugins("zhenxun/builtin_plugins") nonebot.load_plugins("zhenxun/plugins") diff --git a/data/genshin_alc/config.json b/data/genshin_alc/config.json deleted file mode 100644 index 2ddfea98..00000000 --- a/data/genshin_alc/config.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "抽卡":{ - "buff": ["欧气满满,十连出金","出金不歪"], - "debuff": ["武器大师","保底出金","金色会是痛苦大剑"] - }, - "刷世界boss":{ - "buff": ["双攻双爆角斗士"], - "debuff": ["只有保底材料","贪生怕死角斗士"] - }, - "刷风本":{ - "buff": ["会有极品猎人套","会掉真正的少女心","治疗加成少女头"], - "debuff": ["勇往直前少女心","少女飘摇的杀意","少女暴怒的容颜"] - }, - "刷火本":{ - "buff": ["魔女帽子火伤杯","暴伤魔女帽!","火伤魔女心!"], - "debuff": ["幡 然 醒 悟","这么阴间的地方真的会有魔女套吗?","不务正业火魔女","会匹配到3个卢姥爷"] - }, - "刷岩本":{ - "buff": ["悠久的磐岩伴你左右","岩神的庇护常在"], - "debuff": ["防御流星杯,你值得拥有"] - }, - "刷宗室":{ - "buff": ["物理伤害骑士道,元素精通宗室套"], - "debuff": ["贪生怕死骑士道,物理伤害宗室杯"] - }, - "刷冰本":{ - "buff": ["双暴词条概率up"], - "debuff": ["防御力船帽,无人可及"] - }, - "刷雷本":{ - "buff": ["愿雷鸟伴你左右"], - "debuff": ["来表演一个只掉平雷套的绝活","风神忽悠雷凶兆"] - }, - "锄大地":{ - "buff": ["会掉一大堆紫色材料"], - "debuff": ["深渊法师爱你哟","会被冰水法控到死"] - }, - "挖矿":{ - "buff": ["开矿出双材料"], - "debuff": ["去别人世界会被拒"] - }, - "刷天赋本":{ - "buff": ["金色!我看到了金色的书!"], - "debuff": ["2蓝2绿不会变"] - }, - "刷突破材料":{ - "buff": ["金色!我看到了金色的材料!"], - "debuff": ["2蓝2绿不会变"] - }, - "升级圣遗物":{ - "buff": ["稀有词条跳跳跳","会双爆拉满"], - "debuff": ["女 仆 狂 喜","无中生有防御力","生命拉满","完美避开双爆"] - }, - "打风魔龙":{ - "buff": ["看我一箭一个风魔鸡","今天特瓦林可以给想要的突破材料","5金加原胚!"], - "debuff": ["会不小心掉下平台","不小心被地板烫死了"] - }, - "打狼王":{ - "buff": ["今天安德琉斯的心情不错,可以py一下","5金加原胚!"], - "debuff": ["狼尾巴*1"] - }, - "打公子":{ - "buff": ["今天可以和公子py想要的突破材料","5金加原胚!"], - "debuff": ["要角没有!要命一条!"] - } -} \ No newline at end of file diff --git a/docs_image/afd.jpg b/docs_image/afd.jpg new file mode 100644 index 00000000..e7acf463 Binary files /dev/null and b/docs_image/afd.jpg differ diff --git a/docs_image/webui00.png b/docs_image/webui00.png new file mode 100644 index 00000000..71f7d368 Binary files /dev/null and b/docs_image/webui00.png differ diff --git a/docs_image/webui01.png b/docs_image/webui01.png new file mode 100644 index 00000000..cd415685 Binary files /dev/null and b/docs_image/webui01.png differ diff --git a/docs_image/webui02.png b/docs_image/webui02.png new file mode 100644 index 00000000..0fcc4f05 Binary files /dev/null and b/docs_image/webui02.png differ diff --git a/docs_image/webui03.png b/docs_image/webui03.png new file mode 100644 index 00000000..2e7426e3 Binary files /dev/null and b/docs_image/webui03.png differ diff --git a/docs_image/webui04.png b/docs_image/webui04.png new file mode 100644 index 00000000..5810f71b Binary files /dev/null and b/docs_image/webui04.png differ diff --git a/docs_image/webui05.png b/docs_image/webui05.png new file mode 100644 index 00000000..d5f5e304 Binary files /dev/null and b/docs_image/webui05.png differ diff --git a/docs_image/webui06.png b/docs_image/webui06.png new file mode 100644 index 00000000..7541f679 Binary files /dev/null and b/docs_image/webui06.png differ diff --git a/docs_image/webui07.png b/docs_image/webui07.png new file mode 100644 index 00000000..1628ade7 Binary files /dev/null and b/docs_image/webui07.png differ diff --git a/docs_image/webui1.png b/docs_image/webui1.png deleted file mode 100644 index 2cfa5822..00000000 Binary files a/docs_image/webui1.png and /dev/null differ diff --git a/docs_image/webui2.png b/docs_image/webui2.png deleted file mode 100644 index ff884972..00000000 Binary files a/docs_image/webui2.png and /dev/null differ diff --git a/docs_image/webui3.png b/docs_image/webui3.png deleted file mode 100644 index f328df2d..00000000 Binary files a/docs_image/webui3.png and /dev/null differ diff --git a/docs_image/webui4.png b/docs_image/webui4.png deleted file mode 100644 index 8bf31231..00000000 Binary files a/docs_image/webui4.png and /dev/null differ diff --git a/docs_image/webui5.png b/docs_image/webui5.png deleted file mode 100644 index 6b8d350e..00000000 Binary files a/docs_image/webui5.png and /dev/null differ diff --git a/docs_image/webui6.png b/docs_image/webui6.png deleted file mode 100644 index 157ad8a6..00000000 Binary files a/docs_image/webui6.png and /dev/null differ diff --git a/docs_image/webui7.png b/docs_image/webui7.png deleted file mode 100644 index ac82ab02..00000000 Binary files a/docs_image/webui7.png and /dev/null differ diff --git a/docs_image/zhenxun_help.png b/docs_image/zhenxun_help.png new file mode 100644 index 00000000..b235830e Binary files /dev/null and b/docs_image/zhenxun_help.png differ diff --git a/poetry.lock b/poetry.lock index 9d4e8ff1..cc112cf5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,26 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "aiocache" +version = "0.12.3" +description = "multi backend asyncio cache" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d"}, + {file = "aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713"}, +] + +[package.extras] +memcached = ["aiomcache (>=0.5.2)"] +msgpack = ["msgpack (>=0.5.5)"] +redis = ["redis (>=4.2.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "aiofiles" @@ -6,6 +28,7 @@ version = "23.2.1" description = "File support for asyncio." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, @@ -14,144 +37,7 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "aiohappyeyeballs" -version = "2.3.5" -description = "Happy Eyeballs for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohappyeyeballs-2.3.5-py3-none-any.whl", hash = "sha256:4d6dea59215537dbc746e93e779caea8178c866856a721c9c660d7a5a7b8be03"}, - {file = "aiohappyeyeballs-2.3.5.tar.gz", hash = "sha256:6fa48b9f1317254f122a07a131a86b71ca6946ca989ce6326fff54a99a920105"}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "aiohttp" -version = "3.10.3" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc36cbdedf6f259371dbbbcaae5bb0e95b879bc501668ab6306af867577eb5db"}, - {file = "aiohttp-3.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85466b5a695c2a7db13eb2c200af552d13e6a9313d7fa92e4ffe04a2c0ea74c1"}, - {file = "aiohttp-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71bb1d97bfe7e6726267cea169fdf5df7658831bb68ec02c9c6b9f3511e108bb"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baec1eb274f78b2de54471fc4c69ecbea4275965eab4b556ef7a7698dee18bf2"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13031e7ec1188274bad243255c328cc3019e36a5a907978501256000d57a7201"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bbc55a964b8eecb341e492ae91c3bd0848324d313e1e71a27e3d96e6ee7e8e8"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8cc0564b286b625e673a2615ede60a1704d0cbbf1b24604e28c31ed37dc62aa"}, - {file = "aiohttp-3.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f817a54059a4cfbc385a7f51696359c642088710e731e8df80d0607193ed2b73"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8542c9e5bcb2bd3115acdf5adc41cda394e7360916197805e7e32b93d821ef93"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:671efce3a4a0281060edf9a07a2f7e6230dca3a1cbc61d110eee7753d28405f7"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0974f3b5b0132edcec92c3306f858ad4356a63d26b18021d859c9927616ebf27"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:44bb159b55926b57812dca1b21c34528e800963ffe130d08b049b2d6b994ada7"}, - {file = "aiohttp-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6ae9ae382d1c9617a91647575255ad55a48bfdde34cc2185dd558ce476bf16e9"}, - {file = "aiohttp-3.10.3-cp310-cp310-win32.whl", hash = "sha256:aed12a54d4e1ee647376fa541e1b7621505001f9f939debf51397b9329fd88b9"}, - {file = "aiohttp-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:b51aef59370baf7444de1572f7830f59ddbabd04e5292fa4218d02f085f8d299"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e021c4c778644e8cdc09487d65564265e6b149896a17d7c0f52e9a088cc44e1b"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24fade6dae446b183e2410a8628b80df9b7a42205c6bfc2eff783cbeedc224a2"}, - {file = "aiohttp-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc8e9f15939dacb0e1f2d15f9c41b786051c10472c7a926f5771e99b49a5957f"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5a9ec959b5381271c8ec9310aae1713b2aec29efa32e232e5ef7dcca0df0279"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a5d0ea8a6467b15d53b00c4e8ea8811e47c3cc1bdbc62b1aceb3076403d551f"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9ed607dbbdd0d4d39b597e5bf6b0d40d844dfb0ac6a123ed79042ef08c1f87e"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3e66d5b506832e56add66af88c288c1d5ba0c38b535a1a59e436b300b57b23e"}, - {file = "aiohttp-3.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fda91ad797e4914cca0afa8b6cccd5d2b3569ccc88731be202f6adce39503189"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:61ccb867b2f2f53df6598eb2a93329b5eee0b00646ee79ea67d68844747a418e"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d881353264e6156f215b3cb778c9ac3184f5465c2ece5e6fce82e68946868ef"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b031ce229114825f49cec4434fa844ccb5225e266c3e146cb4bdd025a6da52f1"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5337cc742a03f9e3213b097abff8781f79de7190bbfaa987bd2b7ceb5bb0bdec"}, - {file = "aiohttp-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab3361159fd3dcd0e48bbe804006d5cfb074b382666e6c064112056eb234f1a9"}, - {file = "aiohttp-3.10.3-cp311-cp311-win32.whl", hash = "sha256:05d66203a530209cbe40f102ebaac0b2214aba2a33c075d0bf825987c36f1f0b"}, - {file = "aiohttp-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:70b4a4984a70a2322b70e088d654528129783ac1ebbf7dd76627b3bd22db2f17"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166de65e2e4e63357cfa8417cf952a519ac42f1654cb2d43ed76899e2319b1ee"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7084876352ba3833d5d214e02b32d794e3fd9cf21fdba99cff5acabeb90d9806"}, - {file = "aiohttp-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d98c604c93403288591d7d6d7d6cc8a63459168f8846aeffd5b3a7f3b3e5e09"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d73b073a25a0bb8bf014345374fe2d0f63681ab5da4c22f9d2025ca3e3ea54fc"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8da6b48c20ce78f5721068f383e0e113dde034e868f1b2f5ee7cb1e95f91db57"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a9dcdccf50284b1b0dc72bc57e5bbd3cc9bf019060dfa0668f63241ccc16aa7"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56fb94bae2be58f68d000d046172d8b8e6b1b571eb02ceee5535e9633dcd559c"}, - {file = "aiohttp-3.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bf75716377aad2c718cdf66451c5cf02042085d84522aec1f9246d3e4b8641a6"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c51ed03e19c885c8e91f574e4bbe7381793f56f93229731597e4a499ffef2a5"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b84857b66fa6510a163bb083c1199d1ee091a40163cfcbbd0642495fed096204"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c124b9206b1befe0491f48185fd30a0dd51b0f4e0e7e43ac1236066215aff272"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3461d9294941937f07bbbaa6227ba799bc71cc3b22c40222568dc1cca5118f68"}, - {file = "aiohttp-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08bd0754d257b2db27d6bab208c74601df6f21bfe4cb2ec7b258ba691aac64b3"}, - {file = "aiohttp-3.10.3-cp312-cp312-win32.whl", hash = "sha256:7f9159ae530297f61a00116771e57516f89a3de6ba33f314402e41560872b50a"}, - {file = "aiohttp-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:e1128c5d3a466279cb23c4aa32a0f6cb0e7d2961e74e9e421f90e74f75ec1edf"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d1100e68e70eb72eadba2b932b185ebf0f28fd2f0dbfe576cfa9d9894ef49752"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a541414578ff47c0a9b0b8b77381ea86b0c8531ab37fc587572cb662ccd80b88"}, - {file = "aiohttp-3.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5548444ef60bf4c7b19ace21f032fa42d822e516a6940d36579f7bfa8513f9c"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba2e838b5e6a8755ac8297275c9460e729dc1522b6454aee1766c6de6d56e5e"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48665433bb59144aaf502c324694bec25867eb6630fcd831f7a893ca473fcde4"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bac352fceed158620ce2d701ad39d4c1c76d114255a7c530e057e2b9f55bdf9f"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0f670502100cdc567188c49415bebba947eb3edaa2028e1a50dd81bd13363f"}, - {file = "aiohttp-3.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b09f38a67679e32d380fe512189ccb0b25e15afc79b23fbd5b5e48e4fc8fd9"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:cd788602e239ace64f257d1c9d39898ca65525583f0fbf0988bcba19418fe93f"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:214277dcb07ab3875f17ee1c777d446dcce75bea85846849cc9d139ab8f5081f"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:32007fdcaab789689c2ecaaf4b71f8e37bf012a15cd02c0a9db8c4d0e7989fa8"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:123e5819bfe1b87204575515cf448ab3bf1489cdeb3b61012bde716cda5853e7"}, - {file = "aiohttp-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:812121a201f0c02491a5db335a737b4113151926a79ae9ed1a9f41ea225c0e3f"}, - {file = "aiohttp-3.10.3-cp38-cp38-win32.whl", hash = "sha256:b97dc9a17a59f350c0caa453a3cb35671a2ffa3a29a6ef3568b523b9113d84e5"}, - {file = "aiohttp-3.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:3731a73ddc26969d65f90471c635abd4e1546a25299b687e654ea6d2fc052394"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38d91b98b4320ffe66efa56cb0f614a05af53b675ce1b8607cdb2ac826a8d58e"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9743fa34a10a36ddd448bba8a3adc2a66a1c575c3c2940301bacd6cc896c6bf1"}, - {file = "aiohttp-3.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c126f532caf238031c19d169cfae3c6a59129452c990a6e84d6e7b198a001dc"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926e68438f05703e500b06fe7148ef3013dd6f276de65c68558fa9974eeb59ad"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:434b3ab75833accd0b931d11874e206e816f6e6626fd69f643d6a8269cd9166a"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d35235a44ec38109b811c3600d15d8383297a8fab8e3dec6147477ec8636712a"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59c489661edbd863edb30a8bd69ecb044bd381d1818022bc698ba1b6f80e5dd1"}, - {file = "aiohttp-3.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50544fe498c81cb98912afabfc4e4d9d85e89f86238348e3712f7ca6a2f01dab"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09bc79275737d4dc066e0ae2951866bb36d9c6b460cb7564f111cc0427f14844"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:af4dbec58e37f5afff4f91cdf235e8e4b0bd0127a2a4fd1040e2cad3369d2f06"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b22cae3c9dd55a6b4c48c63081d31c00fc11fa9db1a20c8a50ee38c1a29539d2"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ba562736d3fbfe9241dad46c1a8994478d4a0e50796d80e29d50cabe8fbfcc3f"}, - {file = "aiohttp-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f25d6c4e82d7489be84f2b1c8212fafc021b3731abdb61a563c90e37cced3a21"}, - {file = "aiohttp-3.10.3-cp39-cp39-win32.whl", hash = "sha256:b69d832e5f5fa15b1b6b2c8eb6a9fd2c0ec1fd7729cb4322ed27771afc9fc2ac"}, - {file = "aiohttp-3.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:673bb6e3249dc8825df1105f6ef74e2eab779b7ff78e96c15cadb78b04a83752"}, - {file = "aiohttp-3.10.3.tar.gz", hash = "sha256:21650e7032cc2d31fc23d353d7123e771354f2a3d5b05a5647fc30fea214e696"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.3.0" -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "aiosqlite" @@ -159,6 +45,7 @@ version = "0.17.0" description = "asyncio bridge to the standard sqlite3 module" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "aiosqlite-0.17.0-py3-none-any.whl", hash = "sha256:6c49dc6d3405929b1d08eeccc72306d3677503cc5e5e43771efc1e00232e8231"}, {file = "aiosqlite-0.17.0.tar.gz", hash = "sha256:f0e6acc24bc4864149267ac82fb46dfb3be4455f99fe21df82609cc6e6baee51"}, @@ -170,58 +57,77 @@ typing_extensions = ">=3.7.2" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "anyio" -version = "3.7.1" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] - [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "apscheduler" -version = "3.10.4" -description = "In-process task scheduler with Cron-like capabilities" +name = "anyio" +version = "4.8.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, - {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, ] [package.dependencies] -pytz = "*" -six = ">=1.4.0" -tzlocal = ">=2.0,<3.dev0 || >=4.dev0" +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["sphinx", "sphinx-rtd-theme"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "apscheduler" +version = "3.11.0" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"}, + {file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"}, +] + +[package.dependencies] +tzlocal = ">=3.0" + +[package.extras] +doc = ["packaging", "sphinx", "sphinx-rtd-theme (>=1.3.0)"] +etcd = ["etcd3", "protobuf (<=3.21.0)"] gevent = ["gevent"] mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=1.4)"] -testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -229,22 +135,23 @@ zookeeper = ["kazoo"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "arclet-alconna" -version = "1.8.23" +version = "1.8.35" description = "A High-performance, Generality, Humane Command Line Arguments Parser Library." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "arclet_alconna-1.8.23-py3-none-any.whl", hash = "sha256:d4d8a427715408399e46530ec6bdefff4de72ff5d51183fa50ce5ea56a4e2a2a"}, - {file = "arclet_alconna-1.8.23.tar.gz", hash = "sha256:f811caf60dc4231b70a6885fe1af35aa95ae93bad46566e9086b623f449c9a09"}, + {file = "arclet_alconna-1.8.35-py3-none-any.whl", hash = "sha256:95d8aaf079167b24e158a0c5125dc17c671da129969dcc5f8b79a9cc72b6389c"}, + {file = "arclet_alconna-1.8.35.tar.gz", hash = "sha256:0cdb7fbdd154110ed7fb79e2b281df6c5fc87861770301f1c0cf8af594ee95f3"}, ] [package.dependencies] -nepattern = ">=0.7.6,<1.0.0" -tarina = ">=0.5.5" +nepattern = ">=0.7.7,<1.0.0" +tarina = ">=0.6.1,<0.7.0" typing-extensions = ">=4.5.0" [package.extras] @@ -253,27 +160,28 @@ full = ["arclet-alconna-tools (>=0.2.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "arclet-alconna-tools" -version = "0.7.9" +version = "0.7.10" description = "Builtin Tools for Alconna" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "arclet_alconna_tools-0.7.9-py3-none-any.whl", hash = "sha256:01a3462bb9f8dbe55010b394f7a0ac11e331799d463e326738870dce191aa608"}, - {file = "arclet_alconna_tools-0.7.9.tar.gz", hash = "sha256:bded24c4157e13e2d803fe7b77ee246fda456206451337015513f150d1e4449c"}, + {file = "arclet_alconna_tools-0.7.10-py3-none-any.whl", hash = "sha256:50e8b2f433fbc612dc8b99f4f5410006dcb1ef406c971c795071117a4eab8e20"}, + {file = "arclet_alconna_tools-0.7.10.tar.gz", hash = "sha256:446a63a9c56886c23fb44548bb9a18655e0ba5b5dd80cc87915b858dfb02554c"}, ] [package.dependencies] -arclet-alconna = ">=1.8.21" +arclet-alconna = ">=1.8.31" nepattern = ">=0.7.3,<1.0.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "arrow" @@ -281,6 +189,7 @@ version = "1.3.0" description = "Better dates & times for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, @@ -297,101 +206,157 @@ test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" +name = "asgiref" +version = "3.8.1" +description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "asyncpg" -version = "0.29.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, ] [package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""} +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "async-asgi-testclient" +version = "1.4.11" +description = "Async client for testing ASGI web applications" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "async-asgi-testclient-1.4.11.tar.gz", hash = "sha256:4449ac85d512d661998ec61f91c9ae01851639611d748d81ae7f816736551792"}, +] + +[package.dependencies] +multidict = ">=4.0,<7.0" +requests = ">=2.21,<3.0" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11.0\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "asyncpg" +version = "0.30.0" +description = "An asyncio PostgreSQL driver" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"}, + {file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"}, + {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f"}, + {file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af"}, + {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75"}, + {file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f"}, + {file = "asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf"}, + {file = "asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50"}, + {file = "asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a"}, + {file = "asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed"}, + {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a"}, + {file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956"}, + {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056"}, + {file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454"}, + {file = "asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d"}, + {file = "asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f"}, + {file = "asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e"}, + {file = "asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a"}, + {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3"}, + {file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737"}, + {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a"}, + {file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af"}, + {file = "asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e"}, + {file = "asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305"}, + {file = "asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70"}, + {file = "asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3"}, + {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33"}, + {file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4"}, + {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4"}, + {file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba"}, + {file = "asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590"}, + {file = "asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e"}, + {file = "asyncpg-0.30.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ff1fc8b5bf724273782ff8b4f57b0f8220a1b2324184846b39d1ab4122031d"}, + {file = "asyncpg-0.30.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64e899bce0600871b55368b8483e5e3e7f1860c9482e7f12e0a771e747988168"}, + {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b290f4726a887f75dcd1b3006f484252db37602313f806e9ffc4e5996cfe5cb"}, + {file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86b0e2cd3f1249d6fe6fd6cfe0cd4538ba994e2d8249c0491925629b9104d0f"}, + {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:393af4e3214c8fa4c7b86da6364384c0d1b3298d45803375572f415b6f673f38"}, + {file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34"}, + {file = "asyncpg-0.30.0-cp38-cp38-win32.whl", hash = "sha256:0b448f0150e1c3b96cb0438a0d0aa4871f1472e58de14a3ec320dbb2798fb0d4"}, + {file = "asyncpg-0.30.0-cp38-cp38-win_amd64.whl", hash = "sha256:f23b836dd90bea21104f69547923a02b167d999ce053f3d502081acea2fba15b"}, + {file = "asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad"}, + {file = "asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff"}, + {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708"}, + {file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144"}, + {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb"}, + {file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547"}, + {file = "asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a"}, + {file = "asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773"}, + {file = "asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.11.0\""} + +[package.extras] +docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"] +gssauth = ["gssapi", "sspilib"] +test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "attrs" -version = "24.2.0" +version = "25.1.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a"}, + {file = "attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -399,21 +364,23 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "beautifulsoup4" -version = "4.12.3" +version = "4.13.3" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" +groups = ["main"] files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, ] [package.dependencies] soupsieve = ">1.2" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -425,7 +392,7 @@ lxml = ["lxml"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "bilireq" @@ -433,6 +400,7 @@ version = "0.2.3.post0" description = "" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "bilireq-0.2.3.post0-py3-none-any.whl", hash = "sha256:8d1f98bb8fb59c0ce1dec226329353ce51e2efaad0a6b4d240437b6132648322"}, {file = "bilireq-0.2.3.post0.tar.gz", hash = "sha256:3185c3952a2becc7d31b0c01a12fda463fa477253504a68f81ea871594887ab4"}, @@ -450,7 +418,7 @@ qrcode = ["qrcode[pil] (>=7.3.1,<8.0.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "binaryornot" @@ -458,6 +426,7 @@ version = "0.4.4" description = "Ultra-lightweight pure Python package to check if a file is binary or text." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4"}, {file = "binaryornot-0.4.4.tar.gz", hash = "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061"}, @@ -469,84 +438,18 @@ chardet = ">=3.0.2" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "cachetools" -version = "5.4.0" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "cashews" -version = "7.1.0" +version = "7.4.0" description = "cache tools with async power" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "cashews-7.1.0-py3-none-any.whl", hash = "sha256:b7c1ae4d49df6fdbff88e5025d3c1156515f58724c5b96fc9a9d081afada82a8"}, - {file = "cashews-7.1.0.tar.gz", hash = "sha256:058df55a39cb15697d331e7e41c2882b58d0d323f5671316105cc78668af7705"}, + {file = "cashews-7.4.0-py3-none-any.whl", hash = "sha256:e881cc9b4be05ac9ce2c448784bca2864776b1c13ee262658d7c0ebf0d3d257a"}, + {file = "cashews-7.4.0.tar.gz", hash = "sha256:c9d22b9b9da567788f232374a5de3b30ceed1e5c24085c96d304b696df0dcbd8"}, ] [package.extras] @@ -554,13 +457,13 @@ dill = ["dill"] diskcache = ["diskcache (>=5.0.0)"] lint = ["mypy (>=1.5.0)", "types-redis"] redis = ["redis (>=4.3.1,!=5.0.1)"] -speedup = ["bitarray (<3.0.0)", "hiredis", "xxhash (<4.0.0)"] -tests = ["hypothesis (==6.100.2)", "pytest (==8.2.0)", "pytest-asyncio (==0.23.6)", "pytest-cov (==5.0.0)", "pytest-rerunfailures (==14.0)"] +speedup = ["bitarray (<4.0.0)", "hiredis", "xxhash (<4.0.0)"] +tests = ["hypothesis (==6.115.3)", "pytest (==8.3.3)", "pytest-asyncio (==0.24.0)", "pytest-cov (==5.0.0)", "pytest-rerunfailures (==14.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "cattrs" @@ -568,6 +471,7 @@ version = "23.2.3" description = "Composable complex class support for attrs and dataclasses." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "cattrs-23.2.3-py3-none-any.whl", hash = "sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108"}, {file = "cattrs-23.2.3.tar.gz", hash = "sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f"}, @@ -590,98 +494,101 @@ ujson = ["ujson (>=5.7.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "certifi" -version = "2024.7.4" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "cffi" -version = "1.17.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ - {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, - {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, - {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, - {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, - {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, - {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, - {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, - {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, - {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, - {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, - {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, - {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, - {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, - {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, - {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, - {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -690,7 +597,24 @@ pycparser = "*" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "chardet" @@ -698,6 +622,7 @@ version = "5.2.0" description = "Universal encoding detector for Python 3" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, @@ -706,121 +631,125 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -829,27 +758,27 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "cn2an" -version = "0.5.22" +version = "0.5.23" description = "Convert Chinese numerals and Arabic numerals." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ - {file = "cn2an-0.5.22-py3-none-any.whl", hash = "sha256:cba4c8f305b43da01f50696047cca3116c727424ac62338da6a3426e01454f3e"}, - {file = "cn2an-0.5.22.tar.gz", hash = "sha256:27ae5b56441d7329ed2ececffa026bfa8fc353dcf1fb0d9146b303b9cce3ac37"}, + {file = "cn2an-0.5.23-py3-none-any.whl", hash = "sha256:b19ab3c53676765c038ccdab51f69b7efa4f0b888139c34088935769241f1cbf"}, + {file = "cn2an-0.5.23.tar.gz", hash = "sha256:eda06a63e5eff4a64488d9f22e5f2a4ceca6eaa63416e4f771e67edecb1a5bdb"}, ] [package.dependencies] -proces = ">=0.1.3" -setuptools = ">=47.3.1" +proces = ">=0.1.7" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "colorama" @@ -857,15 +786,17 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "cookiecutter" @@ -873,6 +804,7 @@ version = "2.6.0" description = "A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d"}, {file = "cookiecutter-2.6.0.tar.gz", hash = "sha256:db21f8169ea4f4fdc2408d48ca44859349de2647fbe494a9d6c3edfc0542c21c"}, @@ -891,104 +823,195 @@ rich = "*" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "coverage" +version = "7.6.12" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, + {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, + {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, + {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, + {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, + {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, + {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, + {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, + {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, + {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, + {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, + {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, + {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, + {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, + {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, + {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "cryptography" -version = "43.0.0" +version = "44.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" +python-versions = ">=3.7, !=3.9.0, !=3.9.1" +groups = ["main"] files = [ - {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, - {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, - {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, - {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, - {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, - {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, - {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, + {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"}, + {file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"}, + {file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"}, + {file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"}, + {file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"}, + {file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"}, + {file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "dateparser" -version = "1.2.0" +version = "1.2.1" description = "Date parsing library designed to parse dates from HTML pages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"}, - {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"}, + {file = "dateparser-1.2.1-py3-none-any.whl", hash = "sha256:bdcac262a467e6260030040748ad7c10d6bacd4f3b9cdb4cfd2251939174508c"}, + {file = "dateparser-1.2.1.tar.gz", hash = "sha256:7e4919aeb48481dbfc01ac9683c8e20bfe95bb715a38c1e9f6af889f4f30ccc3"}, ] [package.dependencies] -python-dateutil = "*" -pytz = "*" -regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" -tzlocal = "*" +python-dateutil = ">=2.7.0" +pytz = ">=2024.2" +regex = ">=2015.06.24,<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" +tzlocal = ">=0.2" [package.extras] -calendars = ["convertdate", "hijri-converter"] -fasttext = ["fasttext"] -langdetect = ["langdetect"] +calendars = ["convertdate (>=2.2.1)", "hijridate"] +fasttext = ["fasttext (>=0.9.1)", "numpy (>=1.19.3,<2)"] +langdetect = ["langdetect (>=1.0.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "ecdsa" @@ -996,6 +1019,7 @@ version = "0.19.0" description = "ECDSA cryptographic signature library (pure python)" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, @@ -1011,29 +1035,7 @@ gmpy2 = ["gmpy2"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "emoji" -version = "2.12.1" -description = "Emoji for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235"}, - {file = "emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb"}, -] - -[package.dependencies] -typing-extensions = ">=4.7.0" - -[package.extras] -dev = ["coverage", "pytest (>=7.4.4)"] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "exceptiongroup" @@ -1041,6 +1043,7 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -1052,32 +1055,53 @@ test = ["pytest (>=6)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "fastapi" -version = "0.112.0" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +name = "execnet" +version = "2.1.1" +description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "fastapi-0.112.0-py3-none-any.whl", hash = "sha256:3487ded9778006a45834b8c816ec4a48d522e2631ca9e75ec5a774f1b052f821"}, - {file = "fastapi-0.112.0.tar.gz", hash = "sha256:d262bc56b7d101d1f4e8fc0ad2ac75bb9935fec504d2b7117686cec50710cf05"}, + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.38.0" -typing-extensions = ">=4.8.0" - [package.extras] -all = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email_validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] +testing = ["hatch", "pre-commit", "pytest", "tox"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "fastapi" +version = "0.115.8" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi-0.115.8-py3-none-any.whl", hash = "sha256:753a96dd7e036b34eeef8babdfcfe3f28ff79648f86551eb36bfc1b0bf4a8cbf"}, + {file = "fastapi-0.115.8.tar.gz", hash = "sha256:0ce9111231720190473e222cdf0f07f7206ad7e53ea02beb1d2dc36e2f0741e9"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.46.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "feedparser" @@ -1085,6 +1109,7 @@ version = "6.0.11" description = "Universal feed parser, handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45"}, {file = "feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5"}, @@ -1096,185 +1121,111 @@ sgmllib3k = "*" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "filelock" -version = "3.15.4" +version = "3.17.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, + {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, + {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "greenlet" -version = "3.0.3" +version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] [package.extras] @@ -1284,70 +1235,80 @@ test = ["objgraph", "psutil"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "grpcio" -version = "1.65.4" +version = "1.70.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "grpcio-1.65.4-cp310-cp310-linux_armv7l.whl", hash = "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c"}, - {file = "grpcio-1.65.4-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82"}, - {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e"}, - {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de"}, - {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d"}, - {file = "grpcio-1.65.4-cp310-cp310-win32.whl", hash = "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1"}, - {file = "grpcio-1.65.4-cp310-cp310-win_amd64.whl", hash = "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec"}, - {file = "grpcio-1.65.4-cp311-cp311-linux_armv7l.whl", hash = "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef"}, - {file = "grpcio-1.65.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17"}, - {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4"}, - {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e"}, - {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5"}, - {file = "grpcio-1.65.4-cp311-cp311-win32.whl", hash = "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b"}, - {file = "grpcio-1.65.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558"}, - {file = "grpcio-1.65.4-cp312-cp312-linux_armv7l.whl", hash = "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56"}, - {file = "grpcio-1.65.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74"}, - {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63"}, - {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a"}, - {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28"}, - {file = "grpcio-1.65.4-cp312-cp312-win32.whl", hash = "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0"}, - {file = "grpcio-1.65.4-cp312-cp312-win_amd64.whl", hash = "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416"}, - {file = "grpcio-1.65.4-cp38-cp38-linux_armv7l.whl", hash = "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021"}, - {file = "grpcio-1.65.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864"}, - {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816"}, - {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524"}, - {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7"}, - {file = "grpcio-1.65.4-cp38-cp38-win32.whl", hash = "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a"}, - {file = "grpcio-1.65.4-cp38-cp38-win_amd64.whl", hash = "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f"}, - {file = "grpcio-1.65.4-cp39-cp39-linux_armv7l.whl", hash = "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733"}, - {file = "grpcio-1.65.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1"}, - {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b"}, - {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2"}, - {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257"}, - {file = "grpcio-1.65.4-cp39-cp39-win32.whl", hash = "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d"}, - {file = "grpcio-1.65.4-cp39-cp39-win_amd64.whl", hash = "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9"}, - {file = "grpcio-1.65.4.tar.gz", hash = "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39"}, + {file = "grpcio-1.70.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:95469d1977429f45fe7df441f586521361e235982a0b39e33841549143ae2851"}, + {file = "grpcio-1.70.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ed9718f17fbdb472e33b869c77a16d0b55e166b100ec57b016dc7de9c8d236bf"}, + {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:374d014f29f9dfdb40510b041792e0e2828a1389281eb590df066e1cc2b404e5"}, + {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2af68a6f5c8f78d56c145161544ad0febbd7479524a59c16b3e25053f39c87f"}, + {file = "grpcio-1.70.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7df14b2dcd1102a2ec32f621cc9fab6695effef516efbc6b063ad749867295"}, + {file = "grpcio-1.70.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c78b339869f4dbf89881e0b6fbf376313e4f845a42840a7bdf42ee6caed4b11f"}, + {file = "grpcio-1.70.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58ad9ba575b39edef71f4798fdb5c7b6d02ad36d47949cd381d4392a5c9cbcd3"}, + {file = "grpcio-1.70.0-cp310-cp310-win32.whl", hash = "sha256:2b0d02e4b25a5c1f9b6c7745d4fa06efc9fd6a611af0fb38d3ba956786b95199"}, + {file = "grpcio-1.70.0-cp310-cp310-win_amd64.whl", hash = "sha256:0de706c0a5bb9d841e353f6343a9defc9fc35ec61d6eb6111802f3aa9fef29e1"}, + {file = "grpcio-1.70.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:17325b0be0c068f35770f944124e8839ea3185d6d54862800fc28cc2ffad205a"}, + {file = "grpcio-1.70.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:dbe41ad140df911e796d4463168e33ef80a24f5d21ef4d1e310553fcd2c4a386"}, + {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5ea67c72101d687d44d9c56068328da39c9ccba634cabb336075fae2eab0d04b"}, + {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb5277db254ab7586769e490b7b22f4ddab3876c490da0a1a9d7c695ccf0bf77"}, + {file = "grpcio-1.70.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7831a0fc1beeeb7759f737f5acd9fdcda520e955049512d68fda03d91186eea"}, + {file = "grpcio-1.70.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:27cc75e22c5dba1fbaf5a66c778e36ca9b8ce850bf58a9db887754593080d839"}, + {file = "grpcio-1.70.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d63764963412e22f0491d0d32833d71087288f4e24cbcddbae82476bfa1d81fd"}, + {file = "grpcio-1.70.0-cp311-cp311-win32.whl", hash = "sha256:bb491125103c800ec209d84c9b51f1c60ea456038e4734688004f377cfacc113"}, + {file = "grpcio-1.70.0-cp311-cp311-win_amd64.whl", hash = "sha256:d24035d49e026353eb042bf7b058fb831db3e06d52bee75c5f2f3ab453e71aca"}, + {file = "grpcio-1.70.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:ef4c14508299b1406c32bdbb9fb7b47612ab979b04cf2b27686ea31882387cff"}, + {file = "grpcio-1.70.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:aa47688a65643afd8b166928a1da6247d3f46a2784d301e48ca1cc394d2ffb40"}, + {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:880bfb43b1bb8905701b926274eafce5c70a105bc6b99e25f62e98ad59cb278e"}, + {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e654c4b17d07eab259d392e12b149c3a134ec52b11ecdc6a515b39aceeec898"}, + {file = "grpcio-1.70.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2394e3381071045a706ee2eeb6e08962dd87e8999b90ac15c55f56fa5a8c9597"}, + {file = "grpcio-1.70.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b3c76701428d2df01964bc6479422f20e62fcbc0a37d82ebd58050b86926ef8c"}, + {file = "grpcio-1.70.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac073fe1c4cd856ebcf49e9ed6240f4f84d7a4e6ee95baa5d66ea05d3dd0df7f"}, + {file = "grpcio-1.70.0-cp312-cp312-win32.whl", hash = "sha256:cd24d2d9d380fbbee7a5ac86afe9787813f285e684b0271599f95a51bce33528"}, + {file = "grpcio-1.70.0-cp312-cp312-win_amd64.whl", hash = "sha256:0495c86a55a04a874c7627fd33e5beaee771917d92c0e6d9d797628ac40e7655"}, + {file = "grpcio-1.70.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:aa573896aeb7d7ce10b1fa425ba263e8dddd83d71530d1322fd3a16f31257b4a"}, + {file = "grpcio-1.70.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:d405b005018fd516c9ac529f4b4122342f60ec1cee181788249372524e6db429"}, + {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f32090238b720eb585248654db8e3afc87b48d26ac423c8dde8334a232ff53c9"}, + {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa089a734f24ee5f6880c83d043e4f46bf812fcea5181dcb3a572db1e79e01c"}, + {file = "grpcio-1.70.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19375f0300b96c0117aca118d400e76fede6db6e91f3c34b7b035822e06c35f"}, + {file = "grpcio-1.70.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:7c73c42102e4a5ec76608d9b60227d917cea46dff4d11d372f64cbeb56d259d0"}, + {file = "grpcio-1.70.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:0a5c78d5198a1f0aa60006cd6eb1c912b4a1520b6a3968e677dbcba215fabb40"}, + {file = "grpcio-1.70.0-cp313-cp313-win32.whl", hash = "sha256:fe9dbd916df3b60e865258a8c72ac98f3ac9e2a9542dcb72b7a34d236242a5ce"}, + {file = "grpcio-1.70.0-cp313-cp313-win_amd64.whl", hash = "sha256:4119fed8abb7ff6c32e3d2255301e59c316c22d31ab812b3fbcbaf3d0d87cc68"}, + {file = "grpcio-1.70.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:8058667a755f97407fca257c844018b80004ae8035565ebc2812cc550110718d"}, + {file = "grpcio-1.70.0-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:879a61bf52ff8ccacbedf534665bb5478ec8e86ad483e76fe4f729aaef867cab"}, + {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:0ba0a173f4feacf90ee618fbc1a27956bfd21260cd31ced9bc707ef551ff7dc7"}, + {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558c386ecb0148f4f99b1a65160f9d4b790ed3163e8610d11db47838d452512d"}, + {file = "grpcio-1.70.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:412faabcc787bbc826f51be261ae5fa996b21263de5368a55dc2cf824dc5090e"}, + {file = "grpcio-1.70.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3b0f01f6ed9994d7a0b27eeddea43ceac1b7e6f3f9d86aeec0f0064b8cf50fdb"}, + {file = "grpcio-1.70.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7385b1cb064734005204bc8994eed7dcb801ed6c2eda283f613ad8c6c75cf873"}, + {file = "grpcio-1.70.0-cp38-cp38-win32.whl", hash = "sha256:07269ff4940f6fb6710951116a04cd70284da86d0a4368fd5a3b552744511f5a"}, + {file = "grpcio-1.70.0-cp38-cp38-win_amd64.whl", hash = "sha256:aba19419aef9b254e15011b230a180e26e0f6864c90406fdbc255f01d83bc83c"}, + {file = "grpcio-1.70.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4f1937f47c77392ccd555728f564a49128b6a197a05a5cd527b796d36f3387d0"}, + {file = "grpcio-1.70.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:0cd430b9215a15c10b0e7d78f51e8a39d6cf2ea819fd635a7214fae600b1da27"}, + {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:e27585831aa6b57b9250abaf147003e126cd3a6c6ca0c531a01996f31709bed1"}, + {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1af8e15b0f0fe0eac75195992a63df17579553b0c4af9f8362cc7cc99ccddf4"}, + {file = "grpcio-1.70.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbce24409beaee911c574a3d75d12ffb8c3e3dd1b813321b1d7a96bbcac46bf4"}, + {file = "grpcio-1.70.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ff4a8112a79464919bb21c18e956c54add43ec9a4850e3949da54f61c241a4a6"}, + {file = "grpcio-1.70.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5413549fdf0b14046c545e19cfc4eb1e37e9e1ebba0ca390a8d4e9963cab44d2"}, + {file = "grpcio-1.70.0-cp39-cp39-win32.whl", hash = "sha256:b745d2c41b27650095e81dea7091668c040457483c9bdb5d0d9de8f8eb25e59f"}, + {file = "grpcio-1.70.0-cp39-cp39-win_amd64.whl", hash = "sha256:a31d7e3b529c94e930a117b2175b2efd179d96eb3c7a21ccb0289a8ab05b645c"}, + {file = "grpcio-1.70.0.tar.gz", hash = "sha256:8d1584a68d5922330025881e63a6c1b54cc8117291d382e4fa69339b6d914c56"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.65.4)"] +protobuf = ["grpcio-tools (>=1.70.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "h11" @@ -1355,6 +1316,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -1363,7 +1325,7 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "httpcore" @@ -1371,6 +1333,7 @@ version = "0.16.3" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, @@ -1389,60 +1352,68 @@ socks = ["socksio (==1.*)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "httptools" -version = "0.6.1" +version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, + {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, + {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, + {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, + {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, + {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, + {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, + {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, ] [package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] +test = ["Cython (>=0.29.24)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "httpx" @@ -1450,6 +1421,7 @@ version = "0.23.3" description = "The next generation HTTP client." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, @@ -1470,33 +1442,58 @@ socks = ["socksio (==1.*)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" +name = "identify" +version = "2.6.7" +description = "File identification library for Python" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0"}, + {file = "identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684"}, ] +[package.extras] +license = ["ukkonen"] + [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main", "dev"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "imagehash" -version = "4.3.1" +version = "4.3.2" description = "Image Hashing library" optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "ImageHash-4.3.1-py2.py3-none-any.whl", hash = "sha256:5ad9a5cde14fe255745a8245677293ac0d67f09c330986a351f34b614ba62fb5"}, - {file = "ImageHash-4.3.1.tar.gz", hash = "sha256:7038d1b7f9e0585beb3dd8c0a956f02b95a346c0b5f24a9e8cc03ebadaf0aa70"}, + {file = "ImageHash-4.3.2-py2.py3-none-any.whl", hash = "sha256:02b0f965f8c77cd813f61d7d39031ea27d4780e7ebcad56c6cd6a709acc06e5f"}, + {file = "ImageHash-4.3.2.tar.gz", hash = "sha256:e54a79805afb82a34acde4746a16540503a9636fd1ffb31d8e099b29bbbf8156"}, ] [package.dependencies] @@ -1508,31 +1505,53 @@ scipy = "*" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "importlib-metadata" -version = "8.2.0" +version = "8.6.1" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "iso8601" @@ -1540,6 +1559,7 @@ version = "1.1.0" description = "Simple module to parse ISO 8601 dates" optional = false python-versions = ">=3.6.2,<4.0" +groups = ["main"] files = [ {file = "iso8601-1.1.0-py3-none-any.whl", hash = "sha256:8400e90141bf792bce2634df533dc57e3bee19ea120a87bebcd3da89a58ad73f"}, {file = "iso8601-1.1.0.tar.gz", hash = "sha256:32811e7b81deee2063ea6d2e94f8819a86d1f3811e49d23623a41fa832bef03f"}, @@ -1548,17 +1568,18 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -1570,17 +1591,18 @@ i18n = ["Babel (>=2.7)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "loguru" -version = "0.7.2" +version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false -python-versions = ">=3.5" +python-versions = ">=3.5,<4.0" +groups = ["main", "dev"] files = [ - {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, - {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, + {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, + {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, ] [package.dependencies] @@ -1588,181 +1610,183 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] +dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "lxml" -version = "5.3.0" +version = "5.3.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ - {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, - {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, - {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, - {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, - {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, - {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, - {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, - {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, - {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, - {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, - {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, - {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, - {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, - {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, - {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, - {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, - {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, - {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, - {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, - {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, - {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, - {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, - {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, - {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, - {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, - {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, - {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, - {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, - {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, - {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, - {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, - {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, - {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, - {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, - {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, - {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, - {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, - {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, - {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, - {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, - {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, - {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, - {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, - {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, - {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, - {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, - {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, - {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, - {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, - {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, - {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, - {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, + {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a4058f16cee694577f7e4dd410263cd0ef75644b43802a689c2b3c2a7e69453b"}, + {file = "lxml-5.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:364de8f57d6eda0c16dcfb999af902da31396949efa0e583e12675d09709881b"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:528f3a0498a8edc69af0559bdcf8a9f5a8bf7c00051a6ef3141fdcf27017bbf5"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4743e30d6f5f92b6d2b7c86b3ad250e0bad8dee4b7ad8a0c44bfb276af89a3"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17b5d7f8acf809465086d498d62a981fa6a56d2718135bb0e4aa48c502055f5c"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:928e75a7200a4c09e6efc7482a1337919cc61fe1ba289f297827a5b76d8969c2"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a997b784a639e05b9d4053ef3b20c7e447ea80814a762f25b8ed5a89d261eac"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7b82e67c5feb682dbb559c3e6b78355f234943053af61606af126df2183b9ef9"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:f1de541a9893cf8a1b1db9bf0bf670a2decab42e3e82233d36a74eda7822b4c9"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:de1fc314c3ad6bc2f6bd5b5a5b9357b8c6896333d27fdbb7049aea8bd5af2d79"}, + {file = "lxml-5.3.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7c0536bd9178f754b277a3e53f90f9c9454a3bd108b1531ffff720e082d824f2"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:68018c4c67d7e89951a91fbd371e2e34cd8cfc71f0bb43b5332db38497025d51"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa826340a609d0c954ba52fd831f0fba2a4165659ab0ee1a15e4aac21f302406"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:796520afa499732191e39fc95b56a3b07f95256f2d22b1c26e217fb69a9db5b5"}, + {file = "lxml-5.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3effe081b3135237da6e4c4530ff2a868d3f80be0bda027e118a5971285d42d0"}, + {file = "lxml-5.3.1-cp310-cp310-win32.whl", hash = "sha256:a22f66270bd6d0804b02cd49dae2b33d4341015545d17f8426f2c4e22f557a23"}, + {file = "lxml-5.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:0bcfadea3cdc68e678d2b20cb16a16716887dd00a881e16f7d806c2138b8ff0c"}, + {file = "lxml-5.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e220f7b3e8656ab063d2eb0cd536fafef396829cafe04cb314e734f87649058f"}, + {file = "lxml-5.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f2cfae0688fd01f7056a17367e3b84f37c545fb447d7282cf2c242b16262607"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67d2f8ad9dcc3a9e826bdc7802ed541a44e124c29b7d95a679eeb58c1c14ade8"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db0c742aad702fd5d0c6611a73f9602f20aec2007c102630c06d7633d9c8f09a"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:198bb4b4dd888e8390afa4f170d4fa28467a7eaf857f1952589f16cfbb67af27"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2a3e412ce1849be34b45922bfef03df32d1410a06d1cdeb793a343c2f1fd666"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8969dbc8d09d9cd2ae06362c3bad27d03f433252601ef658a49bd9f2b22d79"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5be8f5e4044146a69c96077c7e08f0709c13a314aa5315981185c1f00235fe65"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:133f3493253a00db2c870d3740bc458ebb7d937bd0a6a4f9328373e0db305709"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:52d82b0d436edd6a1d22d94a344b9a58abd6c68c357ed44f22d4ba8179b37629"}, + {file = "lxml-5.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b6f92e35e2658a5ed51c6634ceb5ddae32053182851d8cad2a5bc102a359b33"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:203b1d3eaebd34277be06a3eb880050f18a4e4d60861efba4fb946e31071a295"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:155e1a5693cf4b55af652f5c0f78ef36596c7f680ff3ec6eb4d7d85367259b2c"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22ec2b3c191f43ed21f9545e9df94c37c6b49a5af0a874008ddc9132d49a2d9c"}, + {file = "lxml-5.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7eda194dd46e40ec745bf76795a7cccb02a6a41f445ad49d3cf66518b0bd9cff"}, + {file = "lxml-5.3.1-cp311-cp311-win32.whl", hash = "sha256:fb7c61d4be18e930f75948705e9718618862e6fc2ed0d7159b2262be73f167a2"}, + {file = "lxml-5.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c809eef167bf4a57af4b03007004896f5c60bd38dc3852fcd97a26eae3d4c9e6"}, + {file = "lxml-5.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e69add9b6b7b08c60d7ff0152c7c9a6c45b4a71a919be5abde6f98f1ea16421c"}, + {file = "lxml-5.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4e52e1b148867b01c05e21837586ee307a01e793b94072d7c7b91d2c2da02ffe"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4b382e0e636ed54cd278791d93fe2c4f370772743f02bcbe431a160089025c9"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e49dc23a10a1296b04ca9db200c44d3eb32c8d8ec532e8c1fd24792276522a"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4399b4226c4785575fb20998dc571bc48125dc92c367ce2602d0d70e0c455eb0"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5412500e0dc5481b1ee9cf6b38bb3b473f6e411eb62b83dc9b62699c3b7b79f7"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c93ed3c998ea8472be98fb55aed65b5198740bfceaec07b2eba551e55b7b9ae"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63d57fc94eb0bbb4735e45517afc21ef262991d8758a8f2f05dd6e4174944519"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:b450d7cabcd49aa7ab46a3c6aa3ac7e1593600a1a0605ba536ec0f1b99a04322"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:4df0ec814b50275ad6a99bc82a38b59f90e10e47714ac9871e1b223895825468"}, + {file = "lxml-5.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d184f85ad2bb1f261eac55cddfcf62a70dee89982c978e92b9a74a1bfef2e367"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b725e70d15906d24615201e650d5b0388b08a5187a55f119f25874d0103f90dd"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a31fa7536ec1fb7155a0cd3a4e3d956c835ad0a43e3610ca32384d01f079ea1c"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3c3c8b55c7fc7b7e8877b9366568cc73d68b82da7fe33d8b98527b73857a225f"}, + {file = "lxml-5.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d61ec60945d694df806a9aec88e8f29a27293c6e424f8ff91c80416e3c617645"}, + {file = "lxml-5.3.1-cp312-cp312-win32.whl", hash = "sha256:f4eac0584cdc3285ef2e74eee1513a6001681fd9753b259e8159421ed28a72e5"}, + {file = "lxml-5.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:29bfc8d3d88e56ea0a27e7c4897b642706840247f59f4377d81be8f32aa0cfbf"}, + {file = "lxml-5.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e"}, + {file = "lxml-5.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8"}, + {file = "lxml-5.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5"}, + {file = "lxml-5.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252"}, + {file = "lxml-5.3.1-cp313-cp313-win32.whl", hash = "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78"}, + {file = "lxml-5.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332"}, + {file = "lxml-5.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:016b96c58e9a4528219bb563acf1aaaa8bc5452e7651004894a973f03b84ba81"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82a4bb10b0beef1434fb23a09f001ab5ca87895596b4581fd53f1e5145a8934a"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d68eeef7b4d08a25e51897dac29bcb62aba830e9ac6c4e3297ee7c6a0cf6439"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:f12582b8d3b4c6be1d298c49cb7ae64a3a73efaf4c2ab4e37db182e3545815ac"}, + {file = "lxml-5.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2df7ed5edeb6bd5590914cd61df76eb6cce9d590ed04ec7c183cf5509f73530d"}, + {file = "lxml-5.3.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:585c4dc429deebc4307187d2b71ebe914843185ae16a4d582ee030e6cfbb4d8a"}, + {file = "lxml-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:06a20d607a86fccab2fc15a77aa445f2bdef7b49ec0520a842c5c5afd8381576"}, + {file = "lxml-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:057e30d0012439bc54ca427a83d458752ccda725c1c161cc283db07bcad43cf9"}, + {file = "lxml-5.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4867361c049761a56bd21de507cab2c2a608c55102311d142ade7dab67b34f32"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dddf0fb832486cc1ea71d189cb92eb887826e8deebe128884e15020bb6e3f61"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bcc211542f7af6f2dfb705f5f8b74e865592778e6cafdfd19c792c244ccce19"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaca5a812f050ab55426c32177091130b1e49329b3f002a32934cd0245571307"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:236610b77589faf462337b3305a1be91756c8abc5a45ff7ca8f245a71c5dab70"}, + {file = "lxml-5.3.1-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:aed57b541b589fa05ac248f4cb1c46cbb432ab82cbd467d1c4f6a2bdc18aecf9"}, + {file = "lxml-5.3.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:75fa3d6946d317ffc7016a6fcc44f42db6d514b7fdb8b4b28cbe058303cb6e53"}, + {file = "lxml-5.3.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:96eef5b9f336f623ffc555ab47a775495e7e8846dde88de5f941e2906453a1ce"}, + {file = "lxml-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:ef45f31aec9be01379fc6c10f1d9c677f032f2bac9383c827d44f620e8a88407"}, + {file = "lxml-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0611da6b07dd3720f492db1b463a4d1175b096b49438761cc9f35f0d9eaaef5"}, + {file = "lxml-5.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2aca14c235c7a08558fe0a4786a1a05873a01e86b474dfa8f6df49101853a4e"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82fce1d964f065c32c9517309f0c7be588772352d2f40b1574a214bd6e6098"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7aae7a3d63b935babfdc6864b31196afd5145878ddd22f5200729006366bc4d5"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8e0d177b1fe251c3b1b914ab64135475c5273c8cfd2857964b2e3bb0fe196a7"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:6c4dd3bfd0c82400060896717dd261137398edb7e524527438c54a8c34f736bf"}, + {file = "lxml-5.3.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f1208c1c67ec9e151d78aa3435aa9b08a488b53d9cfac9b699f15255a3461ef2"}, + {file = "lxml-5.3.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c6aacf00d05b38a5069826e50ae72751cb5bc27bdc4d5746203988e429b385bb"}, + {file = "lxml-5.3.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5881aaa4bf3a2d086c5f20371d3a5856199a0d8ac72dd8d0dbd7a2ecfc26ab73"}, + {file = "lxml-5.3.1-cp38-cp38-win32.whl", hash = "sha256:45fbb70ccbc8683f2fb58bea89498a7274af1d9ec7995e9f4af5604e028233fc"}, + {file = "lxml-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:7512b4d0fc5339d5abbb14d1843f70499cab90d0b864f790e73f780f041615d7"}, + {file = "lxml-5.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5885bc586f1edb48e5d68e7a4b4757b5feb2a496b64f462b4d65950f5af3364f"}, + {file = "lxml-5.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1b92fe86e04f680b848fff594a908edfa72b31bfc3499ef7433790c11d4c8cd8"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a091026c3bf7519ab1e64655a3f52a59ad4a4e019a6f830c24d6430695b1cf6a"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ffb141361108e864ab5f1813f66e4e1164181227f9b1f105b042729b6c15125"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3715cdf0dd31b836433af9ee9197af10e3df41d273c19bb249230043667a5dfd"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88b72eb7222d918c967202024812c2bfb4048deeb69ca328363fb8e15254c549"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa59974880ab5ad8ef3afaa26f9bda148c5f39e06b11a8ada4660ecc9fb2feb3"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3bb8149840daf2c3f97cebf00e4ed4a65a0baff888bf2605a8d0135ff5cf764e"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:0d6b2fa86becfa81f0a0271ccb9eb127ad45fb597733a77b92e8a35e53414914"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:136bf638d92848a939fd8f0e06fcf92d9f2e4b57969d94faae27c55f3d85c05b"}, + {file = "lxml-5.3.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:89934f9f791566e54c1d92cdc8f8fd0009447a5ecdb1ec6b810d5f8c4955f6be"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8ade0363f776f87f982572c2860cc43c65ace208db49c76df0a21dde4ddd16e"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:bfbbab9316330cf81656fed435311386610f78b6c93cc5db4bebbce8dd146675"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:172d65f7c72a35a6879217bcdb4bb11bc88d55fb4879e7569f55616062d387c2"}, + {file = "lxml-5.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3c623923967f3e5961d272718655946e5322b8d058e094764180cdee7bab1af"}, + {file = "lxml-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ce0930a963ff593e8bb6fda49a503911accc67dee7e5445eec972668e672a0f0"}, + {file = "lxml-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7b64fcd670bca8800bc10ced36620c6bbb321e7bc1214b9c0c0df269c1dddc2"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:afa578b6524ff85fb365f454cf61683771d0170470c48ad9d170c48075f86725"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f5e80adf0aafc7b5454f2c1cb0cde920c9b1f2cbd0485f07cc1d0497c35c5d"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd0b80ac2d8f13ffc906123a6f20b459cb50a99222d0da492360512f3e50f84"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:422c179022ecdedbe58b0e242607198580804253da220e9454ffe848daa1cfd2"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:524ccfded8989a6595dbdda80d779fb977dbc9a7bc458864fc9a0c2fc15dc877"}, + {file = "lxml-5.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:48fd46bf7155def2e15287c6f2b133a2f78e2d22cdf55647269977b873c65499"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:05123fad495a429f123307ac6d8fd6f977b71e9a0b6d9aeeb8f80c017cb17131"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a243132767150a44e6a93cd1dde41010036e1cbc63cc3e9fe1712b277d926ce3"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c92ea6d9dd84a750b2bae72ff5e8cf5fdd13e58dda79c33e057862c29a8d5b50"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2f1be45d4c15f237209bbf123a0e05b5d630c8717c42f59f31ea9eae2ad89394"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a83d3adea1e0ee36dac34627f78ddd7f093bb9cfc0a8e97f1572a949b695cb98"}, + {file = "lxml-5.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3edbb9c9130bac05d8c3fe150c51c337a471cc7fdb6d2a0a7d3a88e88a829314"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2f23cf50eccb3255b6e913188291af0150d89dab44137a69e14e4dcb7be981f1"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7e5edac4778127f2bf452e0721a58a1cfa4d1d9eac63bdd650535eb8543615"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:094b28ed8a8a072b9e9e2113a81fda668d2053f2ca9f2d202c2c8c7c2d6516b1"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:514fe78fc4b87e7a7601c92492210b20a1b0c6ab20e71e81307d9c2e377c64de"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8fffc08de02071c37865a155e5ea5fce0282e1546fd5bde7f6149fcaa32558ac"}, + {file = "lxml-5.3.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4b0d5cdba1b655d5b18042ac9c9ff50bda33568eb80feaaca4fc237b9c4fbfde"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3031e4c16b59424e8d78522c69b062d301d951dc55ad8685736c3335a97fc270"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb659702a45136c743bc130760c6f137870d4df3a9e14386478b8a0511abcfca"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a11b16a33656ffc43c92a5343a28dc71eefe460bcc2a4923a96f292692709f6"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5ae125276f254b01daa73e2c103363d3e99e3e10505686ac7d9d2442dd4627a"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76722b5ed4a31ba103e0dc77ab869222ec36efe1a614e42e9bcea88a36186fe"}, + {file = "lxml-5.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:33e06717c00c788ab4e79bc4726ecc50c54b9bfb55355eae21473c145d83c2d2"}, + {file = "lxml-5.3.1.tar.gz", hash = "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] -html-clean = ["lxml-html-clean"] +html-clean = ["lxml_html_clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=3.0.11)"] +source = ["Cython (>=3.0.11,<3.1.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "markdown" -version = "3.6" +version = "3.7" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, - {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ] [package.extras] @@ -1772,7 +1796,7 @@ testing = ["coverage", "pyyaml"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "markdown-it-py" @@ -1780,6 +1804,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1801,81 +1826,83 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "mdurl" @@ -1883,6 +1910,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1891,207 +1919,207 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "msgpack" -version = "1.0.8" +version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "multidict" -version = "6.0.5" +version = "6.1.0" description = "multidict implementation" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nb-cli" -version = "1.4.1" +version = "1.4.2" description = "CLI for nonebot2" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ - {file = "nb_cli-1.4.1-py3-none-any.whl", hash = "sha256:57b6111773202bce29c0520f4a281edb8a7643fa33692d4afc70ca5b51b10f70"}, - {file = "nb_cli-1.4.1.tar.gz", hash = "sha256:908dd4cbbf66bf46fe879c23ad1377332f63385cebca1912b627aa686d1816f3"}, + {file = "nb_cli-1.4.2-py3-none-any.whl", hash = "sha256:8348480a988fb8632130e14925977ad117d4a0c76c971f91ad813f91a7592263"}, + {file = "nb_cli-1.4.2.tar.gz", hash = "sha256:1d97b2d51569c7f7c7371744b9ed4b73361bc1853111bde2ddf1e990a1e19fef"}, ] [package.dependencies] @@ -2113,17 +2141,18 @@ wcwidth = ">=0.2,<1.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nepattern" -version = "0.7.6" +version = "0.7.7" description = "a complex pattern, support typing" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "nepattern-0.7.6-py3-none-any.whl", hash = "sha256:233d0befecc190f228ded3651a85faaf53f1308bba40ab8ddec379d0d3c88051"}, - {file = "nepattern-0.7.6.tar.gz", hash = "sha256:07bd5b2f3b9b9739b703bf723ffd642ca93738a32df7b699d57d6f338d46bad0"}, + {file = "nepattern-0.7.7-py3-none-any.whl", hash = "sha256:2d66f964333f42df7971390da4fb98dfed1e8b769236f305c28a83c0bcda849a"}, + {file = "nepattern-0.7.7.tar.gz", hash = "sha256:6667f888457e78937998f9412eb70ad16d220464d2d77850dd2b05e9ecfb3207"}, ] [package.dependencies] @@ -2133,75 +2162,35 @@ typing-extensions = ">=4.5.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "nonebot-adapter-discord" -version = "0.1.8" -description = "Discord adapter for nonebot2" +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" optional = false -python-versions = ">=3.9,<4.0" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +groups = ["dev"] files = [ - {file = "nonebot_adapter_discord-0.1.8-py3-none-any.whl", hash = "sha256:d063bf524f6a75c5c123f2d04227e0ec62c2433f56b28fb92fa5eb2aebef1c16"}, - {file = "nonebot_adapter_discord-0.1.8.tar.gz", hash = "sha256:5d3a7a8e0ab23b7ae84551b479c40c5d09733b15d09538d64765c5af54721781"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -nonebot2 = ">=2.2.1,<3.0.0" - [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "nonebot-adapter-dodo" -version = "0.1.4" -description = "Dodo adapter for nonebot2" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "nonebot_adapter_dodo-0.1.4-py3-none-any.whl", hash = "sha256:3bbe8ce1d686923dc7347d49e9e7164a93bc87e79626d6067e77b7c3d41d6861"}, - {file = "nonebot_adapter_dodo-0.1.4.tar.gz", hash = "sha256:21375ee712e97fe546ef24654dcb479f51e972335f13b4208af9ef53cc5fca29"}, -] - -[package.dependencies] -nonebot2 = ">=2.0.0,<3.0.0" - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "nonebot-adapter-kaiheila" -version = "0.3.4" -description = "kaiheila adapter for nonebot2" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "nonebot_adapter_kaiheila-0.3.4-py3-none-any.whl", hash = "sha256:a4cc0e43bd24e015b8312f1753705116274d5b7e9a68be266384dd413ca4f510"}, - {file = "nonebot_adapter_kaiheila-0.3.4.tar.gz", hash = "sha256:1fea823e5bc2bb5dc8e56a4c10a8f6698dac6e4f77d4526768275fa0925340f2"}, -] - -[package.dependencies] -nonebot2 = ">=2.2.0,<3.0.0" -typing-extensions = ">=4.8.0,<5.0.0" - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nonebot-adapter-onebot" -version = "2.4.4" +version = "2.4.6" description = "OneBot(CQHTTP) adapter for nonebot2" optional = false python-versions = ">=3.9,<4.0" +groups = ["main"] files = [ - {file = "nonebot_adapter_onebot-2.4.4-py3-none-any.whl", hash = "sha256:4dceeec7332bb560652c764405e9dd350268303f69b7c0e92b7cfebe876e8d39"}, - {file = "nonebot_adapter_onebot-2.4.4.tar.gz", hash = "sha256:c8a3645f74a3e43c85f092fb670508c662c36831f019a15e4d74eaac686089f0"}, + {file = "nonebot_adapter_onebot-2.4.6-py3-none-any.whl", hash = "sha256:b1ec7023fd83d731f63b513217327a57d12893a261944934b9195f79173791ad"}, + {file = "nonebot_adapter_onebot-2.4.6.tar.gz", hash = "sha256:e33c93649ad11b320d8e9ff213635f29b23b4d0413c9158bd031c513c2f8f701"}, ] [package.dependencies] @@ -2213,62 +2202,66 @@ typing-extensions = ">=4.0.0,<5.0.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nonebot-plugin-alconna" -version = "0.51.1" +version = "0.54.2" description = "Alconna Adapter for Nonebot" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "nonebot_plugin_alconna-0.51.1-py3-none-any.whl", hash = "sha256:450a27afa9dcaedb6c82f649d57d42c4ca81596bf6accdf2e163f2dc9befc2c4"}, - {file = "nonebot_plugin_alconna-0.51.1.tar.gz", hash = "sha256:aaec8206adc9892e284d7ad12c8bb03b43586bbc145d439f0a40a055146ed176"}, + {file = "nonebot_plugin_alconna-0.54.2-py3-none-any.whl", hash = "sha256:ab9a1a5f0f8c9a30ba57a49bed5d3e9c3f761ea5954cbafb15bcd2aa9c7d5507"}, + {file = "nonebot_plugin_alconna-0.54.2.tar.gz", hash = "sha256:0216da3bc2e5f8b4c4c44c2701f8f0a536d35ea0db79e708cc2ecd002b57ace6"}, ] [package.dependencies] -arclet-alconna = ">=1.8.23" -arclet-alconna-tools = ">=0.7.9" +arclet-alconna = ">=1.8.35,<2.0" +arclet-alconna-tools = ">=0.7.10" importlib-metadata = ">=4.13.0" -nepattern = ">=0.7.4" +nepattern = ">=0.7.7,<1.0" nonebot-plugin-waiter = ">=0.6.0" nonebot2 = ">=2.3.0" -tarina = ">=0.5.5" +tarina = ">=0.6.8,<0.7" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nonebot-plugin-apscheduler" -version = "0.3.0" +version = "0.5.0" description = "APScheduler Support for NoneBot2" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.9,<4.0" +groups = ["main"] files = [ - {file = "nonebot_plugin_apscheduler-0.3.0-py3-none-any.whl", hash = "sha256:ec5e0267293fc9803e543c6086d3e109ac87bf6dccea5473d219cad826238aae"}, - {file = "nonebot_plugin_apscheduler-0.3.0.tar.gz", hash = "sha256:7c41cc1d49ea6af7c4518c72cd15f8c2f549071b8bc8bfc4b21fbdd0a4875cfd"}, + {file = "nonebot_plugin_apscheduler-0.5.0-py3-none-any.whl", hash = "sha256:8b99b5ee60c4bc195d4df2fd27dab3d6963691e3332f6cee31a06eb4277c307f"}, + {file = "nonebot_plugin_apscheduler-0.5.0.tar.gz", hash = "sha256:6c0230e99765f275dc83d6639ff33bd6f71203fa10cd1b8a204b0f95530cda86"}, ] [package.dependencies] apscheduler = ">=3.7.0,<4.0.0" -nonebot2 = ">=2.0.0,<3.0.0" +nonebot2 = ">=2.2.0,<3.0.0" +pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nonebot-plugin-htmlrender" -version = "0.3.3" +version = "0.6.0" description = "通过浏览器渲染图片" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ - {file = "nonebot_plugin_htmlrender-0.3.3-py3-none-any.whl", hash = "sha256:2ac871d345c94103aa630153e007caa6319b5f5468491347513d746ba98b70d7"}, - {file = "nonebot_plugin_htmlrender-0.3.3.tar.gz", hash = "sha256:ab46ecc6dbd102628af8f88437fdc24da11839487950d07d0c5fd8db0db98ae8"}, + {file = "nonebot_plugin_htmlrender-0.6.0-py3-none-any.whl", hash = "sha256:def395b3ced6c810172cee036c5062636402bb76d90c9cc3db4550f45a33a8db"}, + {file = "nonebot_plugin_htmlrender-0.6.0.tar.gz", hash = "sha256:d1b9475f02ff2341e1b79627486eaa257099954429f5ab6551ce2a23504cebe1"}, ] [package.dependencies] @@ -2276,15 +2269,15 @@ aiofiles = ">=0.8.0" jinja2 = ">=3.0.3" markdown = ">=3.3.6" nonebot2 = ">=2.2.0" -playwright = ">=1.17.2" -Pygments = ">=2.10.0" +playwright = ">=1.48.0" +pygments = ">=2.10.0" pymdown-extensions = ">=9.1" python-markdown-math = ">=0.8" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "nonebot-plugin-session" @@ -2292,6 +2285,7 @@ version = "0.2.3" description = "Nonebot2 会话信息提取与会话id定义" optional = false python-versions = ">=3.8,<4.0" +groups = ["main"] files = [ {file = "nonebot_plugin_session-0.2.3-py3-none-any.whl", hash = "sha256:5f652a0c082231c1cea72deb994a81e50f77ba532e14d30fdec09772f69079fd"}, {file = "nonebot_plugin_session-0.2.3.tar.gz", hash = "sha256:33af37400f5005927c4ff861e593774bedc314fba00cfe06f482e582d9f447b7"}, @@ -2304,65 +2298,70 @@ strenum = ">=0.4.8,<0.5.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "nonebot-plugin-userinfo" -version = "0.1.3" -description = "Nonebot2 用户信息获取插件" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "nonebot_plugin_userinfo-0.1.3-py3-none-any.whl", hash = "sha256:e20b22c81e86e81f7953560bd8ce0a54559a87ad615358c613b78cb5a4918191"}, - {file = "nonebot_plugin_userinfo-0.1.3.tar.gz", hash = "sha256:d0a4d64c612486df63cd16950446072f8dfd2063ea28f15d56305a585a6b0b6e"}, -] - -[package.dependencies] -cachetools = ">=5.0.0,<6.0.0" -emoji = ">=2.0.0,<3.0.0" -httpx = ">=0.20.0,<1.0.0" -nonebot2 = {version = ">=2.0.0,<3.0.0", extras = ["fastapi"]} -strenum = ">=0.4.8,<0.5.0" - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "nonebot-plugin-waiter" -version = "0.7.1" -description = "An alternative for got-and-reject in Nonebot" +name = "nonebot-plugin-uninfo" +version = "0.6.8" +description = "Universal Information Model for Nonebot2" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "nonebot_plugin_waiter-0.7.1-py3-none-any.whl", hash = "sha256:b9967cc7aeea0db86053ada20929841830aea60bb8c7da26d0483eefda75635c"}, - {file = "nonebot_plugin_waiter-0.7.1.tar.gz", hash = "sha256:8be2adc175e45ca794881e3df449302b8e6e045cd9bae97a809907f4200b4110"}, + {file = "nonebot_plugin_uninfo-0.6.8-py3-none-any.whl", hash = "sha256:0adc7e731885883bfcb873ec715c69cff75b878092884d28c7d6ff314940ad6b"}, + {file = "nonebot_plugin_uninfo-0.6.8.tar.gz", hash = "sha256:0a30b500b1172fa15cc175b370c7a5935eb2f0515d188a2c73bf8e8ed7ae81d1"}, ] [package.dependencies] +importlib-metadata = ">=4.13.0" nonebot2 = ">=2.3.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "nonebot2" -version = "2.3.2" -description = "An asynchronous python bot framework." +name = "nonebot-plugin-waiter" +version = "0.8.1" +description = "An alternative for got-and-reject in Nonebot" optional = false -python-versions = ">=3.9,<4.0" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "nonebot2-2.3.2-py3-none-any.whl", hash = "sha256:c51aa3c1f23d8062ce6d13c8423dcb9a8bf0c44f21687916095f825da79a9a55"}, - {file = "nonebot2-2.3.2.tar.gz", hash = "sha256:af52e27e03e7fe147f2b642151eec81f264d058efe53b974eb08b5d90177cd14"}, + {file = "nonebot_plugin_waiter-0.8.1-py3-none-any.whl", hash = "sha256:3e1afc8f134496d3a4ecefd9c3a2a98d6ef28a5318268cb22b99a0ef61a44080"}, + {file = "nonebot_plugin_waiter-0.8.1.tar.gz", hash = "sha256:5e54213dfea1fd8a1e20dbe6d93b7881f35cbeedf80005148cdc39c1fd2ccc0f"}, ] [package.dependencies] +nonebot2 = ">=2.3.0" + +[package.extras] +unimsg = ["nonebot-plugin-alconna (>=0.52.2)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "nonebot2" +version = "2.4.1" +description = "An asynchronous python bot framework." +optional = false +python-versions = ">=3.9,<4.0" +groups = ["main", "dev"] +files = [ + {file = "nonebot2-2.4.1-py3-none-any.whl", hash = "sha256:fec95f075efc89dbe9ce148618b413b02f46ba284200367749b035e794695111"}, + {file = "nonebot2-2.4.1.tar.gz", hash = "sha256:8fea364318501ed79721403a8ecd76587bc884d58c356260f691a8bbda9b05e6"}, +] + +[package.dependencies] +anyio = ">=4.4.0,<5.0.0" +exceptiongroup = ">=1.2.2,<2.0.0" fastapi = {version = ">=0.93.0,<1.0.0", optional = true, markers = "extra == \"fastapi\" or extra == \"all\""} loguru = ">=0.6.0,<1.0.0" -pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" +pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<2.10.0 || >2.10.0,<2.10.1 || >2.10.1,<3.0.0" pygtrie = ">=2.4.1,<3.0.0" python-dotenv = ">=0.21.0,<2.0.0" tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} @@ -2371,17 +2370,41 @@ uvicorn = {version = ">=0.20.0,<1.0.0", extras = ["standard"], optional = true, yarl = ">=1.7.2,<2.0.0" [package.extras] -aiohttp = ["aiohttp[speedups] (>=3.9.0b0,<4.0.0)"] -all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.9.0b0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"] +aiohttp = ["aiohttp[speedups] (>=3.11.0,<4.0.0)"] +all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.11.0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.26.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"] fastapi = ["fastapi (>=0.93.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] -httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"] +httpx = ["httpx[http2] (>=0.26.0,<1.0.0)"] quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] websockets = ["websockets (>=10.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "nonebug" +version = "0.4.3" +description = "nonebot2 test framework" +optional = false +python-versions = ">=3.9,<4.0" +groups = ["dev"] +files = [ + {file = "nonebug-0.4.3-py3-none-any.whl", hash = "sha256:eb9b2c8ab3d45459a4f00ebdaae90729e9e9628575c0685fca4c871dd4cfd425"}, + {file = "nonebug-0.4.3.tar.gz", hash = "sha256:e9592d2c7a42b76f4a336f98726cba92e1300f6bab155c8822e865919786f10c"}, +] + +[package.dependencies] +asgiref = ">=3.4.0,<4.0.0" +async-asgi-testclient = ">=1.4.8,<2.0.0" +nonebot2 = ">=2.2.0,<3.0.0" +pytest = ">=7.0.0,<9.0.0" +typing-extensions = ">=4.0.0,<5.0.0" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "noneprompt" @@ -2389,6 +2412,7 @@ version = "0.1.9" description = "Prompt toolkit for console interaction" optional = false python-versions = ">=3.8,<4.0" +groups = ["main"] files = [ {file = "noneprompt-0.1.9-py3-none-any.whl", hash = "sha256:a54f1e6a19a3da2dedf7f365f80420e9ae49326a0ffe60a8a9c7afdee6b6eeb3"}, {file = "noneprompt-0.1.9.tar.gz", hash = "sha256:338b8bb89a8d22ef35f1dedb3aa7c1b228cf139973bdc43c5ffc3eef64457db9"}, @@ -2400,257 +2424,290 @@ prompt-toolkit = ">=3.0.19,<4.0.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "numpy" -version = "2.0.1" +version = "2.2.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, - {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, - {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, - {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, - {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, - {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, - {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, - {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, - {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, - {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"}, + {file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"}, + {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"}, + {file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"}, + {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"}, + {file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"}, + {file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"}, + {file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"}, + {file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"}, + {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"}, + {file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"}, + {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"}, + {file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"}, + {file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"}, + {file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"}, + {file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"}, + {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"}, + {file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"}, + {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"}, + {file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"}, + {file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"}, + {file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"}, + {file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"}, + {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"}, + {file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"}, + {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"}, + {file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"}, + {file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"}, + {file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"}, + {file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"}, + {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"}, + {file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"}, + {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"}, + {file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"}, + {file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"}, + {file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"}, + {file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"}, + {file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "opencv-python" -version = "4.10.0.84" -description = "Wrapper package for OpenCV python bindings." -optional = false -python-versions = ">=3.6" -files = [ - {file = "opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236"}, - {file = "opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, - {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pillow" -version = "9.5.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, - {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, - {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, - {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, - {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, - {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, - {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, - {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, - {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, - {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, - {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, - {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, - {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, - {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, - {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, - {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, - {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, - {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, - {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, - {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, - {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, - {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, - {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, - {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, - {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, - {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, - {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, - {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, - {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, - {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, - {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "playwright" -version = "1.45.1" +version = "1.50.0" description = "A high-level API to automate web browsers" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "playwright-1.45.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:360607e37c00cdf97c74317f010e106ac4671aeaec6a192431dd71a30941da9d"}, - {file = "playwright-1.45.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:20adc2abf164c5e8969f9066011b152e12c210549edec78cd05bd0e9cf4135b7"}, - {file = "playwright-1.45.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:5f047cdc6accf4c7084dfc7587a2a5ef790cddc44cbb111e471293c5a91119db"}, - {file = "playwright-1.45.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:f06f6659abe0abf263e5f6661d379fbf85c112745dd31d82332ceae914f58df7"}, - {file = "playwright-1.45.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87dc3b3d17e12c68830c29b7fdf5e93315221bbb4c6090e83e967e154e2c1828"}, - {file = "playwright-1.45.1-py3-none-win32.whl", hash = "sha256:2b8f517886ef1e2151982f6e7be84be3ef7d8135bdcf8ee705b4e4e99566e866"}, - {file = "playwright-1.45.1-py3-none-win_amd64.whl", hash = "sha256:0d236cf427784e77de352ba1b7d700693c5fe455b8e5f627f6d84ad5b84b5bf5"}, + {file = "playwright-1.50.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f36d754a6c5bd9bf7f14e8f57a2aea6fd08f39ca4c8476481b9c83e299531148"}, + {file = "playwright-1.50.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40f274384591dfd27f2b014596250b2250c843ed1f7f4ef5d2960ecb91b4961e"}, + {file = "playwright-1.50.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9922ef9bcd316995f01e220acffd2d37a463b4ad10fd73e388add03841dfa230"}, + {file = "playwright-1.50.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fc628c492d12b13d1f347137b2ac6c04f98197ff0985ef0403a9a9ee0d39131"}, + {file = "playwright-1.50.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcff35f72db2689a79007aee78f1b0621a22e6e3d6c1f58aaa9ac805bf4497c"}, + {file = "playwright-1.50.0-py3-none-win32.whl", hash = "sha256:3b906f4d351260016a8c5cc1e003bb341651ae682f62213b50168ed581c7558a"}, + {file = "playwright-1.50.0-py3-none-win_amd64.whl", hash = "sha256:1859423da82de631704d5e3d88602d755462b0906824c1debe140979397d2e8d"}, ] [package.dependencies] -greenlet = "3.0.3" -pyee = "11.1.0" +greenlet = ">=3.1.1,<4.0.0" +pyee = ">=12,<13" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pre-commit" +version = "4.1.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"}, + {file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "proces" @@ -2658,6 +2715,7 @@ version = "0.1.7" description = "text preprocess." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "proces-0.1.7-py3-none-any.whl", hash = "sha256:308325bbc96877263f06e57e5e9c760c4b42cc722887ad60be6b18fc37d68762"}, {file = "proces-0.1.7.tar.gz", hash = "sha256:70a05d9e973dd685f7a9092c58be695a8181a411d63796c213232fd3fdc43775"}, @@ -2666,17 +2724,18 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.50" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" +groups = ["main"] files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, ] [package.dependencies] @@ -2685,32 +2744,130 @@ wcwidth = "*" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "protobuf" -version = "4.25.4" -description = "" +name = "propcache" +version = "0.2.1" +description = "Accelerated property cache" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"}, - {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"}, - {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"}, - {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"}, - {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"}, - {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"}, - {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"}, - {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"}, - {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"}, - {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"}, - {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"}, + {file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634"}, + {file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034"}, + {file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b"}, + {file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4"}, + {file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717"}, + {file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af"}, + {file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca"}, + {file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e"}, + {file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034"}, + {file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0"}, + {file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24"}, + {file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6"}, + {file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518"}, + {file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246"}, + {file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9"}, + {file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052"}, + {file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f"}, + {file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30"}, + {file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6"}, + {file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e"}, + {file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16"}, + {file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04"}, + {file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587"}, + {file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb"}, + {file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1"}, + {file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54"}, + {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "protobuf" +version = "4.25.6" +description = "" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "protobuf-4.25.6-cp310-abi3-win32.whl", hash = "sha256:61df6b5786e2b49fc0055f636c1e8f0aff263808bb724b95b164685ac1bcc13a"}, + {file = "protobuf-4.25.6-cp310-abi3-win_amd64.whl", hash = "sha256:b8f837bfb77513fe0e2f263250f423217a173b6d85135be4d81e96a4653bcd3c"}, + {file = "protobuf-4.25.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d4381f2417606d7e01750e2729fe6fbcda3f9883aa0c32b51d23012bded6c91"}, + {file = "protobuf-4.25.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5dd800da412ba7f6f26d2c08868a5023ce624e1fdb28bccca2dc957191e81fb5"}, + {file = "protobuf-4.25.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:4434ff8bb5576f9e0c78f47c41cdf3a152c0b44de475784cd3fd170aef16205a"}, + {file = "protobuf-4.25.6-cp38-cp38-win32.whl", hash = "sha256:8bad0f9e8f83c1fbfcc34e573352b17dfce7d0519512df8519994168dc015d7d"}, + {file = "protobuf-4.25.6-cp38-cp38-win_amd64.whl", hash = "sha256:b6905b68cde3b8243a198268bb46fbec42b3455c88b6b02fb2529d2c306d18fc"}, + {file = "protobuf-4.25.6-cp39-cp39-win32.whl", hash = "sha256:3f3b0b39db04b509859361ac9bca65a265fe9342e6b9406eda58029f5b1d10b2"}, + {file = "protobuf-4.25.6-cp39-cp39-win_amd64.whl", hash = "sha256:6ef2045f89d4ad8d95fd43cd84621487832a61d15b49500e4c1350e8a0ef96be"}, + {file = "protobuf-4.25.6-py3-none-any.whl", hash = "sha256:07972021c8e30b870cfc0863409d033af940213e0e7f64e27fe017b929d2c9f7"}, + {file = "protobuf-4.25.6.tar.gz", hash = "sha256:f8cfbae7c5afd0d0eaccbe73267339bff605a2315860bb1ba08eb66670a9a91f"}, +] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "psutil" @@ -2718,6 +2875,7 @@ version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main"] files = [ {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, @@ -2743,23 +2901,41 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] -name = "pyasn1" -version = "0.6.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +name = "py-cpuinfo" +version = "9.0.0" +description = "Get CPU info with pure Python" optional = false -python-versions = ">=3.8" +python-versions = "*" +groups = ["main"] files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, + {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, + {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "pyasn1" +version = "0.6.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, +] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "pycparser" @@ -2767,6 +2943,8 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2775,81 +2953,162 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pydantic" -version = "1.10.17" -description = "Data validation and settings management using python type hints" +version = "2.10.6" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, - {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, - {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, - {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, - {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, - {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, - {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, - {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, - {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, - {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, - {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "pydantic-core" +version = "2.27.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "pyee" -version = "11.1.0" +version = "12.1.1" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "pyee-11.1.0-py3-none-any.whl", hash = "sha256:5d346a7d0f861a4b2e6c47960295bd895f816725b27d656181947346be98d7c1"}, - {file = "pyee-11.1.0.tar.gz", hash = "sha256:b53af98f6990c810edd9b56b87791021a8f54fd13db4edd1142438d44ba2263f"}, + {file = "pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef"}, + {file = "pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3"}, ] [package.dependencies] @@ -2861,7 +3120,7 @@ dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", " [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pyfiglet" @@ -2869,6 +3128,7 @@ version = "1.0.2" description = "Pure-python FIGlet implementation" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pyfiglet-1.0.2-py3-none-any.whl", hash = "sha256:889b351d79c99e50a3f619c8f8e6ffdb27fd8c939fc43ecbd7559bd57d5f93ea"}, {file = "pyfiglet-1.0.2.tar.gz", hash = "sha256:758788018ab8faaddc0984e1ea05ff330d3c64be663c513cc1f105f6a3066dab"}, @@ -2877,17 +3137,18 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -2896,7 +3157,7 @@ windows-terminal = ["colorama (>=0.4.6)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pygtrie" @@ -2904,6 +3165,7 @@ version = "2.5.0" description = "A pure Python trie data structure implementation." optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"}, {file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"}, @@ -2912,17 +3174,18 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pymdown-extensions" -version = "10.9" +version = "10.14.3" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "pymdown_extensions-10.9-py3-none-any.whl", hash = "sha256:d323f7e90d83c86113ee78f3fe62fc9dee5f56b54d912660703ea1816fed5626"}, - {file = "pymdown_extensions-10.9.tar.gz", hash = "sha256:6ff740bcd99ec4172a938970d42b96128bdc9d4b9bcad72494f29921dc69b753"}, + {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, + {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, ] [package.dependencies] @@ -2930,12 +3193,12 @@ markdown = ">=3.6" pyyaml = "*" [package.extras] -extra = ["pygments (>=2.12)"] +extra = ["pygments (>=2.19.1)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pypika-tortoise" @@ -2943,6 +3206,7 @@ version = "0.1.6" description = "Forked from pypika and streamline just for tortoise-orm" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "pypika-tortoise-0.1.6.tar.gz", hash = "sha256:d802868f479a708e3263724c7b5719a26ad79399b2a70cea065f4a4cadbebf36"}, {file = "pypika_tortoise-0.1.6-py3-none-any.whl", hash = "sha256:2d68bbb7e377673743cff42aa1059f3a80228d411fbcae591e4465e173109fd8"}, @@ -2951,7 +3215,7 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pypinyin" @@ -2959,6 +3223,7 @@ version = "0.51.0" description = "汉字拼音转换模块/工具." optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" +groups = ["main"] files = [ {file = "pypinyin-0.51.0-py2.py3-none-any.whl", hash = "sha256:ae8878f08fee15d0c5c11053a737e68a4158c22c63dc632b4de060af5c95bf84"}, {file = "pypinyin-0.51.0.tar.gz", hash = "sha256:cede34fc35a79ef6c799f161e2c280e7b6755ee072fb741cae5ce2a60c4ae0c5"}, @@ -2967,7 +3232,132 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "pytest" +version = "8.3.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pytest-asyncio" +version = "0.25.3" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, + {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "python-dateutil" @@ -2975,6 +3365,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2986,7 +3377,7 @@ six = ">=1.5" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "python-dotenv" @@ -2994,6 +3385,7 @@ version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, @@ -3005,7 +3397,7 @@ cli = ["click (>=5.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "python-jose" @@ -3013,6 +3405,7 @@ version = "3.3.0" description = "JOSE implementation in Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, @@ -3032,7 +3425,7 @@ pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "python-markdown-math" @@ -3040,6 +3433,7 @@ version = "0.8" description = "Math extension for Python-Markdown" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "python-markdown-math-0.8.tar.gz", hash = "sha256:8564212af679fc18d53f38681f16080fcd3d186073f23825c7ce86fadd3e3635"}, {file = "python_markdown_math-0.8-py3-none-any.whl", hash = "sha256:c685249d84b5b697e9114d7beb352bd8ca2e07fd268fd4057ffca888c14641e5"}, @@ -3051,7 +3445,7 @@ Markdown = ">=3.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "python-multipart" @@ -3059,6 +3453,7 @@ version = "0.0.9" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, @@ -3070,7 +3465,7 @@ dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatc [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "python-slugify" @@ -3078,6 +3473,7 @@ version = "8.0.4" description = "A Python slugify application that also handles Unicode" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, @@ -3092,73 +3488,83 @@ unidecode = ["Unidecode (>=1.1.1)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pytz" -version = "2024.1" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pywavelets" -version = "1.6.0" +version = "1.8.0" description = "PyWavelets, wavelet transform module" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "pywavelets-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ddc1ff5ad706313d930f857f9656f565dfb81b85bbe58a9db16ad8fa7d1537c5"}, - {file = "pywavelets-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:78feab4e0c25fa32034b6b64cb854c6ce15663b4f0ffb25d8f0ee58915300f9b"}, - {file = "pywavelets-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be36f08efe9bc3abf40cf40cd2ee0aa0db26e4894e13ce5ac178442864161e8c"}, - {file = "pywavelets-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0595c51472c9c5724fe087cb73e2797053fd25c788d6553fdad6ff61abc60e91"}, - {file = "pywavelets-1.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:058a750477dde633ac53b8806f835af3559d52db6532fb2b93c1f4b5441365b8"}, - {file = "pywavelets-1.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:538795d9c4181152b414285b5a7f72ac52581ecdcdce74b6cca3fa0b8a5ab0aa"}, - {file = "pywavelets-1.6.0-cp310-cp310-win32.whl", hash = "sha256:47de024ba4f9df97e98b5f540340e1a9edd82d2c477450bef8c9b5381487128e"}, - {file = "pywavelets-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:e2c44760c0906ddf2176920a2613287f6eea947f166ce7eee9546081b06a6835"}, - {file = "pywavelets-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d91aaaf6de53b758bcdc96c81cdb5a8607758602be49f691188c0e108cf1e738"}, - {file = "pywavelets-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b5302edb6d1d1ff6636d37c9ff29c4892f2a3648d736cc1df01f3f36e25c8cf"}, - {file = "pywavelets-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e655446e37a3c87213d5c6386b86f65c4d61736b4432d720171e7dd6523d6a"}, - {file = "pywavelets-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ec7d69b746a0eaa327b829a3252a63619f2345e263177be5dd9bf30d7933c8d"}, - {file = "pywavelets-1.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97ea9613bd6b7108ebb44b709060adc7e2d5fac73be7152342bdd5513d75f84e"}, - {file = "pywavelets-1.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:48b3813c6d1a7a8194f37dbb5dbbdf2fe1112152c91445ea2e54f64ff6350c36"}, - {file = "pywavelets-1.6.0-cp311-cp311-win32.whl", hash = "sha256:4ffb484d096a5eb10af7121e0203546a03e1369328df321a33ef91f67bac40cf"}, - {file = "pywavelets-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:274bc47b289585383aa65519b3fcae5b4dee5e31db3d4198d4fad701a70e59f7"}, - {file = "pywavelets-1.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d6ec113386a432e04103f95e351d2657b42145bd1e1ed26513423391bcb5f011"}, - {file = "pywavelets-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab652112d3932d21f020e281e06926a751354c2b5629fb716f5eb9d0104b84e5"}, - {file = "pywavelets-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47b0314a22616c5f3f08760f0e00b4a15b7c7dadca5e39bb701cf7869a4207c5"}, - {file = "pywavelets-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:138471513bc0a4cd2ddc4e50c7ec04e3468c268e101a0d02f698f6aedd1d5e79"}, - {file = "pywavelets-1.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67936491ae3e5f957c428e34fdaed21f131535b8d60c7c729a1b539ce8864837"}, - {file = "pywavelets-1.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dd798cee3d28fb3d32a26a00d9831a20bf316c36d685e4ced01b4e4a8f36f5ce"}, - {file = "pywavelets-1.6.0-cp312-cp312-win32.whl", hash = "sha256:e772f7f0c16bfc3be8ac3cd10d29a9920bb7a39781358856223c491b899e6e79"}, - {file = "pywavelets-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:4ef15a63a72afa67ae9f4f3b06c95c5382730fb3075e668d49a880e65f2f089c"}, - {file = "pywavelets-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:627df378e63e9c789b6f2e7060cb4264ebae6f6b0efc1da287a2c060de454a1f"}, - {file = "pywavelets-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a413b51dc19e05243fe0b0864a8e8a16b5ca9bf2e4713da00a95b1b5747a5367"}, - {file = "pywavelets-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be615c6c1873e189c265d4a76d1751ec49b17e29725e6dd2e9c74f1868f590b7"}, - {file = "pywavelets-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4021ef69ec9f3862f66580fc4417be728bd78722914394594b48212fd1fcaf21"}, - {file = "pywavelets-1.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8fbf7b61b28b5457693c034e58a01622756d1fd60a80ae13ac5888b1d3e57e80"}, - {file = "pywavelets-1.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f58ddbb0a6cd243928876edfc463b990763a24fb94498607d6fea690e32cca4c"}, - {file = "pywavelets-1.6.0-cp39-cp39-win32.whl", hash = "sha256:42a22e68e345b6de7d387ef752111ab4530c98048d2b4bdac8ceefb078b4ead6"}, - {file = "pywavelets-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:32198de321892743c1a3d1957fe1cd8a8ecc078bfbba6b8f3982518e897271d7"}, - {file = "pywavelets-1.6.0.tar.gz", hash = "sha256:ea027c70977122c5fc27b2510f0a0d9528f9c3df6ea3e4c577ca55fd00325a5b"}, + {file = "pywavelets-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f5c86fcb203c8e61d1f3d4afbfc08d626c64e4e3708207315577264c724632bf"}, + {file = "pywavelets-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafb5fa126277e1690c3d6329287122fc08e4d25a262ce126e3d81b1f5709308"}, + {file = "pywavelets-1.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec23dfe6d5a3f4312b12456b8c546aa90a11c1138e425a885987505f0658ae0"}, + {file = "pywavelets-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:880a0197e9fa108939af50a95e97c1bf9b7d3e148e0fad92ea60a9ed8c8947c0"}, + {file = "pywavelets-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bfa833d08b60d0bf53a7939fbbf3d98015dd34efe89cbe4e53ced880d085fc1"}, + {file = "pywavelets-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e10c3fc7f4a796e94da4bca9871be2186a7bb7a3b3536a0ca9376d84263140f0"}, + {file = "pywavelets-1.8.0-cp310-cp310-win32.whl", hash = "sha256:31baf4be6940fde72cc85663154360857ac1b93c251822deaf72bb804da95031"}, + {file = "pywavelets-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:560c39f1ff8cb37f8b8ea4b7b6eb8a14f6926c11f5cf8c09f013a58f895ed5bc"}, + {file = "pywavelets-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e8dd5be4faed994581a8a4b3c0169be20567a9346e523f0b57f903c8f6722bce"}, + {file = "pywavelets-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d8abaf7c120b151ef309c9ff57e0a44ba9febf49045056dbc1577526ecec6c8"}, + {file = "pywavelets-1.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b43a4c58707b1e8d941bec7f1d83e67c482278575ff0db3189d5c0dfae23a57"}, + {file = "pywavelets-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1aad0b97714e3079a2bfe48e4fb8ccd60778d0427e9ee5e0a9ff922e6c61e4"}, + {file = "pywavelets-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0e1db96dcf3ce08156859df8b359e9ff66fa15061a1b90e70e020bf4cd077a0"}, + {file = "pywavelets-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e62c8fb52ab0e8ff212fff9acae681a8f12d68b76c36fe24cc48809d5b6825ba"}, + {file = "pywavelets-1.8.0-cp311-cp311-win32.whl", hash = "sha256:bf327528d10de471b04bb725c4e10677fac5a49e13d41bf0d0b3a1f6d7097abf"}, + {file = "pywavelets-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3814d354dd109e244ffaac3d480d29a5202212fe24570c920268237c8d276f95"}, + {file = "pywavelets-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3f431c9e2aff1a2240765eff5e804975d0fcc24c82d6f3d4271243f228e5963b"}, + {file = "pywavelets-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e39b0e2314e928cb850ee89b9042733a10ea044176a495a54dc84d2c98407a51"}, + {file = "pywavelets-1.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cae701117f5c7244b7c8d48b9e92a0289637cdc02a9c205e8be83361f0c11fae"}, + {file = "pywavelets-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649936baee933e80083788e0adc4d8bc2da7cdd8b10464d3b113475be2cc5308"}, + {file = "pywavelets-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c68e9d072c536bc646e8bdce443bb1826eeb9aa21b2cb2479a43954dea692a3"}, + {file = "pywavelets-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:63f67fa2ee1610445de64f746fb9c1df31980ad13d896ea2331fc3755f49b3ae"}, + {file = "pywavelets-1.8.0-cp312-cp312-win32.whl", hash = "sha256:4b3c2ab669c91e3474fd63294355487b7dd23f0b51d32f811327ddf3546f4f3d"}, + {file = "pywavelets-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:810a23a631da596fef7196ddec49b345b1aab13525bb58547eeebe1769edbbc1"}, + {file = "pywavelets-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:441ba45c8dff8c6916dbe706958d0d7f91da675695ca0c0d75e483f6f52d0a12"}, + {file = "pywavelets-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:24bb282bab09349d9d128ed0536fa50fff5c2147891971a69c2c36155dfeeeac"}, + {file = "pywavelets-1.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:426ff3799446cb4da1db04c2084e6e58edfe24225596805665fd39c14f53dece"}, + {file = "pywavelets-1.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0607a9c085b8285bc0d04e33d461a6c80f8c325389221ffb1a45141861138e"}, + {file = "pywavelets-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d31c36a39110e8fcc7b1a4a11cfed7d22b610c285d3e7f4fe73ec777aa49fa39"}, + {file = "pywavelets-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa7c68ed1e5bab23b1bafe60ccbcf709b878652d03de59e961baefa5210fcd0a"}, + {file = "pywavelets-1.8.0-cp313-cp313-win32.whl", hash = "sha256:2c6b359b55d713ef683e9da1529181b865a80d759881ceb9adc1c5742e4da4d8"}, + {file = "pywavelets-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dbebcfd55ea8a85b7fc8802d411e75337170422abf6e96019d7e46c394e80e5"}, + {file = "pywavelets-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2e1c79784bebeafd3715c1bea6621daa2e2e6ed37b687719322e2078fb35bb70"}, + {file = "pywavelets-1.8.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f489380c95013cc8fb3ef338f6d8c1a907125db453cc4dc739e2cca06fcd8b6"}, + {file = "pywavelets-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:06786201a91b5e74540f4f3c115c49a29190de2eb424823abbd3a1fd75ea3e28"}, + {file = "pywavelets-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:f2877fb7b58c94211257dcf364b204d6ed259146fc87d5a90bf9d93c97af6226"}, + {file = "pywavelets-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ec5d723c3335ff8aa630fd4b14097077f12cc02893c91cafd60dd7b1730e780f"}, + {file = "pywavelets-1.8.0.tar.gz", hash = "sha256:f3800245754840adc143cbc29534a1b8fc4b8cff6e9d403326bd52b7bb5c35aa"}, ] [package.dependencies] -numpy = ">=1.22.4,<3" +numpy = ">=1.23,<3" + +[package.extras] +optional = ["scipy (>=1.9)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "pyyaml" @@ -3166,6 +3572,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -3225,100 +3632,116 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "regex" -version = "2024.7.24" +version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, - {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, - {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, - {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, - {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, - {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, - {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, - {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, - {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, - {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, - {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, - {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, - {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, - {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, - {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, - {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, - {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, - {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, - {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, - {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, - {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, - {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, - {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, - {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, - {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, - {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, - {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "requests" @@ -3326,6 +3749,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -3344,7 +3768,27 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "respx" +version = "0.21.1" +description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "respx-0.21.1-py2.py3-none-any.whl", hash = "sha256:05f45de23f0c785862a2c92a3e173916e8ca88e4caad715dd5f68584d6053c20"}, + {file = "respx-0.21.1.tar.gz", hash = "sha256:0bd7fe21bfaa52106caa1223ce61224cf30786985f17c63c5d71eff0307ee8af"}, +] + +[package.dependencies] +httpx = ">=0.21.0" + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "retrying" @@ -3352,6 +3796,7 @@ version = "1.3.4" description = "Retrying" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, @@ -3363,7 +3808,7 @@ six = ">=1.7.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "rfc3986" @@ -3371,6 +3816,7 @@ version = "1.5.0" description = "Validating URI References per RFC 3986" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, @@ -3385,22 +3831,24 @@ idna2008 = ["idna"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "rich" -version = "13.7.1" +version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" +groups = ["main"] files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -3408,7 +3856,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "rsa" @@ -3416,6 +3864,7 @@ version = "4.9" description = "Pure-Python RSA implementation" optional = false python-versions = ">=3.6,<4" +groups = ["main"] files = [ {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, @@ -3427,17 +3876,18 @@ pyasn1 = ">=0.1.3" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "ruamel-yaml" -version = "0.18.6" +version = "0.18.10" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, - {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, + {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"}, + {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"}, ] [package.dependencies] @@ -3450,139 +3900,160 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "ruamel-yaml-clib" -version = "0.2.8" +version = "0.2.12" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" files = [ - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, - {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, - {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, + {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "ruff" +version = "0.8.6" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"}, + {file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"}, + {file = "ruff-0.8.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf"}, + {file = "ruff-0.8.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a"}, + {file = "ruff-0.8.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76"}, + {file = "ruff-0.8.6-py3-none-win32.whl", hash = "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764"}, + {file = "ruff-0.8.6-py3-none-win_amd64.whl", hash = "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905"}, + {file = "ruff-0.8.6-py3-none-win_arm64.whl", hash = "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162"}, + {file = "ruff-0.8.6.tar.gz", hash = "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5"}, +] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "scipy" -version = "1.14.0" +version = "1.15.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, - {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, - {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, - {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, - {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, - {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, - {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, - {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c64ded12dcab08afff9e805a67ff4480f5e69993310e093434b10e85dc9d43e1"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5b190b935e7db569960b48840e5bef71dc513314cc4e79a1b7d14664f57fd4ff"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4b17d4220df99bacb63065c76b0d1126d82bbf00167d1730019d2a30d6ae01ea"}, + {file = "scipy-1.15.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:63b9b6cd0333d0eb1a49de6f834e8aeaefe438df8f6372352084535ad095219e"}, + {file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f151e9fb60fbf8e52426132f473221a49362091ce7a5e72f8aa41f8e0da4f25"}, + {file = "scipy-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e10b1dd56ce92fba3e786007322542361984f8463c6d37f6f25935a5a6ef52"}, + {file = "scipy-1.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5dff14e75cdbcf07cdaa1c7707db6017d130f0af9ac41f6ce443a93318d6c6e0"}, + {file = "scipy-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:f82fcf4e5b377f819542fbc8541f7b5fbcf1c0017d0df0bc22c781bf60abc4d8"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5bd8d27d44e2c13d0c1124e6a556454f52cd3f704742985f6b09e75e163d20d2"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be3deeb32844c27599347faa077b359584ba96664c5c79d71a354b80a0ad0ce0"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:5eb0ca35d4b08e95da99a9f9c400dc9f6c21c424298a0ba876fdc69c7afacedf"}, + {file = "scipy-1.15.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:74bb864ff7640dea310a1377d8567dc2cb7599c26a79ca852fc184cc851954ac"}, + {file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:667f950bf8b7c3a23b4199db24cb9bf7512e27e86d0e3813f015b74ec2c6e3df"}, + {file = "scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395be70220d1189756068b3173853029a013d8c8dd5fd3d1361d505b2aa58fa7"}, + {file = "scipy-1.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce3a000cd28b4430426db2ca44d96636f701ed12e2b3ca1f2b1dd7abdd84b39a"}, + {file = "scipy-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fe1d95944f9cf6ba77aa28b82dd6bb2a5b52f2026beb39ecf05304b8392864b"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09aa9d90f3500ea4c9b393ee96f96b0ccb27f2f350d09a47f533293c78ea776"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0ac102ce99934b162914b1e4a6b94ca7da0f4058b6d6fd65b0cef330c0f3346f"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:09c52320c42d7f5c7748b69e9f0389266fd4f82cf34c38485c14ee976cb8cb04"}, + {file = "scipy-1.15.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:cdde8414154054763b42b74fe8ce89d7f3d17a7ac5dd77204f0e142cdc9239e9"}, + {file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c9d8fc81d6a3b6844235e6fd175ee1d4c060163905a2becce8e74cb0d7554ce"}, + {file = "scipy-1.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fb57b30f0017d4afa5fe5f5b150b8f807618819287c21cbe51130de7ccdaed2"}, + {file = "scipy-1.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:491d57fe89927fa1aafbe260f4cfa5ffa20ab9f1435025045a5315006a91b8f5"}, + {file = "scipy-1.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:900f3fa3db87257510f011c292a5779eb627043dd89731b9c461cd16ef76ab3d"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:100193bb72fbff37dbd0bf14322314fc7cbe08b7ff3137f11a34d06dc0ee6b85"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:2114a08daec64980e4b4cbdf5bee90935af66d750146b1d2feb0d3ac30613692"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6b3e71893c6687fc5e29208d518900c24ea372a862854c9888368c0b267387ab"}, + {file = "scipy-1.15.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:837299eec3d19b7e042923448d17d95a86e43941104d33f00da7e31a0f715d3c"}, + {file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82add84e8a9fb12af5c2c1a3a3f1cb51849d27a580cb9e6bd66226195142be6e"}, + {file = "scipy-1.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070d10654f0cb6abd295bc96c12656f948e623ec5f9a4eab0ddb1466c000716e"}, + {file = "scipy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55cc79ce4085c702ac31e49b1e69b27ef41111f22beafb9b49fea67142b696c4"}, + {file = "scipy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:c352c1b6d7cac452534517e022f8f7b8d139cd9f27e6fbd9f3cbd0bfd39f5bef"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0458839c9f873062db69a03de9a9765ae2e694352c76a16be44f93ea45c28d2b"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:af0b61c1de46d0565b4b39c6417373304c1d4f5220004058bdad3061c9fa8a95"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:71ba9a76c2390eca6e359be81a3e879614af3a71dfdabb96d1d7ab33da6f2364"}, + {file = "scipy-1.15.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14eaa373c89eaf553be73c3affb11ec6c37493b7eaaf31cf9ac5dffae700c2e0"}, + {file = "scipy-1.15.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f735bc41bd1c792c96bc426dece66c8723283695f02df61dcc4d0a707a42fc54"}, + {file = "scipy-1.15.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2722a021a7929d21168830790202a75dbb20b468a8133c74a2c0230c72626b6c"}, + {file = "scipy-1.15.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc7136626261ac1ed988dca56cfc4ab5180f75e0ee52e58f1e6aa74b5f3eacd5"}, + {file = "scipy-1.15.1.tar.gz", hash = "sha256:033a75ddad1463970c96a88063a1df87ccfddd526437136b6ee81ff0312ebdf6"}, ] [package.dependencies] -numpy = ">=1.23.5,<2.3" +numpy = ">=1.23.5,<2.5" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" - -[[package]] -name = "setuptools" -version = "72.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, - {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, -] - -[package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - -[package.source] -type = "legacy" -url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "sgmllib3k" @@ -3590,6 +4061,7 @@ version = "1.0.0" description = "Py3k port of sgmllib." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9"}, ] @@ -3597,23 +4069,24 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "sniffio" @@ -3621,6 +4094,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -3629,45 +4103,47 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "starlette" -version = "0.37.2" +version = "0.45.3" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, - {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, + {file = "starlette-0.45.3-py3-none-any.whl", hash = "sha256:dfb6d332576f136ec740296c7e8bb8c8a7125044e7c6da30744718880cdd059d"}, + {file = "starlette-0.45.3.tar.gz", hash = "sha256:2cbcba2a75806f8a41c722141486f37c28e30a0921c5f6fe4346cb0dcee1302f"}, ] [package.dependencies] -anyio = ">=3.4.0,<5" +anyio = ">=3.6.2,<5" [package.extras] -full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "strenum" @@ -3675,6 +4151,7 @@ version = "0.4.15" description = "An Enum that inherits from str." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"}, {file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"}, @@ -3688,62 +4165,93 @@ test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "tarina" -version = "0.5.5" +version = "0.6.8" description = "A collection of common utils for Arclet" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "tarina-0.5.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fda200701a81ed48e4303ccff10b5d680a7ad3d1772a6830f32995fe04459d6e"}, - {file = "tarina-0.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ffe373da5f9e35179b96e233731e8a7bb83fe6bf8866753f468db53b3ed22e"}, - {file = "tarina-0.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb7474ba9f9d55dc29df9d317c12fdc870ba10582b0c5ce36550e237881c9ea6"}, - {file = "tarina-0.5.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a392ac4d4b94a9a51b7540d8194605be621a129147dc874933a524911a09c94e"}, - {file = "tarina-0.5.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cc131ecab68d7ec31a12dfb8f0ab0638729a9b866043a79b66dcf7022000652"}, - {file = "tarina-0.5.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:724a3d33ed7c48f68af7fc583aa21abff2cd1b60d0c51d3ba043683d715717f8"}, - {file = "tarina-0.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b04897665d96ebd55461c0876407c3e569008ba8efee4d4342bad47c32b64b0f"}, - {file = "tarina-0.5.5-cp310-cp310-win32.whl", hash = "sha256:f58c9eaa087af597cfd7e2885073c9dc93a3f93ba3f6957d55a9dacbcc1270ee"}, - {file = "tarina-0.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:b7dc4a5e0779fd4ee023abf445c2f801069a5861133c3ad04a5e055d5d5071fb"}, - {file = "tarina-0.5.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ffb4ed6bd241809fd76b82bc7df857413cbc4a73a2ac8397374b79cb6e85e9b"}, - {file = "tarina-0.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f5551815a970cd22d6d609a8769eac3e8b499e54ac5283e01169727f9ce0edd0"}, - {file = "tarina-0.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e2c18bcb1a3c59e45dc0fe39880b41d7e4fb5d742ef98a88fb4621aea9da02f"}, - {file = "tarina-0.5.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2db5c4bc285d73bec00b159dde6ec41b74d14371eb6da29d8b14a382e370567e"}, - {file = "tarina-0.5.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74923bc3d6884639e102a6a35bffda9578d934a23c4eb3f2d835e718ac75cee"}, - {file = "tarina-0.5.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e55686cff98c91ed4982226163ac5daeaf85510b4acab0c3d75331e255fbdce0"}, - {file = "tarina-0.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:50572901cd69983cfdc9d5a5823d17c49755f9e071eb287e091df014beaf6e73"}, - {file = "tarina-0.5.5-cp311-cp311-win32.whl", hash = "sha256:9d0a20f8b084af361fab7b070917edad611ede38014bab2cfc4024599586ade0"}, - {file = "tarina-0.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:8e740532d5a9346079c55613adfb77895f596a9c57e46c06d7d6c03640bd4f38"}, - {file = "tarina-0.5.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1bab4762a24d9fcd8eacae4376c8fa2d4a96e1a3c5aadbeaad9e113cd679ee7d"}, - {file = "tarina-0.5.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:05149d5aef6947fcf11a5b6cbbab788202077a734b7a2d184a574283de311725"}, - {file = "tarina-0.5.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b4ae866721d7b906fb327f847d9f8522f46bbea3b0df61b74d6bcc22dad1a33c"}, - {file = "tarina-0.5.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c687aa0cfef24b1df2c8f044a72d8993d68b4e13ea8967b79105be7a2e4097dd"}, - {file = "tarina-0.5.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e609199df957cd35cee6a942028f4caded21f1db8ac4c300c1dba94d61f0080"}, - {file = "tarina-0.5.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d57033ce9fa1c6c0a3a4851503c7320e7f7eba5dfc77e4e2f98932f1b329ba85"}, - {file = "tarina-0.5.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:986c5c59e30041e2a223c04b429777d3848c40e70b449f395b4b40290b6ff1ef"}, - {file = "tarina-0.5.5-cp312-cp312-win32.whl", hash = "sha256:256cf6a4f6a395b90aa4c1305f69a36c5fa6155124b30157a4c7e7af7c6be9ca"}, - {file = "tarina-0.5.5-cp312-cp312-win_amd64.whl", hash = "sha256:ada4a85937cb7f0c5968ffc1b4914779d35525bff14e451113da94028d6a7a23"}, - {file = "tarina-0.5.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4dc78ecae28f9422cb211268e7741058838d24dbf0714ae68ee3c00da278519d"}, - {file = "tarina-0.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a85e14f1006c4f1cab21535c47819c3aceedd909e9b34c3044cfec584deee9ea"}, - {file = "tarina-0.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0689be4febdb9ba442b44c79d9dd861f6269f3dd62a33d258db6f6f1c40454c7"}, - {file = "tarina-0.5.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:046e441e9598c03d3013693688aa1825ba9f78538f81ba15ab3a0dc31cffb74c"}, - {file = "tarina-0.5.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6af01b231f724aef7233ce85ad99619e0bda81bf7d29863ba624117b5e3a82f9"}, - {file = "tarina-0.5.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:028f156980c0e89bc739d3875bafee82bfb198523a0199dd80b10931b50cda8f"}, - {file = "tarina-0.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7f4738381cb9291918c0f83928a13720879e0cfdcc679389bfa1bef985beed93"}, - {file = "tarina-0.5.5-cp38-cp38-win32.whl", hash = "sha256:30b30d0e3c21d2ab04f11f079d2205faa7320b595d1252c6728e8705781f6171"}, - {file = "tarina-0.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:e1f36c9972fa2e0cf3c1ca3842660531008fa4b6b1b89b31cdf06c56254cc902"}, - {file = "tarina-0.5.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d819c4fa630c78e1d3c1b5fbc72158a84da6404009dc040e675e664fa38c030a"}, - {file = "tarina-0.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a81375dab4b02eacedd2364e2394d0c3d76ac064fb0a9d3af1f0c0ea7740e296"}, - {file = "tarina-0.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:926bf0cd6901091c60460c6ac90ef5ea53ebb5a24d865ab1b9381117e4ba2825"}, - {file = "tarina-0.5.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee3dd8ebe04370915e7b763d39f8faee1bd4e9d2600acc8005da5104a698d9e8"}, - {file = "tarina-0.5.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bccac5a9b5af0c4c4b545d7e37eca55abab0abd779f4554cf69bbe29635e3c5c"}, - {file = "tarina-0.5.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dda57675b259a8b0db6647832c4f6a734ce3acf63b2392b7a45e34bace681230"}, - {file = "tarina-0.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:aeba9af50fba8d270abdcffb9f7ca3390223e7e7b4cf1a6a52c8adb2c98b8726"}, - {file = "tarina-0.5.5-cp39-cp39-win32.whl", hash = "sha256:fb1e3130cb6e35495f5867c54d8f049f06a1d915644afce2138ab915ff78291a"}, - {file = "tarina-0.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:da9ababc95b38037280eaeedbbb80c45179bda08578e2a4254e44ee1ef794ac9"}, - {file = "tarina-0.5.5-py3-none-any.whl", hash = "sha256:4828ace26e49037b2dab624e62ca13a473909b2f535f1b4fd5169dd01e16f6c5"}, - {file = "tarina-0.5.5.tar.gz", hash = "sha256:762a3871906e3dd79fc82d13ff99f14f1af977c4b8e2ce860209b8fa97a8b321"}, + {file = "tarina-0.6.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2f7b7e61912a020d6ba3c591c4edbc31bb468544640bd814470c69a07dcc4cd"}, + {file = "tarina-0.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1cac7cbd49317b8e63eba7d0ce0ba11e1218ab51c9d6ee9df8404b5e226db15b"}, + {file = "tarina-0.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69d9923d888948ccc9d20a191ab1f6ff2bc097f4154b6a3780c45dc78ae047cd"}, + {file = "tarina-0.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b0f12395301584e0d56c7edd6118c5b5732b456067735ee7f0800575d5d490f"}, + {file = "tarina-0.6.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84bd95b18c7f6a55606b0071654137b98f70faf5d12343d361377cf0d81ecde6"}, + {file = "tarina-0.6.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94e6ee4df273e1fd3eaf36de7c78a93dde08bf2ad67526122afab1b077e344d9"}, + {file = "tarina-0.6.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7afc1a8a11983f39cce18ad7f8d27601c4d34b1ffe1a1aca2a59b4111d181d01"}, + {file = "tarina-0.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0f74dc96979b774d7e23b964886137f7cf5f58cb2d652c23c3e2d42c1ad39a9"}, + {file = "tarina-0.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3493d59ef19d1ded232e7a33e52c7d6ebfff66f30ac4c3649ae765b14dacc393"}, + {file = "tarina-0.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:323351247817dac174f609104c54fcdc4923aaf3ac5e4a4a81c3efaa007d5778"}, + {file = "tarina-0.6.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:eb7407046d8051b062f71440b90a11be3ef1a64e99b1c6be157312fe2889e4f4"}, + {file = "tarina-0.6.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:50bbbb249d0849855cf43f4fa09c4eecac53e76a54c876ef1a7fb967f17d35b6"}, + {file = "tarina-0.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8a05063e41f01f64c3c6e87b740c1e2cebffdcae20b9d89014a934bb2707a2b7"}, + {file = "tarina-0.6.8-cp310-cp310-win32.whl", hash = "sha256:162756b5c0872856c11cc97ed1a618a9118fef3fb6999edc917578b56f3eb67f"}, + {file = "tarina-0.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:a58a8a6e36e43de8f3a0561cb264abe300ec6bb4a4215f16c316f1db564de6fb"}, + {file = "tarina-0.6.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c05d38546712014aafcf123e89d90ad75f08ebd40c5a7e4c76db40d2ae11b387"}, + {file = "tarina-0.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b036d53d2f8ec46fff9f649b5ec70431cdee3a2b99c8706f67d8d55ea4d4e493"}, + {file = "tarina-0.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:195d2b4a8a8ec2e946ecd8abab70cf3065fbba67e18e971e0412a74a8417a656"}, + {file = "tarina-0.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9bbe85a83904eb29143a43511c04a3fea9a7392c70a6216fcec3ba06cf1b83a"}, + {file = "tarina-0.6.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d6b5893b735d24d2a57f6dbb26de92bb5e09e476077a16408e2054f06263530"}, + {file = "tarina-0.6.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a74cc68f4678d37d517a2d661184712f15da608510de6b1bb9103d85d6014409"}, + {file = "tarina-0.6.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd0f0d769e503fb81d07bd2d342585347f5149803bd7b4b233deaff1833451f"}, + {file = "tarina-0.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b7c91a488aa616e141c337cddce1ba1e0bdac6b98362bc15ce76bd3cd71e17"}, + {file = "tarina-0.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f0b91da85a816e34a3eb4670d21c26a3909872b1f5525797bedc32a45efd98d"}, + {file = "tarina-0.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bf3a5a8fe912d5984627f64cdd187c8a0801c6f3b2f7f12c68cdb5babf691155"}, + {file = "tarina-0.6.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:622e226553453a9a873fca5a2d495d2d28003a23834503147954d651186146c4"}, + {file = "tarina-0.6.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:198102e526d096f37bfb868f815cb3bfc864e136aa8b70e90a107a38ffb82c15"}, + {file = "tarina-0.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:07b252f686410fbf1fe64b3b6705dd40ff218bc1d6a67efac640a0a246f66da3"}, + {file = "tarina-0.6.8-cp311-cp311-win32.whl", hash = "sha256:9e4f56478c58706548e0f08bd277666bac783d918257950be49c4a5d8a35e7b0"}, + {file = "tarina-0.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:d0a92b5368a8a1700f0482d0bc3891dccdfc6dc61f7e66101f68544fca10f096"}, + {file = "tarina-0.6.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8e4bf8ed36c92af7b2a973905f5f4f58dabf7490c5dda695534bf5e3b20d92e"}, + {file = "tarina-0.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:175754132a107c2830a341d3efcae60f44bfd8e0eab56d92537cc6f849ad44e3"}, + {file = "tarina-0.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5f983f0c84959589f884777c7ac0b22786d6d183ae367e53265f09e9ed7dc9f"}, + {file = "tarina-0.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c8cc5f1bf3c2099377b5de76b0e192bb4fae3dcfcc245be9ed14315b29c8c75"}, + {file = "tarina-0.6.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7067131af4081d8ca3d4e70c96e8903c9e483e4d5af4910b93051602f7e8b34"}, + {file = "tarina-0.6.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a66ea0778dcbd9e9b9b226529ed1822261722956468de026655e09b41feee42b"}, + {file = "tarina-0.6.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd8501b0cd51ad8c0e294b46614ef27ef6773324099b5a24711534db55ded44d"}, + {file = "tarina-0.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5812e903c79c02029bfe61fd0ff30bf2a5ac58e7c5b1ab3abfb0f0b57260c5b8"}, + {file = "tarina-0.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60ea05cb8e07a629096d9abbd11258df2bcea03168bc8ef8f369ca0dddfc504a"}, + {file = "tarina-0.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2635d27c34353e9431cb995acece3a0b607cde0934ebf8df35f0e4ae70a0335"}, + {file = "tarina-0.6.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:542b5359d23f969f275f9e80960ce7f19a2684e93cd3ab659afaeb7ee06de038"}, + {file = "tarina-0.6.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4ba2018a855f59c28f087ec1ec1d892c6afccdd0da8f4347fbf8e3219145d66c"}, + {file = "tarina-0.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdf9c5c44d402041df79e747ff9f82c6794875febb053376acdb724043770975"}, + {file = "tarina-0.6.8-cp312-cp312-win32.whl", hash = "sha256:cf44ca009d79d0ecc8541b9067a3b7d086d1939de3bc1b04c164286b0fcf2112"}, + {file = "tarina-0.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:8cd9ff00e2eb9ed6c42b80504005c9b6de4a9afe9ef36771f63b8f9492e38a90"}, + {file = "tarina-0.6.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:af3ff4742b0324d590298812997d028a6d6892bfab5680cd266bed0c1bbf90b0"}, + {file = "tarina-0.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7919d53f20e7d780b44e1804f7c9006bcf5832f87cfeb8d5432f1c832953ed4"}, + {file = "tarina-0.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:878f8d9411ed36cf4408751e209a09c5e2363fc06b6074e966995cdbdc365a9d"}, + {file = "tarina-0.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa166fe58b65be6aed113fca54851b236cfd5c23e33f5e61c403f7b71eda17a"}, + {file = "tarina-0.6.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ea6b8f4568fe5b4f167d002fbe05879bfa04a4121b4ca3d294580cfc86b85d"}, + {file = "tarina-0.6.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbdf37600789f34c1eba6bf05e246f8e9a5b46cf93f9652e92d355e767bbc430"}, + {file = "tarina-0.6.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb748ad914898c293771ccdb32c73b3117a15a3234c9d5eb001f834533cf67ef"}, + {file = "tarina-0.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55ca473eb8f3bbd70af71a29d59c6c0951e19ae24f3be86259b884fc097028a0"}, + {file = "tarina-0.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4b124eee1ce8e25f1c1aba0464c95226ce5ed1b35f079f0fce0f993f38d3393b"}, + {file = "tarina-0.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4295448f4dd9f54703adde2683cf6a515f0944687868da4ec500d595e6a9122d"}, + {file = "tarina-0.6.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:77392b3e53e9e1069b9131b1eb2037c53b360e4d7f3cfa8ae04277fe4e785eb1"}, + {file = "tarina-0.6.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:da9771f40ddcd9da087a4fc2d472b548c0da2bb61ad642b7e186ea7098120be6"}, + {file = "tarina-0.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fac01686a3f9d4461b8ce6ba3afadb5b24138e27f725ee11efc4b057d98d4e4a"}, + {file = "tarina-0.6.8-cp313-cp313-win32.whl", hash = "sha256:a977e3e38535455b38e018ec345f9982c4d2b40945b330aa5c70c1a24a2f8f5b"}, + {file = "tarina-0.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:62445e9a7c0ab7ac8f9c316836ade382f4b9780492f0a8a35a658f827bcb1d7f"}, + {file = "tarina-0.6.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:491b46bfd030f64edc8daac99051e9e1a82d4592aaa9d91c86d18da7f1816a8d"}, + {file = "tarina-0.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8dab1c7ff584f427ae671ecd0379b5795f3ddfee867c5c8997fb110c35b72901"}, + {file = "tarina-0.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f998c32df568da30f236876118f5c359caf00d4935249c78c2e6e15caa1a72f"}, + {file = "tarina-0.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b427b03be5e9d19882558dafd8cae6decc4406f1511741409f1b75a5ce0bf447"}, + {file = "tarina-0.6.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec0bb5da7bbea3df20750acab45403c5fbd5a0b5c4dd7267e6d0cb3669935db4"}, + {file = "tarina-0.6.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7acbc4b547fd623beb174431e2d01b2bc2a5d7451fe3d0e9ae3e0118d862d375"}, + {file = "tarina-0.6.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed03a67986b00411aa5d3cd02896e5daf0e5af44247fd0ef79484b06780a2462"}, + {file = "tarina-0.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:637740055c0e7da77793e0f2d56189f4620e9db31578c590234f85240364e80d"}, + {file = "tarina-0.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9df3d4441b36b7e6ac290b05dd9a1e6193ff0acc2d970be2b855283b76d0ad40"}, + {file = "tarina-0.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ff583c3579e5ea939da67c6a32ebced76b270c884d9f686a003f66f32a0cecdc"}, + {file = "tarina-0.6.8-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:91589c96904f6a07ccf7fec977c403bab52aad63bd3a801a32e11d2ee85e1d46"}, + {file = "tarina-0.6.8-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4a7448cacff18d536c3cf4453282ee29cf401442cba1a50f02981e5d4c374d7f"}, + {file = "tarina-0.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:96fe4a3a33fa20cc3bd996ef9e4a909b00c37d89bd24d594a6024ca59a26c6c4"}, + {file = "tarina-0.6.8-cp39-cp39-win32.whl", hash = "sha256:cbff457659568f46fbbb8e594644278b51efddb532890117b4965b8c059d4d4a"}, + {file = "tarina-0.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:bace5eeaed47048e9a8e9dd7953aadc11aace8c8a5db8457906d8f9c52604fc5"}, + {file = "tarina-0.6.8-py3-none-any.whl", hash = "sha256:0e8a04eecb741937244d97da54f8cd36995819fc12a6b60c9b3f3f3d2860ebe6"}, + {file = "tarina-0.6.8.tar.gz", hash = "sha256:d77d68948967a130066b61e85b21189259030f80ad19295bd81ff6b52f348316"}, ] [package.dependencies] @@ -3755,7 +4263,28 @@ yaml = ["pyyaml (>=6.0.1)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" + +[[package]] +name = "tenacity" +version = "9.0.0" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[package.source] +type = "legacy" +url = "https://mirrors.aliyun.com/pypi/simple" +reference = "aliyun" [[package]] name = "text-unidecode" @@ -3763,6 +4292,7 @@ version = "1.3" description = "The most basic Text::Unidecode port" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, @@ -3771,55 +4301,90 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +markers = {main = "python_version < \"3.11\"", dev = "python_full_version <= \"3.11.0a6\""} [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "tomlkit" -version = "0.13.0" +version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, - {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "tortoise-orm" -version = "0.20.0" +version = "0.20.1" description = "Easy async ORM for python, built with relations in mind" optional = false python-versions = ">=3.8,<4.0" +groups = ["main"] files = [ - {file = "tortoise_orm-0.20.0-py3-none-any.whl", hash = "sha256:1891ad935de689ddf002c5c65c864176d28659ab6069e45f0e2cde32359bb8d9"}, - {file = "tortoise_orm-0.20.0.tar.gz", hash = "sha256:283af584d685dcc58d6cc1da35b9115bb1e41c89075eae2a19c493b39b9b41f7"}, + {file = "tortoise_orm-0.20.1-py3-none-any.whl", hash = "sha256:bf88bc1ba7495a8827565c071efba0a89c4b5f83ff1c16be3c837a4e6b672c21"}, + {file = "tortoise_orm-0.20.1.tar.gz", hash = "sha256:c896c90a90d1213b822ac0d607b61659ad5fcd5ff72698a8ba2d9efbad9932f3"}, ] [package.dependencies] aiosqlite = ">=0.16.0,<0.18.0" asyncpg = {version = "*", optional = true, markers = "extra == \"asyncpg\""} iso8601 = ">=1.0.2,<2.0.0" +pydantic = ">=2.0,<2.7.0 || >2.7.0,<3.0" pypika-tortoise = ">=0.1.6,<0.2.0" pytz = "*" @@ -3834,23 +4399,24 @@ psycopg = ["psycopg[binary,pool] (>=3.0.12,<4.0.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "types-python-dateutil" -version = "2.9.0.20240316" +version = "2.9.0.20241206" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, - {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, + {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, + {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "typing-extensions" @@ -3858,6 +4424,7 @@ version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, @@ -3866,23 +4433,25 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "tzdata" -version = "2024.1" +version = "2025.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] +markers = "platform_system == \"Windows\"" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, + {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "tzlocal" @@ -3890,6 +4459,7 @@ version = "5.2" description = "tzinfo object for the local timezone" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, @@ -3904,7 +4474,7 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "ujson" @@ -3912,6 +4482,7 @@ version = "5.10.0" description = "Ultra fast JSON encoder and decoder for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, @@ -3996,17 +4567,18 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "urllib3" -version = "2.2.2" +version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] @@ -4018,24 +4590,25 @@ zstd = ["zstandard (>=0.18.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "uvicorn" -version = "0.30.5" +version = "0.34.0" description = "The lightning-fast ASGI server." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "uvicorn-0.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"}, - {file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"}, + {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, + {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} @@ -4044,71 +4617,81 @@ watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standar websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "uvloop" -version = "0.19.0" +version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" +groups = ["main"] +markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\"" files = [ - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"}, - {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"}, - {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"}, - {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"}, - {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"}, - {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"}, - {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"}, - {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"}, - {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"}, - {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"}, - {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"}, - {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"}, - {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"}, - {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"}, - {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"}, - {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"}, - {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"}, + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, + {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"}, + {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"}, + {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"}, + {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"}, + {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"}, + {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"}, + {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"}, + {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"}, + {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"}, + {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"}, + {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"}, + {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"}, + {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"}, + {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"}, + {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"}, + {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"}, + {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"}, + {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"}, + {file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"}, ] [package.extras] +dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"] docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "virtualenv" -version = "20.26.3" +version = "20.29.2" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, + {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, + {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, ] [package.dependencies] @@ -4123,102 +4706,99 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "watchfiles" -version = "0.23.0" +version = "0.24.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "watchfiles-0.23.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4"}, - {file = "watchfiles-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1"}, - {file = "watchfiles-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207"}, - {file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b"}, - {file = "watchfiles-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981"}, - {file = "watchfiles-0.23.0-cp310-none-win32.whl", hash = "sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42"}, - {file = "watchfiles-0.23.0-cp310-none-win_amd64.whl", hash = "sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75"}, - {file = "watchfiles-0.23.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab"}, - {file = "watchfiles-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788"}, - {file = "watchfiles-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1"}, - {file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220"}, - {file = "watchfiles-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320"}, - {file = "watchfiles-0.23.0-cp311-none-win32.whl", hash = "sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b"}, - {file = "watchfiles-0.23.0-cp311-none-win_amd64.whl", hash = "sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e"}, - {file = "watchfiles-0.23.0-cp311-none-win_arm64.whl", hash = "sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304"}, - {file = "watchfiles-0.23.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5"}, - {file = "watchfiles-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f"}, - {file = "watchfiles-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0"}, - {file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c"}, - {file = "watchfiles-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581"}, - {file = "watchfiles-0.23.0-cp312-none-win32.whl", hash = "sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75"}, - {file = "watchfiles-0.23.0-cp312-none-win_amd64.whl", hash = "sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb"}, - {file = "watchfiles-0.23.0-cp312-none-win_arm64.whl", hash = "sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8"}, - {file = "watchfiles-0.23.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9"}, - {file = "watchfiles-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d"}, - {file = "watchfiles-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0"}, - {file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef"}, - {file = "watchfiles-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1"}, - {file = "watchfiles-0.23.0-cp313-none-win32.whl", hash = "sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b"}, - {file = "watchfiles-0.23.0-cp313-none-win_amd64.whl", hash = "sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff"}, - {file = "watchfiles-0.23.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:2dddc2487d33e92f8b6222b5fb74ae2cfde5e8e6c44e0248d24ec23befdc5366"}, - {file = "watchfiles-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e75695cc952e825fa3e0684a7f4a302f9128721f13eedd8dbd3af2ba450932b8"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2537ef60596511df79b91613a5bb499b63f46f01a11a81b0a2b0dedf645d0a9c"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20b423b58f5fdde704a226b598a2d78165fe29eb5621358fe57ea63f16f165c4"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b98732ec893975455708d6fc9a6daab527fc8bbe65be354a3861f8c450a632a4"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee1f5fcbf5bc33acc0be9dd31130bcba35d6d2302e4eceafafd7d9018c7755ab"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f195338a5a7b50a058522b39517c50238358d9ad8284fd92943643144c0c03"}, - {file = "watchfiles-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524fcb8d59b0dbee2c9b32207084b67b2420f6431ed02c18bd191e6c575f5c48"}, - {file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0eff099a4df36afaa0eea7a913aa64dcf2cbd4e7a4f319a73012210af4d23810"}, - {file = "watchfiles-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a8323daae27ea290ba3350c70c836c0d2b0fb47897fa3b0ca6a5375b952b90d3"}, - {file = "watchfiles-0.23.0-cp38-none-win32.whl", hash = "sha256:aafea64a3ae698695975251f4254df2225e2624185a69534e7fe70581066bc1b"}, - {file = "watchfiles-0.23.0-cp38-none-win_amd64.whl", hash = "sha256:c846884b2e690ba62a51048a097acb6b5cd263d8bd91062cd6137e2880578472"}, - {file = "watchfiles-0.23.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a753993635eccf1ecb185dedcc69d220dab41804272f45e4aef0a67e790c3eb3"}, - {file = "watchfiles-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6bb91fa4d0b392f0f7e27c40981e46dda9eb0fbc84162c7fb478fe115944f491"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1f67312efa3902a8e8496bfa9824d3bec096ff83c4669ea555c6bdd213aa516"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ca6b71dcc50d320c88fb2d88ecd63924934a8abc1673683a242a7ca7d39e781"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aec5c29915caf08771d2507da3ac08e8de24a50f746eb1ed295584ba1820330"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1733b9bc2c8098c6bdb0ff7a3d7cb211753fecb7bd99bdd6df995621ee1a574b"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02ff5d7bd066c6a7673b17c8879cd8ee903078d184802a7ee851449c43521bdd"}, - {file = "watchfiles-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18e2de19801b0eaa4c5292a223effb7cfb43904cb742c5317a0ac686ed604765"}, - {file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8ada449e22198c31fb013ae7e9add887e8d2bd2335401abd3cbc55f8c5083647"}, - {file = "watchfiles-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3af1b05361e1cc497bf1be654a664750ae61f5739e4bb094a2be86ec8c6db9b6"}, - {file = "watchfiles-0.23.0-cp39-none-win32.whl", hash = "sha256:486bda18be5d25ab5d932699ceed918f68eb91f45d018b0343e3502e52866e5e"}, - {file = "watchfiles-0.23.0-cp39-none-win_amd64.whl", hash = "sha256:d2d42254b189a346249424fb9bb39182a19289a2409051ee432fb2926bad966a"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed"}, - {file = "watchfiles-0.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aba037c1310dd108411d27b3d5815998ef0e83573e47d4219f45753c710f969f"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:a96ac14e184aa86dc43b8a22bb53854760a58b2966c2b41580de938e9bf26ed0"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11698bb2ea5e991d10f1f4f83a39a02f91e44e4bd05f01b5c1ec04c9342bf63c"}, - {file = "watchfiles-0.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efadd40fca3a04063d40c4448c9303ce24dd6151dc162cfae4a2a060232ebdcb"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:556347b0abb4224c5ec688fc58214162e92a500323f50182f994f3ad33385dcb"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1cf7f486169986c4b9d34087f08ce56a35126600b6fef3028f19ca16d5889071"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f18de0f82c62c4197bea5ecf4389288ac755896aac734bd2cc44004c56e4ac47"}, - {file = "watchfiles-0.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:532e1f2c491274d1333a814e4c5c2e8b92345d41b12dc806cf07aaff786beb66"}, - {file = "watchfiles-0.23.0.tar.gz", hash = "sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b"}, + {file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0"}, + {file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a"}, + {file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e"}, + {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c"}, + {file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188"}, + {file = "watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735"}, + {file = "watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04"}, + {file = "watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428"}, + {file = "watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15"}, + {file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823"}, + {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab"}, + {file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec"}, + {file = "watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d"}, + {file = "watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c"}, + {file = "watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633"}, + {file = "watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a"}, + {file = "watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f"}, + {file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234"}, + {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef"}, + {file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968"}, + {file = "watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444"}, + {file = "watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896"}, + {file = "watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418"}, + {file = "watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48"}, + {file = "watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab"}, + {file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f"}, + {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b"}, + {file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18"}, + {file = "watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07"}, + {file = "watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366"}, + {file = "watchfiles-0.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318"}, + {file = "watchfiles-0.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b"}, + {file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91"}, + {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b"}, + {file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22"}, + {file = "watchfiles-0.24.0-cp38-none-win32.whl", hash = "sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1"}, + {file = "watchfiles-0.24.0-cp38-none-win_amd64.whl", hash = "sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1"}, + {file = "watchfiles-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886"}, + {file = "watchfiles-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a"}, + {file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9"}, + {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca"}, + {file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e"}, + {file = "watchfiles-0.24.0-cp39-none-win32.whl", hash = "sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da"}, + {file = "watchfiles-0.24.0-cp39-none-win_amd64.whl", hash = "sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4"}, + {file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777"}, + {file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e"}, + {file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1"}, ] [package.dependencies] @@ -4227,7 +4807,7 @@ anyio = ">=3.0.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "wcwidth" @@ -4235,6 +4815,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -4243,103 +4824,103 @@ files = [ [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "websockets" -version = "12.0" +version = "14.2" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, - {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, - {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, - {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, - {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, - {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, - {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, - {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, - {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, - {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, - {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, - {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, - {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, - {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, - {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, - {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, - {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, - {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, - {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, - {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, - {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, - {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, - {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, - {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, - {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, - {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, - {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, - {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, - {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, - {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, - {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, - {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, - {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, - {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, - {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, - {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, - {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, - {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, - {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, - {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, - {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, - {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, + {file = "websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885"}, + {file = "websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397"}, + {file = "websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610"}, + {file = "websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3"}, + {file = "websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980"}, + {file = "websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8"}, + {file = "websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7"}, + {file = "websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f"}, + {file = "websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d"}, + {file = "websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d"}, + {file = "websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2"}, + {file = "websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166"}, + {file = "websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f"}, + {file = "websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910"}, + {file = "websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c"}, + {file = "websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473"}, + {file = "websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473"}, + {file = "websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56"}, + {file = "websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142"}, + {file = "websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d"}, + {file = "websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a"}, + {file = "websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b"}, + {file = "websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c"}, + {file = "websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967"}, + {file = "websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990"}, + {file = "websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda"}, + {file = "websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95"}, + {file = "websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3"}, + {file = "websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9"}, + {file = "websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267"}, + {file = "websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe"}, + {file = "websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205"}, + {file = "websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce"}, + {file = "websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e"}, + {file = "websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad"}, + {file = "websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03"}, + {file = "websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f"}, + {file = "websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5"}, + {file = "websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a"}, + {file = "websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20"}, + {file = "websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2"}, + {file = "websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307"}, + {file = "websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc"}, + {file = "websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f"}, + {file = "websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe"}, + {file = "websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12"}, + {file = "websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7"}, + {file = "websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5"}, + {file = "websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0"}, + {file = "websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258"}, + {file = "websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0"}, + {file = "websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4"}, + {file = "websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc"}, + {file = "websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661"}, + {file = "websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef"}, + {file = "websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29"}, + {file = "websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c"}, + {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2"}, + {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c"}, + {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a"}, + {file = "websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3"}, + {file = "websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f"}, + {file = "websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42"}, + {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f"}, + {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574"}, + {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270"}, + {file = "websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365"}, + {file = "websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b"}, + {file = "websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5"}, ] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "win32-setctime" -version = "1.1.0" +version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] +markers = "sys_platform == \"win32\"" files = [ - {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, - {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, + {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, + {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, ] [package.extras] @@ -4348,137 +4929,136 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "yarl" -version = "1.9.4" +version = "1.18.3" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [[package]] name = "zipp" -version = "3.19.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [package.source] type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" -reference = "ali" +reference = "aliyun" [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "0c19bc0955bde3a4f368395f6d7117c3ab638e36b9af0d9ac05d08d1922dabbc" +content-hash = "f1d23ae2feff2f5c96a97f1a7e52e1738858817b13281b9253a41851cacf0740" diff --git a/pyproject.toml b/pyproject.toml index dc2d6501..1b3685a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,53 +1,139 @@ [tool.poetry] name = "zhenxun_bot" -version = "0.1.1" +version = "0.2.4" description = "基于 Nonebot2 和 go-cqhttp 开发,以 postgresql 作为数据库,非常可爱的绪山真寻bot" authors = ["HibiKier <775757368@qq.com>"] license = "AGPL" +package-mode = false [[tool.poetry.source]] -name = "ali" -default = true +name = "aliyun" url = "https://mirrors.aliyun.com/pypi/simple/" +priority = "primary" [tool.poetry.dependencies] python = "^3.10" playwright = "^1.41.1" nonebot-adapter-onebot = "^2.3.1" -nonebot-plugin-apscheduler = "^0.3.0" -tortoise-orm = {extras = ["asyncpg"], version = "^0.20.0"} +nonebot-plugin-apscheduler = "^0.5" +tortoise-orm = { extras = ["asyncpg"], version = "^0.20.0" } cattrs = "^23.2.3" ruamel-yaml = "^0.18.5" strenum = "^0.4.15" nonebot-plugin-session = "^0.2.3" ujson = "^5.9.0" -nonebot-adapter-kaiheila = "^0.3.0" nb-cli = "^1.3.0" -nonebot2 = "^2.1.3" -nonebot-adapter-discord = "^0.1.3" -nonebot-adapter-dodo = "^0.1.4" -pillow = "9.5" +nonebot2 = { extras = ["fastapi"], version = "^2.3.3" } +pillow = "^10.0.0" retrying = "^1.3.4" aiofiles = "^23.2.1" -nonebot-plugin-htmlrender = "^0.3.0" -nonebot-plugin-userinfo = "^0.1.3" +nonebot-plugin-htmlrender = ">=0.6.0,<1.0.0" pypinyin = "^0.51.0" beautifulsoup4 = "^4.12.3" lxml = "^5.1.0" psutil = "^5.9.8" feedparser = "^6.0.11" -opencv-python = "^4.9.0.80" imagehash = "^4.3.1" -black = "^24.4.2" cn2an = "^0.5.22" -aiohttp = "^3.9.5" dateparser = "^1.2.0" bilireq = "0.2.3post0" -python-jose = {extras = ["cryptography"], version = "^3.3.0"} +python-jose = { extras = ["cryptography"], version = "^3.3.0" } python-multipart = "^0.0.9" -nonebot-plugin-alconna = "^0.51.1" +aiocache = "^0.12.2" +py-cpuinfo = "^9.0.0" +nonebot-plugin-alconna = "^0.54.0" +tenacity = "^9.0.0" +nonebot-plugin-uninfo = ">0.4.1" +nonebot-plugin-waiter = "^0.8.1" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] +nonebug = "^0.4" +pytest-cov = "^5.0.0" +pytest-mock = "^3.6.1" +pytest-asyncio = "^0.25" +pytest-xdist = "^3.3.1" +respx = "^0.21.1" +ruff = "^0.8.0" +pre-commit = "^4.0.0" + + +[tool.nonebot] +plugins = [ + "nonebot_plugin_apscheduler", + "nonebot_plugin_session", + "nonebot_plugin_htmlrender", + "nonebot_plugin_alconna", +] +plugin_dirs = ["zhenxun/services", "zhenxun/builtin_plugins", "zhenxun/plugins"] +adapters = [ + { name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" }, + # { name = "DoDo", module_name = "nonebot.adapters.dodo" }, + # { name = "开黑啦", module_name = "nonebot.adapters.kaiheila" }, +] + +[tool.ruff] +line-length = 88 +target-version = "py310" + +[tool.ruff.format] +line-ending = "lf" + +[tool.ruff.lint] +select = [ + "F", # Pyflakes + "W", # pycodestyle warnings + "E", # pycodestyle errors + "I", # isort + "UP", # pyupgrade + "ASYNC", # flake8-async + "C4", # flake8-comprehensions + "T10", # flake8-debugger + "T20", # flake8-print + "PYI", # flake8-pyi + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "TID", # flake8-tidy-imports + "RUF", # Ruff-specific rules +] +ignore = [ + "E402", # module-import-not-at-top-of-file + "UP037", # quoted-annotation + "RUF001", # ambiguous-unicode-character-string + "RUF002", # ambiguous-unicode-character-docstring + "RUF003", # ambiguous-unicode-character-comment + "TID252", # relative-imports +] + +[tool.ruff.lint.isort] +force-sort-within-sections = true +known-first-party = ["zhenxun", "tests/*"] +extra-standard-library = ["typing_extensions"] + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false + +[tool.ruff.lint.pyupgrade] +keep-runtime-typing = true + +[tool.pyright] +pythonVersion = "3.10" +pythonPlatform = "All" +defineConstant = { PYDANTIC_V2 = true } +executionEnvironments = [ + { root = "./tests", extraPaths = [ + "./", + ] }, + { root = "./" }, +] + +typeCheckingMode = "standard" +reportShadowedImports = false +disableBytesTypePromotions = true + +[tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d717a0bb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,129 @@ +aiocache==0.12.3 ; python_version >= "3.10" and python_version < "4.0" +aiofiles==23.2.1 ; python_version >= "3.10" and python_version < "4.0" +aiosqlite==0.17.0 ; python_version >= "3.10" and python_version < "4.0" +annotated-types==0.7.0 ; python_version >= "3.10" and python_version < "4.0" +anyio==4.8.0 ; python_version >= "3.10" and python_version < "4.0" +apscheduler==3.11.0 ; python_version >= "3.10" and python_version < "4.0" +arclet-alconna-tools==0.7.10 ; python_version >= "3.10" and python_version < "4.0" +arclet-alconna==1.8.35 ; python_version >= "3.10" and python_version < "4.0" +arrow==1.3.0 ; python_version >= "3.10" and python_version < "4.0" +async-timeout==5.0.1 ; python_version >= "3.10" and python_version < "3.11.0" +asyncpg==0.30.0 ; python_version >= "3.10" and python_version < "4.0" +attrs==25.1.0 ; python_version >= "3.10" and python_version < "4.0" +beautifulsoup4==4.13.3 ; python_version >= "3.10" and python_version < "4.0" +bilireq==0.2.3.post0 ; python_version >= "3.10" and python_version < "4.0" +binaryornot==0.4.4 ; python_version >= "3.10" and python_version < "4.0" +cashews==7.4.0 ; python_version >= "3.10" and python_version < "4.0" +cattrs==23.2.3 ; python_version >= "3.10" and python_version < "4.0" +certifi==2025.1.31 ; python_version >= "3.10" and python_version < "4.0" +cffi==1.17.1 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" +chardet==5.2.0 ; python_version >= "3.10" and python_version < "4.0" +charset-normalizer==3.4.1 ; python_version >= "3.10" and python_version < "4.0" +click==8.1.8 ; python_version >= "3.10" and python_version < "4.0" +cn2an==0.5.23 ; python_version >= "3.10" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and sys_platform == "win32" or python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" +cookiecutter==2.6.0 ; python_version >= "3.10" and python_version < "4.0" +cryptography==44.0.1 ; python_version >= "3.10" and python_version < "4.0" +dateparser==1.2.1 ; python_version >= "3.10" and python_version < "4.0" +distlib==0.3.9 ; python_version >= "3.10" and python_version < "4.0" +ecdsa==0.19.0 ; python_version >= "3.10" and python_version < "4.0" +exceptiongroup==1.2.2 ; python_version >= "3.10" and python_version < "4.0" +fastapi==0.115.8 ; python_version >= "3.10" and python_version < "4.0" +feedparser==6.0.11 ; python_version >= "3.10" and python_version < "4.0" +filelock==3.17.0 ; python_version >= "3.10" and python_version < "4.0" +greenlet==3.1.1 ; python_version >= "3.10" and python_version < "4.0" +grpcio==1.70.0 ; python_version >= "3.10" and python_version < "4.0" +h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0" +httpcore==0.16.3 ; python_version >= "3.10" and python_version < "4.0" +httptools==0.6.4 ; python_version >= "3.10" and python_version < "4.0" +httpx==0.23.3 ; python_version >= "3.10" and python_version < "4.0" +idna==3.10 ; python_version >= "3.10" and python_version < "4.0" +imagehash==4.3.2 ; python_version >= "3.10" and python_version < "4.0" +importlib-metadata==8.6.1 ; python_version >= "3.10" and python_version < "4.0" +iso8601==1.1.0 ; python_version >= "3.10" and python_version < "4.0" +jinja2==3.1.5 ; python_version >= "3.10" and python_version < "4.0" +loguru==0.7.3 ; python_version >= "3.10" and python_version < "4.0" +lxml==5.3.1 ; python_version >= "3.10" and python_version < "4.0" +markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0" +markdown==3.7 ; python_version >= "3.10" and python_version < "4.0" +markupsafe==3.0.2 ; python_version >= "3.10" and python_version < "4.0" +mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0" +msgpack==1.1.0 ; python_version >= "3.10" and python_version < "4.0" +multidict==6.1.0 ; python_version >= "3.10" and python_version < "4.0" +nb-cli==1.4.2 ; python_version >= "3.10" and python_version < "4.0" +nepattern==0.7.7 ; python_version >= "3.10" and python_version < "4.0" +nonebot-adapter-onebot==2.4.6 ; python_version >= "3.10" and python_version < "4.0" +nonebot-plugin-alconna==0.54.2 ; python_version >= "3.10" and python_version < "4.0" +nonebot-plugin-apscheduler==0.5.0 ; python_version >= "3.10" and python_version < "4.0" +nonebot-plugin-htmlrender==0.6.0 ; python_version >= "3.10" and python_version < "4.0" +nonebot-plugin-session==0.2.3 ; python_version >= "3.10" and python_version < "4.0" +nonebot-plugin-uninfo==0.6.8 ; python_version >= "3.10" and python_version < "4.0" +nonebot-plugin-waiter==0.8.1 ; python_version >= "3.10" and python_version < "4.0" +nonebot2==2.4.1 ; python_version >= "3.10" and python_version < "4.0" +noneprompt==0.1.9 ; python_version >= "3.10" and python_version < "4.0" +numpy==2.2.2 ; python_version >= "3.10" and python_version < "4.0" +pillow==10.4.0 ; python_version >= "3.10" and python_version < "4.0" +platformdirs==4.3.6 ; python_version >= "3.10" and python_version < "4.0" +playwright==1.50.0 ; python_version >= "3.10" and python_version < "4.0" +proces==0.1.7 ; python_version >= "3.10" and python_version < "4.0" +prompt-toolkit==3.0.50 ; python_version >= "3.10" and python_version < "4.0" +propcache==0.2.1 ; python_version >= "3.10" and python_version < "4.0" +protobuf==4.25.6 ; python_version >= "3.10" and python_version < "4.0" +psutil==5.9.8 ; python_version >= "3.10" and python_version < "4.0" +py-cpuinfo==9.0.0 ; python_version >= "3.10" and python_version < "4.0" +pyasn1==0.6.1 ; python_version >= "3.10" and python_version < "4.0" +pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy" +pydantic-core==2.27.2 ; python_version >= "3.10" and python_version < "4.0" +pydantic==2.10.6 ; python_version >= "3.10" and python_version < "4.0" +pyee==12.1.1 ; python_version >= "3.10" and python_version < "4.0" +pyfiglet==1.0.2 ; python_version >= "3.10" and python_version < "4.0" +pygments==2.19.1 ; python_version >= "3.10" and python_version < "4.0" +pygtrie==2.5.0 ; python_version >= "3.10" and python_version < "4.0" +pymdown-extensions==10.14.3 ; python_version >= "3.10" and python_version < "4.0" +pypika-tortoise==0.1.6 ; python_version >= "3.10" and python_version < "4.0" +pypinyin==0.51.0 ; python_version >= "3.10" and python_version < "4.0" +python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" +python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0" +python-jose==3.3.0 ; python_version >= "3.10" and python_version < "4.0" +python-markdown-math==0.8 ; python_version >= "3.10" and python_version < "4.0" +python-multipart==0.0.9 ; python_version >= "3.10" and python_version < "4.0" +python-slugify==8.0.4 ; python_version >= "3.10" and python_version < "4.0" +pytz==2025.1 ; python_version >= "3.10" and python_version < "4.0" +pywavelets==1.8.0 ; python_version >= "3.10" and python_version < "4.0" +pyyaml==6.0.2 ; python_version >= "3.10" and python_version < "4.0" +regex==2024.11.6 ; python_version >= "3.10" and python_version < "4.0" +requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0" +retrying==1.3.4 ; python_version >= "3.10" and python_version < "4.0" +rfc3986==1.5.0 ; python_version >= "3.10" and python_version < "4.0" +rich==13.9.4 ; python_version >= "3.10" and python_version < "4.0" +rsa==4.9 ; python_version >= "3.10" and python_version < "4.0" +ruamel-yaml-clib==0.2.12 ; python_version >= "3.10" and python_version < "3.13" and platform_python_implementation == "CPython" +ruamel-yaml==0.18.10 ; python_version >= "3.10" and python_version < "4.0" +scipy==1.15.1 ; python_version >= "3.10" and python_version < "4.0" +sgmllib3k==1.0.0 ; python_version >= "3.10" and python_version < "4.0" +six==1.17.0 ; python_version >= "3.10" and python_version < "4.0" +sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0" +soupsieve==2.6 ; python_version >= "3.10" and python_version < "4.0" +starlette==0.45.3 ; python_version >= "3.10" and python_version < "4.0" +strenum==0.4.15 ; python_version >= "3.10" and python_version < "4.0" +tarina==0.6.8 ; python_version >= "3.10" and python_version < "4.0" +tenacity==9.0.0 ; python_version >= "3.10" and python_version < "4.0" +text-unidecode==1.3 ; python_version >= "3.10" and python_version < "4.0" +tomli==2.2.1 ; python_version >= "3.10" and python_version < "3.11" +tomlkit==0.13.2 ; python_version >= "3.10" and python_version < "4.0" +tortoise-orm==0.20.1 ; python_version >= "3.10" and python_version < "4.0" +types-python-dateutil==2.9.0.20241206 ; python_version >= "3.10" and python_version < "4.0" +typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0" +tzdata==2025.1 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" +tzlocal==5.2 ; python_version >= "3.10" and python_version < "4.0" +ujson==5.10.0 ; python_version >= "3.10" and python_version < "4.0" +urllib3==2.3.0 ; python_version >= "3.10" and python_version < "4.0" +uvicorn==0.34.0 ; python_version >= "3.10" and python_version < "4.0" +uvloop==0.21.0 ; python_version >= "3.10" and python_version < "4.0" and (sys_platform != "win32" and sys_platform != "cygwin") and platform_python_implementation != "PyPy" +virtualenv==20.29.2 ; python_version >= "3.10" and python_version < "4.0" +watchfiles==0.24.0 ; python_version >= "3.10" and python_version < "4.0" +wcwidth==0.2.13 ; python_version >= "3.10" and python_version < "4.0" +websockets==14.2 ; python_version >= "3.10" and python_version < "4.0" +win32-setctime==1.2.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform == "win32" +yarl==1.18.3 ; python_version >= "3.10" and python_version < "4.0" +zipp==3.21.0 ; python_version >= "3.10" and python_version < "4.0" diff --git a/resources/font/CJGaoDeGuo.otf b/resources/font/CJGaoDeGuo.otf deleted file mode 100644 index 1e7d1195..00000000 Binary files a/resources/font/CJGaoDeGuo.otf and /dev/null differ diff --git a/resources/font/HWXingKai.ttf b/resources/font/HWXingKai.ttf deleted file mode 100644 index 353ba6a9..00000000 Binary files a/resources/font/HWXingKai.ttf and /dev/null differ diff --git a/resources/font/HWZhongSong.ttf b/resources/font/HWZhongSong.ttf deleted file mode 100644 index 0f167cbd..00000000 Binary files a/resources/font/HWZhongSong.ttf and /dev/null differ diff --git a/resources/font/HYWenHei-85W.ttf b/resources/font/HYWenHei-85W.ttf deleted file mode 100644 index 1ea57000..00000000 Binary files a/resources/font/HYWenHei-85W.ttf and /dev/null differ diff --git a/resources/font/STKAITI.TTF b/resources/font/STKAITI.TTF deleted file mode 100644 index 50441161..00000000 Binary files a/resources/font/STKAITI.TTF and /dev/null differ diff --git a/resources/font/SweiSpringCJKtc-Bold.ttf b/resources/font/SweiSpringCJKtc-Bold.ttf deleted file mode 100644 index ebfafe8c..00000000 Binary files a/resources/font/SweiSpringCJKtc-Bold.ttf and /dev/null differ diff --git a/resources/font/SweiSpringSugarCJKtc-Regular.ttf b/resources/font/SweiSpringSugarCJKtc-Regular.ttf deleted file mode 100644 index 2028acd5..00000000 Binary files a/resources/font/SweiSpringSugarCJKtc-Regular.ttf and /dev/null differ diff --git a/resources/font/YSHaoShenTi-2.ttf b/resources/font/YSHaoShenTi-2.ttf deleted file mode 100644 index a67764a9..00000000 Binary files a/resources/font/YSHaoShenTi-2.ttf and /dev/null differ diff --git a/resources/font/gorga.otf b/resources/font/gorga.otf deleted file mode 100644 index fc911fbb..00000000 Binary files a/resources/font/gorga.otf and /dev/null differ diff --git a/resources/font/msyh.ttf b/resources/font/msyh.ttf deleted file mode 100644 index aa23ae1f..00000000 Binary files a/resources/font/msyh.ttf and /dev/null differ diff --git a/resources/font/sarasa-mono-sc-nerd-regular.ttf b/resources/font/sarasa-mono-sc-nerd-regular.ttf deleted file mode 100644 index 636c2f44..00000000 Binary files a/resources/font/sarasa-mono-sc-nerd-regular.ttf and /dev/null differ diff --git a/resources/font/wq.ttf b/resources/font/wq.ttf deleted file mode 100644 index f15cfefc..00000000 Binary files a/resources/font/wq.ttf and /dev/null differ diff --git a/resources/font/yz.ttf b/resources/font/yz.ttf deleted file mode 100644 index d150b537..00000000 Binary files a/resources/font/yz.ttf and /dev/null differ diff --git a/resources/font/yzz.ttc b/resources/font/yzz.ttc deleted file mode 100644 index d150b537..00000000 Binary files a/resources/font/yzz.ttc and /dev/null differ diff --git a/resources/image/_base/laugh/0.jpg b/resources/image/_base/laugh/0.jpg deleted file mode 100644 index 3ad37672..00000000 Binary files a/resources/image/_base/laugh/0.jpg and /dev/null differ diff --git a/resources/image/_base/laugh/1.jpg b/resources/image/_base/laugh/1.jpg deleted file mode 100644 index 8e302817..00000000 Binary files a/resources/image/_base/laugh/1.jpg and /dev/null differ diff --git a/resources/image/_icon/abrasion_white.png b/resources/image/_icon/abrasion_white.png deleted file mode 100644 index ec08c67d..00000000 Binary files a/resources/image/_icon/abrasion_white.png and /dev/null differ diff --git a/resources/image/_icon/box_gray.png b/resources/image/_icon/box_gray.png deleted file mode 100644 index 04f6a68e..00000000 Binary files a/resources/image/_icon/box_gray.png and /dev/null differ diff --git a/resources/image/_icon/box_white.png b/resources/image/_icon/box_white.png deleted file mode 100644 index a072acdc..00000000 Binary files a/resources/image/_icon/box_white.png and /dev/null differ diff --git a/resources/image/_icon/discode.png b/resources/image/_icon/discode.png deleted file mode 100644 index 08e0a937..00000000 Binary files a/resources/image/_icon/discode.png and /dev/null differ diff --git a/resources/image/_icon/dodo.png b/resources/image/_icon/dodo.png deleted file mode 100644 index 58fa20fc..00000000 Binary files a/resources/image/_icon/dodo.png and /dev/null differ diff --git a/resources/image/_icon/kook.png b/resources/image/_icon/kook.png deleted file mode 100644 index 3c87716f..00000000 Binary files a/resources/image/_icon/kook.png and /dev/null differ diff --git a/resources/image/_icon/name_gray.png b/resources/image/_icon/name_gray.png deleted file mode 100644 index b4fde960..00000000 Binary files a/resources/image/_icon/name_gray.png and /dev/null differ diff --git a/resources/image/_icon/name_white.png b/resources/image/_icon/name_white.png deleted file mode 100644 index 0c2afdb4..00000000 Binary files a/resources/image/_icon/name_white.png and /dev/null differ diff --git a/resources/image/_icon/num_white.png b/resources/image/_icon/num_white.png deleted file mode 100644 index a4048260..00000000 Binary files a/resources/image/_icon/num_white.png and /dev/null differ diff --git a/resources/image/_icon/price_white.png b/resources/image/_icon/price_white.png deleted file mode 100644 index 8f088012..00000000 Binary files a/resources/image/_icon/price_white.png and /dev/null differ diff --git a/resources/image/_icon/qq.png b/resources/image/_icon/qq.png deleted file mode 100644 index 42825456..00000000 Binary files a/resources/image/_icon/qq.png and /dev/null differ diff --git a/resources/image/_icon/reload_white.png b/resources/image/_icon/reload_white.png deleted file mode 100644 index e5c5f98a..00000000 Binary files a/resources/image/_icon/reload_white.png and /dev/null differ diff --git a/resources/image/_icon/tone_white.png b/resources/image/_icon/tone_white.png deleted file mode 100644 index 8e8434ba..00000000 Binary files a/resources/image/_icon/tone_white.png and /dev/null differ diff --git a/resources/image/_icon/type_white.png b/resources/image/_icon/type_white.png deleted file mode 100644 index 2770e5dc..00000000 Binary files a/resources/image/_icon/type_white.png and /dev/null differ diff --git a/resources/image/_icon/want_buy_white.png b/resources/image/_icon/want_buy_white.png deleted file mode 100644 index a0fc1395..00000000 Binary files a/resources/image/_icon/want_buy_white.png and /dev/null differ diff --git a/resources/image/background/0.png b/resources/image/background/0.png deleted file mode 100644 index 495111d4..00000000 Binary files a/resources/image/background/0.png and /dev/null differ diff --git a/resources/image/background/1.png b/resources/image/background/1.png deleted file mode 100644 index fcad28a6..00000000 Binary files a/resources/image/background/1.png and /dev/null differ diff --git a/resources/image/background/check/0.jpg b/resources/image/background/check/0.jpg deleted file mode 100644 index 00785805..00000000 Binary files a/resources/image/background/check/0.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/0.jpg b/resources/image/background/create_mat/0.jpg deleted file mode 100644 index 42d8be37..00000000 Binary files a/resources/image/background/create_mat/0.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/1.jpg b/resources/image/background/create_mat/1.jpg deleted file mode 100644 index 72e1c661..00000000 Binary files a/resources/image/background/create_mat/1.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/10.jpg b/resources/image/background/create_mat/10.jpg deleted file mode 100644 index 418ed808..00000000 Binary files a/resources/image/background/create_mat/10.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/11.jpg b/resources/image/background/create_mat/11.jpg deleted file mode 100644 index 22535d58..00000000 Binary files a/resources/image/background/create_mat/11.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/2.jpg b/resources/image/background/create_mat/2.jpg deleted file mode 100644 index e970b9dc..00000000 Binary files a/resources/image/background/create_mat/2.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/3.jpg b/resources/image/background/create_mat/3.jpg deleted file mode 100644 index 924abb4d..00000000 Binary files a/resources/image/background/create_mat/3.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/4.jpg b/resources/image/background/create_mat/4.jpg deleted file mode 100644 index 639f5a86..00000000 Binary files a/resources/image/background/create_mat/4.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/5.jpg b/resources/image/background/create_mat/5.jpg deleted file mode 100644 index cb881c2f..00000000 Binary files a/resources/image/background/create_mat/5.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/6.jpg b/resources/image/background/create_mat/6.jpg deleted file mode 100644 index aa33cc8d..00000000 Binary files a/resources/image/background/create_mat/6.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/7.jpg b/resources/image/background/create_mat/7.jpg deleted file mode 100644 index 0ae2766c..00000000 Binary files a/resources/image/background/create_mat/7.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/8.jpg b/resources/image/background/create_mat/8.jpg deleted file mode 100644 index 12b49fa7..00000000 Binary files a/resources/image/background/create_mat/8.jpg and /dev/null differ diff --git a/resources/image/background/create_mat/9.jpg b/resources/image/background/create_mat/9.jpg deleted file mode 100644 index b6daafc0..00000000 Binary files a/resources/image/background/create_mat/9.jpg and /dev/null differ diff --git a/resources/image/background/help/simple_help/11.jpg b/resources/image/background/help/simple_help/11.jpg deleted file mode 100644 index 22535d58..00000000 Binary files a/resources/image/background/help/simple_help/11.jpg and /dev/null differ diff --git a/resources/image/background/help/simple_help/3.jpg b/resources/image/background/help/simple_help/3.jpg deleted file mode 100644 index 924abb4d..00000000 Binary files a/resources/image/background/help/simple_help/3.jpg and /dev/null differ diff --git a/resources/image/background/help/simple_help/8.jpg b/resources/image/background/help/simple_help/8.jpg deleted file mode 100644 index 12b49fa7..00000000 Binary files a/resources/image/background/help/simple_help/8.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/101475556_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/101475556_p0_master1200.jpg deleted file mode 100644 index b6107a8a..00000000 Binary files a/resources/image/csgo_cases/_background/shu/101475556_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/103669823_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/103669823_p0_master1200.jpg deleted file mode 100644 index ab0777b9..00000000 Binary files a/resources/image/csgo_cases/_background/shu/103669823_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/105365519_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/105365519_p0_master1200.jpg deleted file mode 100644 index 057e21cb..00000000 Binary files a/resources/image/csgo_cases/_background/shu/105365519_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/72073042_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/72073042_p0_master1200.jpg deleted file mode 100644 index 9feedd1f..00000000 Binary files a/resources/image/csgo_cases/_background/shu/72073042_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/96876583_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/96876583_p0_master1200.jpg deleted file mode 100644 index 0988f1a1..00000000 Binary files a/resources/image/csgo_cases/_background/shu/96876583_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/98991592_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/98991592_p0_master1200.jpg deleted file mode 100644 index 4f0475ab..00000000 Binary files a/resources/image/csgo_cases/_background/shu/98991592_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/csgo_cases/_background/shu/99057213_p0_master1200.jpg b/resources/image/csgo_cases/_background/shu/99057213_p0_master1200.jpg deleted file mode 100644 index 1432c548..00000000 Binary files a/resources/image/csgo_cases/_background/shu/99057213_p0_master1200.jpg and /dev/null differ diff --git a/resources/image/dayouxi/0.jpg b/resources/image/dayouxi/0.jpg deleted file mode 100644 index b9014286..00000000 Binary files a/resources/image/dayouxi/0.jpg and /dev/null differ diff --git a/resources/image/dayouxi/1.jpg b/resources/image/dayouxi/1.jpg deleted file mode 100644 index a49d1132..00000000 Binary files a/resources/image/dayouxi/1.jpg and /dev/null differ diff --git a/resources/image/dayouxi/2.jpg b/resources/image/dayouxi/2.jpg deleted file mode 100644 index 35a6bdab..00000000 Binary files a/resources/image/dayouxi/2.jpg and /dev/null differ diff --git a/resources/image/genshin/alc/back.png b/resources/image/genshin/alc/back.png deleted file mode 100644 index bce9428b..00000000 Binary files a/resources/image/genshin/alc/back.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000003.png b/resources/image/genshin/chars/10000003.png deleted file mode 100644 index ae6eab54..00000000 Binary files a/resources/image/genshin/chars/10000003.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000005.png b/resources/image/genshin/chars/10000005.png deleted file mode 100644 index bdea303a..00000000 Binary files a/resources/image/genshin/chars/10000005.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000006.png b/resources/image/genshin/chars/10000006.png deleted file mode 100644 index bbb04a67..00000000 Binary files a/resources/image/genshin/chars/10000006.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000007.png b/resources/image/genshin/chars/10000007.png deleted file mode 100644 index b3294257..00000000 Binary files a/resources/image/genshin/chars/10000007.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000014.png b/resources/image/genshin/chars/10000014.png deleted file mode 100644 index 7a6cc4d2..00000000 Binary files a/resources/image/genshin/chars/10000014.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000015.png b/resources/image/genshin/chars/10000015.png deleted file mode 100644 index 890539ab..00000000 Binary files a/resources/image/genshin/chars/10000015.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000016.png b/resources/image/genshin/chars/10000016.png deleted file mode 100644 index 1539860c..00000000 Binary files a/resources/image/genshin/chars/10000016.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000020.png b/resources/image/genshin/chars/10000020.png deleted file mode 100644 index df3ccc95..00000000 Binary files a/resources/image/genshin/chars/10000020.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000021.png b/resources/image/genshin/chars/10000021.png deleted file mode 100644 index 48a405b8..00000000 Binary files a/resources/image/genshin/chars/10000021.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000022.png b/resources/image/genshin/chars/10000022.png deleted file mode 100644 index f05317f4..00000000 Binary files a/resources/image/genshin/chars/10000022.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000023.png b/resources/image/genshin/chars/10000023.png deleted file mode 100644 index 151cf5d2..00000000 Binary files a/resources/image/genshin/chars/10000023.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000024.png b/resources/image/genshin/chars/10000024.png deleted file mode 100644 index b593ce11..00000000 Binary files a/resources/image/genshin/chars/10000024.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000025.png b/resources/image/genshin/chars/10000025.png deleted file mode 100644 index 3793fd44..00000000 Binary files a/resources/image/genshin/chars/10000025.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000027.png b/resources/image/genshin/chars/10000027.png deleted file mode 100644 index 36b9eef1..00000000 Binary files a/resources/image/genshin/chars/10000027.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000029.png b/resources/image/genshin/chars/10000029.png deleted file mode 100644 index e151f141..00000000 Binary files a/resources/image/genshin/chars/10000029.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000031.png b/resources/image/genshin/chars/10000031.png deleted file mode 100644 index b9c4d1a0..00000000 Binary files a/resources/image/genshin/chars/10000031.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000032.png b/resources/image/genshin/chars/10000032.png deleted file mode 100644 index 8a9ef694..00000000 Binary files a/resources/image/genshin/chars/10000032.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000033.png b/resources/image/genshin/chars/10000033.png deleted file mode 100644 index 7fc538ff..00000000 Binary files a/resources/image/genshin/chars/10000033.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000034.png b/resources/image/genshin/chars/10000034.png deleted file mode 100644 index 09aa4b24..00000000 Binary files a/resources/image/genshin/chars/10000034.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000035.png b/resources/image/genshin/chars/10000035.png deleted file mode 100644 index af654b1e..00000000 Binary files a/resources/image/genshin/chars/10000035.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000036.png b/resources/image/genshin/chars/10000036.png deleted file mode 100644 index dbad18e4..00000000 Binary files a/resources/image/genshin/chars/10000036.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000037.png b/resources/image/genshin/chars/10000037.png deleted file mode 100644 index 375a2ed8..00000000 Binary files a/resources/image/genshin/chars/10000037.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000039.png b/resources/image/genshin/chars/10000039.png deleted file mode 100644 index 53656d47..00000000 Binary files a/resources/image/genshin/chars/10000039.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000041.png b/resources/image/genshin/chars/10000041.png deleted file mode 100644 index c1d04836..00000000 Binary files a/resources/image/genshin/chars/10000041.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000042.png b/resources/image/genshin/chars/10000042.png deleted file mode 100644 index 80d12631..00000000 Binary files a/resources/image/genshin/chars/10000042.png and /dev/null differ diff --git a/resources/image/genshin/chars/10000043.png b/resources/image/genshin/chars/10000043.png deleted file mode 100644 index cd29e228..00000000 Binary files a/resources/image/genshin/chars/10000043.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000002.png b/resources/image/genshin/genshin_card/chars_ava/10000002.png deleted file mode 100644 index 97caa00a..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000002.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000003.png b/resources/image/genshin/genshin_card/chars_ava/10000003.png deleted file mode 100644 index 175185c2..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000003.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000005.png b/resources/image/genshin/genshin_card/chars_ava/10000005.png deleted file mode 100644 index bba7aa28..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000005.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000006.png b/resources/image/genshin/genshin_card/chars_ava/10000006.png deleted file mode 100644 index 6b023e88..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000006.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000007.png b/resources/image/genshin/genshin_card/chars_ava/10000007.png deleted file mode 100644 index c05d9c3b..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000007.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000014.png b/resources/image/genshin/genshin_card/chars_ava/10000014.png deleted file mode 100644 index a77ade17..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000014.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000015.png b/resources/image/genshin/genshin_card/chars_ava/10000015.png deleted file mode 100644 index ed537b67..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000015.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000016.png b/resources/image/genshin/genshin_card/chars_ava/10000016.png deleted file mode 100644 index 3e5611b5..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000016.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000020.png b/resources/image/genshin/genshin_card/chars_ava/10000020.png deleted file mode 100644 index e7162ea4..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000020.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000021.png b/resources/image/genshin/genshin_card/chars_ava/10000021.png deleted file mode 100644 index a147187b..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000021.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000022.png b/resources/image/genshin/genshin_card/chars_ava/10000022.png deleted file mode 100644 index 1882eaa1..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000022.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000023.png b/resources/image/genshin/genshin_card/chars_ava/10000023.png deleted file mode 100644 index 8c6baa8c..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000023.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000024.png b/resources/image/genshin/genshin_card/chars_ava/10000024.png deleted file mode 100644 index 5f1e9023..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000024.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000025.png b/resources/image/genshin/genshin_card/chars_ava/10000025.png deleted file mode 100644 index a70ce00e..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000025.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000026.png b/resources/image/genshin/genshin_card/chars_ava/10000026.png deleted file mode 100644 index 65054966..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000026.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000027.png b/resources/image/genshin/genshin_card/chars_ava/10000027.png deleted file mode 100644 index 0a772f9c..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000027.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000029.png b/resources/image/genshin/genshin_card/chars_ava/10000029.png deleted file mode 100644 index bf77d740..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000029.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000030.png b/resources/image/genshin/genshin_card/chars_ava/10000030.png deleted file mode 100644 index a2722639..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000030.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000031.png b/resources/image/genshin/genshin_card/chars_ava/10000031.png deleted file mode 100644 index 2cbd7952..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000031.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000032.png b/resources/image/genshin/genshin_card/chars_ava/10000032.png deleted file mode 100644 index 30726c17..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000032.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000033.png b/resources/image/genshin/genshin_card/chars_ava/10000033.png deleted file mode 100644 index 6aef4cc4..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000033.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000034.png b/resources/image/genshin/genshin_card/chars_ava/10000034.png deleted file mode 100644 index 2cfa0575..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000034.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000035.png b/resources/image/genshin/genshin_card/chars_ava/10000035.png deleted file mode 100644 index f0765f04..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000035.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000036.png b/resources/image/genshin/genshin_card/chars_ava/10000036.png deleted file mode 100644 index a49e462d..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000036.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000037.png b/resources/image/genshin/genshin_card/chars_ava/10000037.png deleted file mode 100644 index 34bf0ca8..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000037.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000038.png b/resources/image/genshin/genshin_card/chars_ava/10000038.png deleted file mode 100644 index e923d5e8..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000038.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000039.png b/resources/image/genshin/genshin_card/chars_ava/10000039.png deleted file mode 100644 index d2d99dfc..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000039.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000041.png b/resources/image/genshin/genshin_card/chars_ava/10000041.png deleted file mode 100644 index eb2595c6..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000041.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000042.png b/resources/image/genshin/genshin_card/chars_ava/10000042.png deleted file mode 100644 index a4fb807c..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000042.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000043.png b/resources/image/genshin/genshin_card/chars_ava/10000043.png deleted file mode 100644 index 0b50bcfd..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000043.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000044.png b/resources/image/genshin/genshin_card/chars_ava/10000044.png deleted file mode 100644 index 5c234088..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000044.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000045.png b/resources/image/genshin/genshin_card/chars_ava/10000045.png deleted file mode 100644 index 8a8808dc..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000045.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000046.png b/resources/image/genshin/genshin_card/chars_ava/10000046.png deleted file mode 100644 index bd8fd03a..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000046.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000047.png b/resources/image/genshin/genshin_card/chars_ava/10000047.png deleted file mode 100644 index c6b19bc6..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000047.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000048.png b/resources/image/genshin/genshin_card/chars_ava/10000048.png deleted file mode 100644 index d98790e6..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000048.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000049.png b/resources/image/genshin/genshin_card/chars_ava/10000049.png deleted file mode 100644 index f14cd9c9..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000049.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000050.png b/resources/image/genshin/genshin_card/chars_ava/10000050.png deleted file mode 100644 index 7e0d43b3..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000050.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000051.png b/resources/image/genshin/genshin_card/chars_ava/10000051.png deleted file mode 100644 index 35dcc104..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000051.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000052.png b/resources/image/genshin/genshin_card/chars_ava/10000052.png deleted file mode 100644 index 4c7247de..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000052.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000053.png b/resources/image/genshin/genshin_card/chars_ava/10000053.png deleted file mode 100644 index 8f5d584f..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000053.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000054.png b/resources/image/genshin/genshin_card/chars_ava/10000054.png deleted file mode 100644 index cef2d5e0..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000054.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000056.png b/resources/image/genshin/genshin_card/chars_ava/10000056.png deleted file mode 100644 index b39125d6..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000056.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/chars_ava/10000062.png b/resources/image/genshin/genshin_card/chars_ava/10000062.png deleted file mode 100644 index 7e0d43b3..00000000 Binary files a/resources/image/genshin/genshin_card/chars_ava/10000062.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/cover.png b/resources/image/genshin/genshin_card/cover.png deleted file mode 100644 index 63638182..00000000 Binary files a/resources/image/genshin/genshin_card/cover.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/element.png b/resources/image/genshin/genshin_card/element.png deleted file mode 100644 index 0b1491bd..00000000 Binary files a/resources/image/genshin/genshin_card/element.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/head.png b/resources/image/genshin/genshin_card/head.png deleted file mode 100644 index e72b4584..00000000 Binary files a/resources/image/genshin/genshin_card/head.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/homes/lock.png b/resources/image/genshin/genshin_card/homes/lock.png deleted file mode 100644 index eca4c6d5..00000000 Binary files a/resources/image/genshin/genshin_card/homes/lock.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/logo/璃月.png b/resources/image/genshin/genshin_card/logo/璃月.png deleted file mode 100644 index fc2754b8..00000000 Binary files a/resources/image/genshin/genshin_card/logo/璃月.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/logo/稻妻.png b/resources/image/genshin/genshin_card/logo/稻妻.png deleted file mode 100644 index 086a720f..00000000 Binary files a/resources/image/genshin/genshin_card/logo/稻妻.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/logo/蒙德.png b/resources/image/genshin/genshin_card/logo/蒙德.png deleted file mode 100644 index 0272b710..00000000 Binary files a/resources/image/genshin/genshin_card/logo/蒙德.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/logo/龙脊雪山.png b/resources/image/genshin/genshin_card/logo/龙脊雪山.png deleted file mode 100644 index 77e5afeb..00000000 Binary files a/resources/image/genshin/genshin_card/logo/龙脊雪山.png and /dev/null differ diff --git a/resources/image/genshin/genshin_card/middle.png b/resources/image/genshin/genshin_card/middle.png deleted file mode 100644 index 2b9f12b6..00000000 Binary files a/resources/image/genshin/genshin_card/middle.png and /dev/null differ diff --git a/resources/image/genshin/genshin_icon/box.png b/resources/image/genshin/genshin_icon/box.png deleted file mode 100644 index dadec344..00000000 Binary files a/resources/image/genshin/genshin_icon/box.png and /dev/null differ diff --git a/resources/image/genshin/genshin_icon/box_alpha.png b/resources/image/genshin/genshin_icon/box_alpha.png deleted file mode 100644 index 0c634fa2..00000000 Binary files a/resources/image/genshin/genshin_icon/box_alpha.png and /dev/null differ diff --git a/resources/image/genshin/genshin_memo/resin.png b/resources/image/genshin/genshin_memo/resin.png deleted file mode 100644 index 7eef3780..00000000 Binary files a/resources/image/genshin/genshin_memo/resin.png and /dev/null differ diff --git a/resources/image/genshin/genshin_memo/resin_discount.png b/resources/image/genshin/genshin_memo/resin_discount.png deleted file mode 100644 index 810e0af0..00000000 Binary files a/resources/image/genshin/genshin_memo/resin_discount.png and /dev/null differ diff --git a/resources/image/genshin/genshin_memo/task.png b/resources/image/genshin/genshin_memo/task.png deleted file mode 100644 index a3b415ef..00000000 Binary files a/resources/image/genshin/genshin_memo/task.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/UI_EmotionIcon5.png b/resources/image/genshin/texture2d/UI_EmotionIcon5.png deleted file mode 100644 index cefe8925..00000000 Binary files a/resources/image/genshin/texture2d/UI_EmotionIcon5.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/ba.png b/resources/image/genshin/texture2d/ba.png deleted file mode 100644 index 4167908b..00000000 Binary files a/resources/image/genshin/texture2d/ba.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/bar.png b/resources/image/genshin/texture2d/bar.png deleted file mode 100644 index 6a18255b..00000000 Binary files a/resources/image/genshin/texture2d/bar.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/bg_1.jpg b/resources/image/genshin/texture2d/bg_1.jpg deleted file mode 100644 index d74a58b7..00000000 Binary files a/resources/image/genshin/texture2d/bg_1.jpg and /dev/null differ diff --git a/resources/image/genshin/texture2d/cover.png b/resources/image/genshin/texture2d/cover.png deleted file mode 100644 index 63638182..00000000 Binary files a/resources/image/genshin/texture2d/cover.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/default_ava.png b/resources/image/genshin/texture2d/default_ava.png deleted file mode 100644 index dd30e1fe..00000000 Binary files a/resources/image/genshin/texture2d/default_ava.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/earth.png b/resources/image/genshin/texture2d/earth.png deleted file mode 100644 index bec1f3ca..00000000 Binary files a/resources/image/genshin/texture2d/earth.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/h.png b/resources/image/genshin/texture2d/h.png deleted file mode 100644 index c68e2a64..00000000 Binary files a/resources/image/genshin/texture2d/h.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/l.png b/resources/image/genshin/texture2d/l.png deleted file mode 100644 index 92492a60..00000000 Binary files a/resources/image/genshin/texture2d/l.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/level.png b/resources/image/genshin/texture2d/level.png deleted file mode 100644 index 15561957..00000000 Binary files a/resources/image/genshin/texture2d/level.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/level2.png b/resources/image/genshin/texture2d/level2.png deleted file mode 100644 index 9bc7dbd9..00000000 Binary files a/resources/image/genshin/texture2d/level2.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/luoxuan.png b/resources/image/genshin/texture2d/luoxuan.png deleted file mode 100644 index 5ec57368..00000000 Binary files a/resources/image/genshin/texture2d/luoxuan.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/p1.png b/resources/image/genshin/texture2d/p1.png deleted file mode 100644 index 913593f7..00000000 Binary files a/resources/image/genshin/texture2d/p1.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/staron.png b/resources/image/genshin/texture2d/staron.png deleted file mode 100644 index 96a2f449..00000000 Binary files a/resources/image/genshin/texture2d/staron.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/wind.png b/resources/image/genshin/texture2d/wind.png deleted file mode 100644 index 402fc9ed..00000000 Binary files a/resources/image/genshin/texture2d/wind.png and /dev/null differ diff --git a/resources/image/genshin/texture2d/wlevel.png b/resources/image/genshin/texture2d/wlevel.png deleted file mode 100644 index fb3e4391..00000000 Binary files a/resources/image/genshin/texture2d/wlevel.png and /dev/null differ diff --git a/resources/image/luoxiang/0.jpg b/resources/image/luoxiang/0.jpg deleted file mode 100644 index 7e686062..00000000 Binary files a/resources/image/luoxiang/0.jpg and /dev/null differ diff --git a/resources/image/luoxiang/1.jpg b/resources/image/luoxiang/1.jpg deleted file mode 100644 index d1daa34e..00000000 Binary files a/resources/image/luoxiang/1.jpg and /dev/null differ diff --git a/resources/image/luoxiang/2.jpg b/resources/image/luoxiang/2.jpg deleted file mode 100644 index fd3443ed..00000000 Binary files a/resources/image/luoxiang/2.jpg and /dev/null differ diff --git a/resources/image/luoxiang/4.jpg b/resources/image/luoxiang/4.jpg deleted file mode 100644 index f51477f9..00000000 Binary files a/resources/image/luoxiang/4.jpg and /dev/null differ diff --git a/resources/image/luoxiang/5.jpg b/resources/image/luoxiang/5.jpg deleted file mode 100644 index 3c3f2bda..00000000 Binary files a/resources/image/luoxiang/5.jpg and /dev/null differ diff --git a/resources/image/luoxiang/6.jpg b/resources/image/luoxiang/6.jpg deleted file mode 100644 index 8615116f..00000000 Binary files a/resources/image/luoxiang/6.jpg and /dev/null differ diff --git a/resources/image/luoxiang/7.jpg b/resources/image/luoxiang/7.jpg deleted file mode 100644 index 54d32c93..00000000 Binary files a/resources/image/luoxiang/7.jpg and /dev/null differ diff --git a/resources/image/noresult/0.jpg b/resources/image/noresult/0.jpg deleted file mode 100644 index d6366fea..00000000 Binary files a/resources/image/noresult/0.jpg and /dev/null differ diff --git a/resources/image/noresult/1.jpg b/resources/image/noresult/1.jpg deleted file mode 100644 index b1bfd26f..00000000 Binary files a/resources/image/noresult/1.jpg and /dev/null differ diff --git a/resources/image/noresult/2.jpg b/resources/image/noresult/2.jpg deleted file mode 100644 index a9dc0b90..00000000 Binary files a/resources/image/noresult/2.jpg and /dev/null differ diff --git a/resources/image/noresult/3.jpg b/resources/image/noresult/3.jpg deleted file mode 100644 index b1a8a1f7..00000000 Binary files a/resources/image/noresult/3.jpg and /dev/null differ diff --git a/resources/image/noresult/4.jpg b/resources/image/noresult/4.jpg deleted file mode 100644 index 96cf2742..00000000 Binary files a/resources/image/noresult/4.jpg and /dev/null differ diff --git a/resources/image/noresult/5.jpg b/resources/image/noresult/5.jpg deleted file mode 100644 index 443ce6b2..00000000 Binary files a/resources/image/noresult/5.jpg and /dev/null differ diff --git a/resources/image/other/btn_false.png b/resources/image/other/btn_false.png deleted file mode 100644 index b48a8280..00000000 Binary files a/resources/image/other/btn_false.png and /dev/null differ diff --git a/resources/image/other/btn_true.png b/resources/image/other/btn_true.png deleted file mode 100644 index 06d47784..00000000 Binary files a/resources/image/other/btn_true.png and /dev/null differ diff --git a/resources/image/other/daily_limit.png b/resources/image/other/daily_limit.png deleted file mode 100644 index dee99fc8..00000000 Binary files a/resources/image/other/daily_limit.png and /dev/null differ diff --git a/resources/image/other/discount.png b/resources/image/other/discount.png deleted file mode 100644 index 2a17bf4e..00000000 Binary files a/resources/image/other/discount.png and /dev/null differ diff --git a/resources/image/other/laopo.jpg b/resources/image/other/laopo.jpg deleted file mode 100644 index 163aa951..00000000 Binary files a/resources/image/other/laopo.jpg and /dev/null differ diff --git a/resources/image/other/luxun.jpg b/resources/image/other/luxun.jpg deleted file mode 100644 index 8b10eaf6..00000000 Binary files a/resources/image/other/luxun.jpg and /dev/null differ diff --git a/resources/image/other/shop.png b/resources/image/other/shop.png deleted file mode 100644 index d7597426..00000000 Binary files a/resources/image/other/shop.png and /dev/null differ diff --git a/resources/image/other/shop_text.png b/resources/image/other/shop_text.png deleted file mode 100644 index af5a2413..00000000 Binary files a/resources/image/other/shop_text.png and /dev/null differ diff --git a/resources/image/other/time.png b/resources/image/other/time.png deleted file mode 100644 index c795b9e4..00000000 Binary files a/resources/image/other/time.png and /dev/null differ diff --git a/resources/image/other/webtop.png b/resources/image/other/webtop.png deleted file mode 100644 index 6f2eb1ef..00000000 Binary files a/resources/image/other/webtop.png and /dev/null differ diff --git a/resources/image/pa/0.jpg b/resources/image/pa/0.jpg deleted file mode 100644 index 0f65122e..00000000 Binary files a/resources/image/pa/0.jpg and /dev/null differ diff --git a/resources/image/pa/1.jpg b/resources/image/pa/1.jpg deleted file mode 100644 index 4252526e..00000000 Binary files a/resources/image/pa/1.jpg and /dev/null differ diff --git a/resources/image/pa/10.jpg b/resources/image/pa/10.jpg deleted file mode 100644 index a60df705..00000000 Binary files a/resources/image/pa/10.jpg and /dev/null differ diff --git a/resources/image/pa/11.jpg b/resources/image/pa/11.jpg deleted file mode 100644 index 0a89bd45..00000000 Binary files a/resources/image/pa/11.jpg and /dev/null differ diff --git a/resources/image/pa/12.jpg b/resources/image/pa/12.jpg deleted file mode 100644 index b75ca7ff..00000000 Binary files a/resources/image/pa/12.jpg and /dev/null differ diff --git a/resources/image/pa/13.jpg b/resources/image/pa/13.jpg deleted file mode 100644 index a878f378..00000000 Binary files a/resources/image/pa/13.jpg and /dev/null differ diff --git a/resources/image/pa/14.jpg b/resources/image/pa/14.jpg deleted file mode 100644 index 85fb8dcc..00000000 Binary files a/resources/image/pa/14.jpg and /dev/null differ diff --git a/resources/image/pa/15.jpg b/resources/image/pa/15.jpg deleted file mode 100644 index 81e8d32e..00000000 Binary files a/resources/image/pa/15.jpg and /dev/null differ diff --git a/resources/image/pa/16.jpg b/resources/image/pa/16.jpg deleted file mode 100644 index 11e793bc..00000000 Binary files a/resources/image/pa/16.jpg and /dev/null differ diff --git a/resources/image/pa/17.jpg b/resources/image/pa/17.jpg deleted file mode 100644 index b45fa8c6..00000000 Binary files a/resources/image/pa/17.jpg and /dev/null differ diff --git a/resources/image/pa/18.jpg b/resources/image/pa/18.jpg deleted file mode 100644 index a7d6bcc4..00000000 Binary files a/resources/image/pa/18.jpg and /dev/null differ diff --git a/resources/image/pa/19.jpg b/resources/image/pa/19.jpg deleted file mode 100644 index e81191dd..00000000 Binary files a/resources/image/pa/19.jpg and /dev/null differ diff --git a/resources/image/pa/2.jpg b/resources/image/pa/2.jpg deleted file mode 100644 index adbe1e1b..00000000 Binary files a/resources/image/pa/2.jpg and /dev/null differ diff --git a/resources/image/pa/20.jpg b/resources/image/pa/20.jpg deleted file mode 100644 index d53d6bd5..00000000 Binary files a/resources/image/pa/20.jpg and /dev/null differ diff --git a/resources/image/pa/21.jpg b/resources/image/pa/21.jpg deleted file mode 100644 index 8bde156b..00000000 Binary files a/resources/image/pa/21.jpg and /dev/null differ diff --git a/resources/image/pa/22.jpg b/resources/image/pa/22.jpg deleted file mode 100644 index fbbeb049..00000000 Binary files a/resources/image/pa/22.jpg and /dev/null differ diff --git a/resources/image/pa/23.jpg b/resources/image/pa/23.jpg deleted file mode 100644 index a9b153ec..00000000 Binary files a/resources/image/pa/23.jpg and /dev/null differ diff --git a/resources/image/pa/24.jpg b/resources/image/pa/24.jpg deleted file mode 100644 index 6716e4f8..00000000 Binary files a/resources/image/pa/24.jpg and /dev/null differ diff --git a/resources/image/pa/25.jpg b/resources/image/pa/25.jpg deleted file mode 100644 index 7c17e8e4..00000000 Binary files a/resources/image/pa/25.jpg and /dev/null differ diff --git a/resources/image/pa/26.jpg b/resources/image/pa/26.jpg deleted file mode 100644 index 4106ba59..00000000 Binary files a/resources/image/pa/26.jpg and /dev/null differ diff --git a/resources/image/pa/27.jpg b/resources/image/pa/27.jpg deleted file mode 100644 index 2744b00c..00000000 Binary files a/resources/image/pa/27.jpg and /dev/null differ diff --git a/resources/image/pa/28.jpg b/resources/image/pa/28.jpg deleted file mode 100644 index 10b8822a..00000000 Binary files a/resources/image/pa/28.jpg and /dev/null differ diff --git a/resources/image/pa/29.jpg b/resources/image/pa/29.jpg deleted file mode 100644 index 0a0b4710..00000000 Binary files a/resources/image/pa/29.jpg and /dev/null differ diff --git a/resources/image/pa/3.jpg b/resources/image/pa/3.jpg deleted file mode 100644 index 22b43067..00000000 Binary files a/resources/image/pa/3.jpg and /dev/null differ diff --git a/resources/image/pa/30.jpg b/resources/image/pa/30.jpg deleted file mode 100644 index adab0893..00000000 Binary files a/resources/image/pa/30.jpg and /dev/null differ diff --git a/resources/image/pa/31.jpg b/resources/image/pa/31.jpg deleted file mode 100644 index 0736edf1..00000000 Binary files a/resources/image/pa/31.jpg and /dev/null differ diff --git a/resources/image/pa/32.jpg b/resources/image/pa/32.jpg deleted file mode 100644 index 8e4431b4..00000000 Binary files a/resources/image/pa/32.jpg and /dev/null differ diff --git a/resources/image/pa/33.jpg b/resources/image/pa/33.jpg deleted file mode 100644 index 9689547a..00000000 Binary files a/resources/image/pa/33.jpg and /dev/null differ diff --git a/resources/image/pa/34.jpg b/resources/image/pa/34.jpg deleted file mode 100644 index 4b25fdd5..00000000 Binary files a/resources/image/pa/34.jpg and /dev/null differ diff --git a/resources/image/pa/35.jpg b/resources/image/pa/35.jpg deleted file mode 100644 index 7772aa27..00000000 Binary files a/resources/image/pa/35.jpg and /dev/null differ diff --git a/resources/image/pa/36.jpg b/resources/image/pa/36.jpg deleted file mode 100644 index 4997e152..00000000 Binary files a/resources/image/pa/36.jpg and /dev/null differ diff --git a/resources/image/pa/37.jpg b/resources/image/pa/37.jpg deleted file mode 100644 index 8a145042..00000000 Binary files a/resources/image/pa/37.jpg and /dev/null differ diff --git a/resources/image/pa/38.jpg b/resources/image/pa/38.jpg deleted file mode 100644 index 98426dc3..00000000 Binary files a/resources/image/pa/38.jpg and /dev/null differ diff --git a/resources/image/pa/39.jpg b/resources/image/pa/39.jpg deleted file mode 100644 index 4b4254ff..00000000 Binary files a/resources/image/pa/39.jpg and /dev/null differ diff --git a/resources/image/pa/4.jpg b/resources/image/pa/4.jpg deleted file mode 100644 index c739aa67..00000000 Binary files a/resources/image/pa/4.jpg and /dev/null differ diff --git a/resources/image/pa/40.jpg b/resources/image/pa/40.jpg deleted file mode 100644 index 75259612..00000000 Binary files a/resources/image/pa/40.jpg and /dev/null differ diff --git a/resources/image/pa/41.jpg b/resources/image/pa/41.jpg deleted file mode 100644 index 9d1d8d5a..00000000 Binary files a/resources/image/pa/41.jpg and /dev/null differ diff --git a/resources/image/pa/42.jpg b/resources/image/pa/42.jpg deleted file mode 100644 index 7e92bad1..00000000 Binary files a/resources/image/pa/42.jpg and /dev/null differ diff --git a/resources/image/pa/43.jpg b/resources/image/pa/43.jpg deleted file mode 100644 index 24d5e06d..00000000 Binary files a/resources/image/pa/43.jpg and /dev/null differ diff --git a/resources/image/pa/44.jpg b/resources/image/pa/44.jpg deleted file mode 100644 index 6ccf41ba..00000000 Binary files a/resources/image/pa/44.jpg and /dev/null differ diff --git a/resources/image/pa/45.jpg b/resources/image/pa/45.jpg deleted file mode 100644 index 78923c34..00000000 Binary files a/resources/image/pa/45.jpg and /dev/null differ diff --git a/resources/image/pa/5.jpg b/resources/image/pa/5.jpg deleted file mode 100644 index 4c3194c9..00000000 Binary files a/resources/image/pa/5.jpg and /dev/null differ diff --git a/resources/image/pa/6.jpg b/resources/image/pa/6.jpg deleted file mode 100644 index accb1fa4..00000000 Binary files a/resources/image/pa/6.jpg and /dev/null differ diff --git a/resources/image/pa/7.jpg b/resources/image/pa/7.jpg deleted file mode 100644 index 0abed0b2..00000000 Binary files a/resources/image/pa/7.jpg and /dev/null differ diff --git a/resources/image/pa/8.jpg b/resources/image/pa/8.jpg deleted file mode 100644 index 10b8822a..00000000 Binary files a/resources/image/pa/8.jpg and /dev/null differ diff --git a/resources/image/pa/9.jpg b/resources/image/pa/9.jpg deleted file mode 100644 index 390b05c6..00000000 Binary files a/resources/image/pa/9.jpg and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_00.png b/resources/image/prts/redbag_1/redbag_00.png deleted file mode 100644 index 566d3f5c..00000000 Binary files a/resources/image/prts/redbag_1/redbag_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_01.png b/resources/image/prts/redbag_1/redbag_01.png deleted file mode 100644 index a3cb502e..00000000 Binary files a/resources/image/prts/redbag_1/redbag_01.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_02.png b/resources/image/prts/redbag_1/redbag_02.png deleted file mode 100644 index fff393f1..00000000 Binary files a/resources/image/prts/redbag_1/redbag_02.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_03.png b/resources/image/prts/redbag_1/redbag_03.png deleted file mode 100644 index e22f7c54..00000000 Binary files a/resources/image/prts/redbag_1/redbag_03.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_04.png b/resources/image/prts/redbag_1/redbag_04.png deleted file mode 100644 index b403c777..00000000 Binary files a/resources/image/prts/redbag_1/redbag_04.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_05.png b/resources/image/prts/redbag_1/redbag_05.png deleted file mode 100644 index 37637de8..00000000 Binary files a/resources/image/prts/redbag_1/redbag_05.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_06.png b/resources/image/prts/redbag_1/redbag_06.png deleted file mode 100644 index 68dee3bc..00000000 Binary files a/resources/image/prts/redbag_1/redbag_06.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_07.png b/resources/image/prts/redbag_1/redbag_07.png deleted file mode 100644 index a9673d98..00000000 Binary files a/resources/image/prts/redbag_1/redbag_07.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_08.png b/resources/image/prts/redbag_1/redbag_08.png deleted file mode 100644 index fc0cbbd8..00000000 Binary files a/resources/image/prts/redbag_1/redbag_08.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_09.png b/resources/image/prts/redbag_1/redbag_09.png deleted file mode 100644 index 9ba6a3f2..00000000 Binary files a/resources/image/prts/redbag_1/redbag_09.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_10.png b/resources/image/prts/redbag_1/redbag_10.png deleted file mode 100644 index c323e647..00000000 Binary files a/resources/image/prts/redbag_1/redbag_10.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_11.png b/resources/image/prts/redbag_1/redbag_11.png deleted file mode 100644 index b70bdb68..00000000 Binary files a/resources/image/prts/redbag_1/redbag_11.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_12.png b/resources/image/prts/redbag_1/redbag_12.png deleted file mode 100644 index 450ff2ed..00000000 Binary files a/resources/image/prts/redbag_1/redbag_12.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_13.png b/resources/image/prts/redbag_1/redbag_13.png deleted file mode 100644 index 0e6a0ef5..00000000 Binary files a/resources/image/prts/redbag_1/redbag_13.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_14.png b/resources/image/prts/redbag_1/redbag_14.png deleted file mode 100644 index fc3c6229..00000000 Binary files a/resources/image/prts/redbag_1/redbag_14.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_15.png b/resources/image/prts/redbag_1/redbag_15.png deleted file mode 100644 index bf408a46..00000000 Binary files a/resources/image/prts/redbag_1/redbag_15.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_16.png b/resources/image/prts/redbag_1/redbag_16.png deleted file mode 100644 index 45cd574e..00000000 Binary files a/resources/image/prts/redbag_1/redbag_16.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_17.png b/resources/image/prts/redbag_1/redbag_17.png deleted file mode 100644 index 0aa3593f..00000000 Binary files a/resources/image/prts/redbag_1/redbag_17.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_18.png b/resources/image/prts/redbag_1/redbag_18.png deleted file mode 100644 index cf5c42a2..00000000 Binary files a/resources/image/prts/redbag_1/redbag_18.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_19.png b/resources/image/prts/redbag_1/redbag_19.png deleted file mode 100644 index 3e9d1f4f..00000000 Binary files a/resources/image/prts/redbag_1/redbag_19.png and /dev/null differ diff --git a/resources/image/prts/redbag_1/redbag_20.png b/resources/image/prts/redbag_1/redbag_20.png deleted file mode 100644 index 05325c48..00000000 Binary files a/resources/image/prts/redbag_1/redbag_20.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/abeiduo_00.png b/resources/image/prts/redbag_2/abeiduo_00.png deleted file mode 100644 index eb2bfe10..00000000 Binary files a/resources/image/prts/redbag_2/abeiduo_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/chen_00.png b/resources/image/prts/redbag_2/chen_00.png deleted file mode 100644 index 9c6ded6e..00000000 Binary files a/resources/image/prts/redbag_2/chen_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/dusk_00.png b/resources/image/prts/redbag_2/dusk_00.png deleted file mode 100644 index ce1c2551..00000000 Binary files a/resources/image/prts/redbag_2/dusk_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/ganyu_00.png b/resources/image/prts/redbag_2/ganyu_00.png deleted file mode 100644 index e123591d..00000000 Binary files a/resources/image/prts/redbag_2/ganyu_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/ganyu_01.png b/resources/image/prts/redbag_2/ganyu_01.png deleted file mode 100644 index 83b2ac56..00000000 Binary files a/resources/image/prts/redbag_2/ganyu_01.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/gongzi_00.png b/resources/image/prts/redbag_2/gongzi_00.png deleted file mode 100644 index 7cba450d..00000000 Binary files a/resources/image/prts/redbag_2/gongzi_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/hutao_00.png b/resources/image/prts/redbag_2/hutao_00.png deleted file mode 100644 index 42954273..00000000 Binary files a/resources/image/prts/redbag_2/hutao_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/jessica_00.png b/resources/image/prts/redbag_2/jessica_00.png deleted file mode 100644 index 21413f70..00000000 Binary files a/resources/image/prts/redbag_2/jessica_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/keli_00.png b/resources/image/prts/redbag_2/keli_00.png deleted file mode 100644 index 661b1a5a..00000000 Binary files a/resources/image/prts/redbag_2/keli_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/keqing_00.png b/resources/image/prts/redbag_2/keqing_00.png deleted file mode 100644 index aa674d44..00000000 Binary files a/resources/image/prts/redbag_2/keqing_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/keqing_01.png b/resources/image/prts/redbag_2/keqing_01.png deleted file mode 100644 index 1a3874c5..00000000 Binary files a/resources/image/prts/redbag_2/keqing_01.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/lvxingzhe_00.png b/resources/image/prts/redbag_2/lvxingzhe_00.png deleted file mode 100644 index 371f3802..00000000 Binary files a/resources/image/prts/redbag_2/lvxingzhe_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/lvxingzhe_01.png b/resources/image/prts/redbag_2/lvxingzhe_01.png deleted file mode 100644 index 42f430ba..00000000 Binary files a/resources/image/prts/redbag_2/lvxingzhe_01.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/nian_00.png b/resources/image/prts/redbag_2/nian_00.png deleted file mode 100644 index fe2c8f83..00000000 Binary files a/resources/image/prts/redbag_2/nian_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/nuoaier_00.png b/resources/image/prts/redbag_2/nuoaier_00.png deleted file mode 100644 index 7a93dda1..00000000 Binary files a/resources/image/prts/redbag_2/nuoaier_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/qiqi_00.png b/resources/image/prts/redbag_2/qiqi_00.png deleted file mode 100644 index 30a2ccf2..00000000 Binary files a/resources/image/prts/redbag_2/qiqi_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/shenli_00.png b/resources/image/prts/redbag_2/shenli_00.png deleted file mode 100644 index f0a7cca5..00000000 Binary files a/resources/image/prts/redbag_2/shenli_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/sikadi_00.png b/resources/image/prts/redbag_2/sikadi_00.png deleted file mode 100644 index 151e6724..00000000 Binary files a/resources/image/prts/redbag_2/sikadi_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/tknogi_00.png b/resources/image/prts/redbag_2/tknogi_00.png deleted file mode 100644 index ad4b87b2..00000000 Binary files a/resources/image/prts/redbag_2/tknogi_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/wanye_00.png b/resources/image/prts/redbag_2/wanye_00.png deleted file mode 100644 index bf61f9b0..00000000 Binary files a/resources/image/prts/redbag_2/wanye_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/wendi_00.png b/resources/image/prts/redbag_2/wendi_00.png deleted file mode 100644 index 6a8120c7..00000000 Binary files a/resources/image/prts/redbag_2/wendi_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/xiao_00.png b/resources/image/prts/redbag_2/xiao_00.png deleted file mode 100644 index e3d1fc1c..00000000 Binary files a/resources/image/prts/redbag_2/xiao_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/xiao_01.png b/resources/image/prts/redbag_2/xiao_01.png deleted file mode 100644 index fce2cdce..00000000 Binary files a/resources/image/prts/redbag_2/xiao_01.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/xiaogong_00.png b/resources/image/prts/redbag_2/xiaogong_00.png deleted file mode 100644 index 1469e7dd..00000000 Binary files a/resources/image/prts/redbag_2/xiaogong_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/xingqiu_00.png b/resources/image/prts/redbag_2/xingqiu_00.png deleted file mode 100644 index d9e7b127..00000000 Binary files a/resources/image/prts/redbag_2/xingqiu_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/zhongli_00.png b/resources/image/prts/redbag_2/zhongli_00.png deleted file mode 100644 index b1385b3a..00000000 Binary files a/resources/image/prts/redbag_2/zhongli_00.png and /dev/null differ diff --git a/resources/image/prts/redbag_2/zhongli_01.png b/resources/image/prts/redbag_2/zhongli_01.png deleted file mode 100644 index 1cba9e30..00000000 Binary files a/resources/image/prts/redbag_2/zhongli_01.png and /dev/null differ diff --git a/resources/image/qxz/0.jpg b/resources/image/qxz/0.jpg deleted file mode 100644 index bbe52165..00000000 Binary files a/resources/image/qxz/0.jpg and /dev/null differ diff --git a/resources/image/qxz/1.jpg b/resources/image/qxz/1.jpg deleted file mode 100644 index b5e47d98..00000000 Binary files a/resources/image/qxz/1.jpg and /dev/null differ diff --git a/resources/image/shop_icon/favorability_card_1.png b/resources/image/shop_icon/favorability_card_1.png deleted file mode 100644 index 83b1c2b8..00000000 Binary files a/resources/image/shop_icon/favorability_card_1.png and /dev/null differ diff --git a/resources/image/shop_icon/favorability_card_2.png b/resources/image/shop_icon/favorability_card_2.png deleted file mode 100644 index d98ca598..00000000 Binary files a/resources/image/shop_icon/favorability_card_2.png and /dev/null differ diff --git a/resources/image/shop_icon/favorability_card_3.png b/resources/image/shop_icon/favorability_card_3.png deleted file mode 100644 index d2f8bdb3..00000000 Binary files a/resources/image/shop_icon/favorability_card_3.png and /dev/null differ diff --git a/resources/image/sign/sign_res/background/background_01.jpg b/resources/image/sign/sign_res/background/background_01.jpg deleted file mode 100644 index fe48e2e0..00000000 Binary files a/resources/image/sign/sign_res/background/background_01.jpg and /dev/null differ diff --git a/resources/image/sign/sign_res/bar.png b/resources/image/sign/sign_res/bar.png deleted file mode 100644 index 18b898d1..00000000 Binary files a/resources/image/sign/sign_res/bar.png and /dev/null differ diff --git a/resources/image/sign/sign_res/bar_white.png b/resources/image/sign/sign_res/bar_white.png deleted file mode 100644 index 2f3bcae4..00000000 Binary files a/resources/image/sign/sign_res/bar_white.png and /dev/null differ diff --git a/resources/image/sign/sign_res/border/ava_border_01.png b/resources/image/sign/sign_res/border/ava_border_01.png deleted file mode 100644 index b9af9cc2..00000000 Binary files a/resources/image/sign/sign_res/border/ava_border_01.png and /dev/null differ diff --git a/resources/image/sign/sign_res/border/gift_border_02.png b/resources/image/sign/sign_res/border/gift_border_02.png deleted file mode 100644 index 9d89b376..00000000 Binary files a/resources/image/sign/sign_res/border/gift_border_02.png and /dev/null differ diff --git a/resources/image/sign/sign_res/white.png b/resources/image/sign/sign_res/white.png deleted file mode 100644 index 366c89e4..00000000 Binary files a/resources/image/sign/sign_res/white.png and /dev/null differ diff --git a/resources/image/wordcloud/default.png b/resources/image/wordcloud/default.png deleted file mode 100644 index 7912ce8b..00000000 Binary files a/resources/image/wordcloud/default.png and /dev/null differ diff --git a/resources/image/zai/1.jpg b/resources/image/zai/1.jpg deleted file mode 100644 index f2643505..00000000 Binary files a/resources/image/zai/1.jpg and /dev/null differ diff --git a/resources/image/zai/2.gif b/resources/image/zai/2.gif deleted file mode 100644 index f28419dd..00000000 Binary files a/resources/image/zai/2.gif and /dev/null differ diff --git a/resources/image/zai/3.jpg b/resources/image/zai/3.jpg deleted file mode 100644 index fb39b9f9..00000000 Binary files a/resources/image/zai/3.jpg and /dev/null differ diff --git a/resources/image/zai/4.jpg b/resources/image/zai/4.jpg deleted file mode 100644 index 927419eb..00000000 Binary files a/resources/image/zai/4.jpg and /dev/null differ diff --git a/resources/image/zai/5.jpg b/resources/image/zai/5.jpg deleted file mode 100644 index b91a5a43..00000000 Binary files a/resources/image/zai/5.jpg and /dev/null differ diff --git a/resources/image/zhenxun.jpg b/resources/image/zhenxun.jpg deleted file mode 100644 index cccd123c..00000000 Binary files a/resources/image/zhenxun.jpg and /dev/null differ diff --git a/resources/image/zhenxun/bujiangli.jpg b/resources/image/zhenxun/bujiangli.jpg deleted file mode 100644 index abb2d2a2..00000000 Binary files a/resources/image/zhenxun/bujiangli.jpg and /dev/null differ diff --git a/resources/image/zhenxun/daojia.jpg b/resources/image/zhenxun/daojia.jpg deleted file mode 100644 index 371324ce..00000000 Binary files a/resources/image/zhenxun/daojia.jpg and /dev/null differ diff --git a/resources/image/zhenxun/dayouxi.png b/resources/image/zhenxun/dayouxi.png deleted file mode 100644 index 35a6bdab..00000000 Binary files a/resources/image/zhenxun/dayouxi.png and /dev/null differ diff --git a/resources/image/zhenxun/gezi.jpg b/resources/image/zhenxun/gezi.jpg deleted file mode 100644 index c93062c8..00000000 Binary files a/resources/image/zhenxun/gezi.jpg and /dev/null differ diff --git a/resources/image/zhenxun/haipa.jpg b/resources/image/zhenxun/haipa.jpg deleted file mode 100644 index 7986e492..00000000 Binary files a/resources/image/zhenxun/haipa.jpg and /dev/null differ diff --git a/resources/image/zhenxun/miao.jpg b/resources/image/zhenxun/miao.jpg deleted file mode 100644 index 927419eb..00000000 Binary files a/resources/image/zhenxun/miao.jpg and /dev/null differ diff --git a/resources/image/zhenxun/o.png b/resources/image/zhenxun/o.png deleted file mode 100644 index fb39b9f9..00000000 Binary files a/resources/image/zhenxun/o.png and /dev/null differ diff --git a/resources/image/zhenxun/qiang.jpg b/resources/image/zhenxun/qiang.jpg deleted file mode 100644 index b91a5a43..00000000 Binary files a/resources/image/zhenxun/qiang.jpg and /dev/null differ diff --git a/resources/image/zhenxun/shengqi.jpg b/resources/image/zhenxun/shengqi.jpg deleted file mode 100644 index 4c01cfbe..00000000 Binary files a/resources/image/zhenxun/shengqi.jpg and /dev/null differ diff --git a/resources/image/zhenxun/sleep.jpg b/resources/image/zhenxun/sleep.jpg deleted file mode 100644 index 250eb8e0..00000000 Binary files a/resources/image/zhenxun/sleep.jpg and /dev/null differ diff --git a/resources/image/zhenxun/sorry.jpg b/resources/image/zhenxun/sorry.jpg deleted file mode 100644 index 80f49da4..00000000 Binary files a/resources/image/zhenxun/sorry.jpg and /dev/null differ diff --git a/resources/image/zhenxun/toukan.png b/resources/image/zhenxun/toukan.png deleted file mode 100644 index 1ad98ca1..00000000 Binary files a/resources/image/zhenxun/toukan.png and /dev/null differ diff --git a/resources/image/zhenxun/toukan_2.png b/resources/image/zhenxun/toukan_2.png deleted file mode 100644 index 088907a9..00000000 Binary files a/resources/image/zhenxun/toukan_2.png and /dev/null differ diff --git a/resources/image/zhenxun/toukan_3.png b/resources/image/zhenxun/toukan_3.png deleted file mode 100644 index 53004fe6..00000000 Binary files a/resources/image/zhenxun/toukan_3.png and /dev/null differ diff --git a/resources/image/zhenxun/wa.jpg b/resources/image/zhenxun/wa.jpg deleted file mode 100644 index f2643505..00000000 Binary files a/resources/image/zhenxun/wa.jpg and /dev/null differ diff --git a/resources/image/zhenxun/wenhao.jpg b/resources/image/zhenxun/wenhao.jpg deleted file mode 100644 index c3544fdc..00000000 Binary files a/resources/image/zhenxun/wenhao.jpg and /dev/null differ diff --git a/resources/image/zhenxun/yiwen.jpg b/resources/image/zhenxun/yiwen.jpg deleted file mode 100644 index cc894f31..00000000 Binary files a/resources/image/zhenxun/yiwen.jpg and /dev/null differ diff --git a/resources/image/zhenxun/yun.jpg b/resources/image/zhenxun/yun.jpg deleted file mode 100644 index 234e318a..00000000 Binary files a/resources/image/zhenxun/yun.jpg and /dev/null differ diff --git a/resources/image/zhenxun/zao.jpg b/resources/image/zhenxun/zao.jpg deleted file mode 100644 index e8debba6..00000000 Binary files a/resources/image/zhenxun/zao.jpg and /dev/null differ diff --git a/resources/record/dinggong/01_你这家伙,找块豆腐一头撞死算了!_.mp3 b/resources/record/dinggong/01_你这家伙,找块豆腐一头撞死算了!_.mp3 deleted file mode 100644 index f25790e7..00000000 Binary files a/resources/record/dinggong/01_你这家伙,找块豆腐一头撞死算了!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/02_你还是老老实实定个闹钟起床啊!干吗每次都非要我来叫醒你嘛。_.mp3 b/resources/record/dinggong/02_你还是老老实实定个闹钟起床啊!干吗每次都非要我来叫醒你嘛。_.mp3 deleted file mode 100644 index 18954677..00000000 Binary files a/resources/record/dinggong/02_你还是老老实实定个闹钟起床啊!干吗每次都非要我来叫醒你嘛。_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/03_吵死了吵死了吵死了!真是吵死了!_.mp3 b/resources/record/dinggong/03_吵死了吵死了吵死了!真是吵死了!_.mp3 deleted file mode 100644 index 6d29f656..00000000 Binary files a/resources/record/dinggong/03_吵死了吵死了吵死了!真是吵死了!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/04_ 是我选的你,你可别以为是你给了我机会哦!_.mp3 b/resources/record/dinggong/04_ 是我选的你,你可别以为是你给了我机会哦!_.mp3 deleted file mode 100644 index afb7a487..00000000 Binary files a/resources/record/dinggong/04_ 是我选的你,你可别以为是你给了我机会哦!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/05_什么“早安”,应该说“您早上好”才对!_.mp3 b/resources/record/dinggong/05_什么“早安”,应该说“您早上好”才对!_.mp3 deleted file mode 100644 index 0b09ba71..00000000 Binary files a/resources/record/dinggong/05_什么“早安”,应该说“您早上好”才对!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/06_你可别胡思乱想_.mp3 b/resources/record/dinggong/06_你可别胡思乱想_.mp3 deleted file mode 100644 index 29bc6d0b..00000000 Binary files a/resources/record/dinggong/06_你可别胡思乱想_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/07_我才不是特地为你做的。_.mp3 b/resources/record/dinggong/07_我才不是特地为你做的。_.mp3 deleted file mode 100644 index 07e96425..00000000 Binary files a/resources/record/dinggong/07_我才不是特地为你做的。_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/08_ 接、接吻什么的,你还早了100年呢!_.mp3 b/resources/record/dinggong/08_ 接、接吻什么的,你还早了100年呢!_.mp3 deleted file mode 100644 index 35f02f54..00000000 Binary files a/resources/record/dinggong/08_ 接、接吻什么的,你还早了100年呢!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/09_… 哭…我的胸部…_.mp3 b/resources/record/dinggong/09_… 哭…我的胸部…_.mp3 deleted file mode 100644 index 24aa0d92..00000000 Binary files a/resources/record/dinggong/09_… 哭…我的胸部…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/10_要是你受伤的话,岂不是会让我很头疼。_.mp3 b/resources/record/dinggong/10_要是你受伤的话,岂不是会让我很头疼。_.mp3 deleted file mode 100644 index 2d06a4fc..00000000 Binary files a/resources/record/dinggong/10_要是你受伤的话,岂不是会让我很头疼。_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/11_你这个笨蛋!_.mp3 b/resources/record/dinggong/11_你这个笨蛋!_.mp3 deleted file mode 100644 index af73364a..00000000 Binary files a/resources/record/dinggong/11_你这个笨蛋!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/12_再见之类的话,我才懒得对你说…_.mp3 b/resources/record/dinggong/12_再见之类的话,我才懒得对你说…_.mp3 deleted file mode 100644 index af12b9b2..00000000 Binary files a/resources/record/dinggong/12_再见之类的话,我才懒得对你说…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/13_我才没有为你担心!_.mp3 b/resources/record/dinggong/13_我才没有为你担心!_.mp3 deleted file mode 100644 index b0875626..00000000 Binary files a/resources/record/dinggong/13_我才没有为你担心!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/14_我、我可不是因为自己喜欢才打扮成这样…只有在你面前罢了…_.mp3 b/resources/record/dinggong/14_我、我可不是因为自己喜欢才打扮成这样…只有在你面前罢了…_.mp3 deleted file mode 100644 index 352243e8..00000000 Binary files a/resources/record/dinggong/14_我、我可不是因为自己喜欢才打扮成这样…只有在你面前罢了…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/15_真是的…笨蛋…_.mp3 b/resources/record/dinggong/15_真是的…笨蛋…_.mp3 deleted file mode 100644 index b1ac6645..00000000 Binary files a/resources/record/dinggong/15_真是的…笨蛋…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/16_尽管放马过来吧!让我打你个落花流水!_.mp3 b/resources/record/dinggong/16_尽管放马过来吧!让我打你个落花流水!_.mp3 deleted file mode 100644 index 3a99930e..00000000 Binary files a/resources/record/dinggong/16_尽管放马过来吧!让我打你个落花流水!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/17_留在我身边!一辈子!没异议吧!_.mp3 b/resources/record/dinggong/17_留在我身边!一辈子!没异议吧!_.mp3 deleted file mode 100644 index 91e89e86..00000000 Binary files a/resources/record/dinggong/17_留在我身边!一辈子!没异议吧!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/18_生日礼物这种东西,我想没有其他人会送给你了吧…_.mp3 b/resources/record/dinggong/18_生日礼物这种东西,我想没有其他人会送给你了吧…_.mp3 deleted file mode 100644 index cb6286aa..00000000 Binary files a/resources/record/dinggong/18_生日礼物这种东西,我想没有其他人会送给你了吧…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/19_ hi~_.mp3 b/resources/record/dinggong/19_ hi~_.mp3 deleted file mode 100644 index 1aa14684..00000000 Binary files a/resources/record/dinggong/19_ hi~_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/20_这个送给你……_.mp3 b/resources/record/dinggong/20_这个送给你……_.mp3 deleted file mode 100644 index 1328bd2d..00000000 Binary files a/resources/record/dinggong/20_这个送给你……_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/21_给我等一下!你这个木头人!_.mp3 b/resources/record/dinggong/21_给我等一下!你这个木头人!_.mp3 deleted file mode 100644 index 27dddc54..00000000 Binary files a/resources/record/dinggong/21_给我等一下!你这个木头人!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/22_为什么…_.mp3 b/resources/record/dinggong/22_为什么…_.mp3 deleted file mode 100644 index 5ab7ce85..00000000 Binary files a/resources/record/dinggong/22_为什么…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/23_为什么你就是注意不到嘛!_.mp3 b/resources/record/dinggong/23_为什么你就是注意不到嘛!_.mp3 deleted file mode 100644 index b793ce9a..00000000 Binary files a/resources/record/dinggong/23_为什么你就是注意不到嘛!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/24_正好顺便,我也邀请一下你吧。_.mp3 b/resources/record/dinggong/24_正好顺便,我也邀请一下你吧。_.mp3 deleted file mode 100644 index 82e28bde..00000000 Binary files a/resources/record/dinggong/24_正好顺便,我也邀请一下你吧。_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/25_就算你不打电话给我,我也没什么寂寞的…_.mp3 b/resources/record/dinggong/25_就算你不打电话给我,我也没什么寂寞的…_.mp3 deleted file mode 100644 index 49efe1ee..00000000 Binary files a/resources/record/dinggong/25_就算你不打电话给我,我也没什么寂寞的…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/26_别离我这么近…丢死人了…_.mp3 b/resources/record/dinggong/26_别离我这么近…丢死人了…_.mp3 deleted file mode 100644 index 31fb93ac..00000000 Binary files a/resources/record/dinggong/26_别离我这么近…丢死人了…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/27_那、那家伙干什么嘛!_.mp3 b/resources/record/dinggong/27_那、那家伙干什么嘛!_.mp3 deleted file mode 100644 index 40ee4d75..00000000 Binary files a/resources/record/dinggong/27_那、那家伙干什么嘛!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/28_明明已经有我了的说!呜呜…_.mp3 b/resources/record/dinggong/28_明明已经有我了的说!呜呜…_.mp3 deleted file mode 100644 index 01674bcf..00000000 Binary files a/resources/record/dinggong/28_明明已经有我了的说!呜呜…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/29_别逃,至少听我把话说完!_.mp3 b/resources/record/dinggong/29_别逃,至少听我把话说完!_.mp3 deleted file mode 100644 index 3a848398..00000000 Binary files a/resources/record/dinggong/29_别逃,至少听我把话说完!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/30_ 真是的,不管啦笨蛋!!你让人家怎么说嘛!!_.mp3 b/resources/record/dinggong/30_ 真是的,不管啦笨蛋!!你让人家怎么说嘛!!_.mp3 deleted file mode 100644 index d3908649..00000000 Binary files a/resources/record/dinggong/30_ 真是的,不管啦笨蛋!!你让人家怎么说嘛!!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/31_你睡糊涂了吧,干什么呐!_.mp3 b/resources/record/dinggong/31_你睡糊涂了吧,干什么呐!_.mp3 deleted file mode 100644 index 9ddb9f9b..00000000 Binary files a/resources/record/dinggong/31_你睡糊涂了吧,干什么呐!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/32_你可不许对我说“No”哦_.mp3 b/resources/record/dinggong/32_你可不许对我说“No”哦_.mp3 deleted file mode 100644 index a1b8b816..00000000 Binary files a/resources/record/dinggong/32_你可不许对我说“No”哦_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/33_虽然很丢人,让、让、让你牵牵手还是可以容忍…来吧…_.mp3 b/resources/record/dinggong/33_虽然很丢人,让、让、让你牵牵手还是可以容忍…来吧…_.mp3 deleted file mode 100644 index bd797127..00000000 Binary files a/resources/record/dinggong/33_虽然很丢人,让、让、让你牵牵手还是可以容忍…来吧…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/34_睡、睡…睡在我的腿上!?只、只能睡1分钟哦!_.mp3 b/resources/record/dinggong/34_睡、睡…睡在我的腿上!?只、只能睡1分钟哦!_.mp3 deleted file mode 100644 index 4e142a02..00000000 Binary files a/resources/record/dinggong/34_睡、睡…睡在我的腿上!?只、只能睡1分钟哦!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/35_唔,今天只有我们两个人的话…_.mp3 b/resources/record/dinggong/35_唔,今天只有我们两个人的话…_.mp3 deleted file mode 100644 index 3bcc8350..00000000 Binary files a/resources/record/dinggong/35_唔,今天只有我们两个人的话…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/36_真是拿你没办法呢…_.mp3 b/resources/record/dinggong/36_真是拿你没办法呢…_.mp3 deleted file mode 100644 index 9a76b75a..00000000 Binary files a/resources/record/dinggong/36_真是拿你没办法呢…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/37_变态、变态、变态、变态、变态笨蛋大变态!!!_.mp3 b/resources/record/dinggong/37_变态、变态、变态、变态、变态笨蛋大变态!!!_.mp3 deleted file mode 100644 index 984b2b22..00000000 Binary files a/resources/record/dinggong/37_变态、变态、变态、变态、变态笨蛋大变态!!!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/38_真是的,不管了啦…_.mp3 b/resources/record/dinggong/38_真是的,不管了啦…_.mp3 deleted file mode 100644 index 1c3eddce..00000000 Binary files a/resources/record/dinggong/38_真是的,不管了啦…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/39_别在那装傻了啦!!真是的_.mp3 b/resources/record/dinggong/39_别在那装傻了啦!!真是的_.mp3 deleted file mode 100644 index 485be4a2..00000000 Binary files a/resources/record/dinggong/39_别在那装傻了啦!!真是的_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/40_小心我一脚踢飞你!_.mp3 b/resources/record/dinggong/40_小心我一脚踢飞你!_.mp3 deleted file mode 100644 index eeec9dc3..00000000 Binary files a/resources/record/dinggong/40_小心我一脚踢飞你!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/41_啊…也好,既然你坚持要带着…_.mp3 b/resources/record/dinggong/41_啊…也好,既然你坚持要带着…_.mp3 deleted file mode 100644 index 026da2a2..00000000 Binary files a/resources/record/dinggong/41_啊…也好,既然你坚持要带着…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/42_我也没办法了呢…_.mp3 b/resources/record/dinggong/42_我也没办法了呢…_.mp3 deleted file mode 100644 index 4be7d90b..00000000 Binary files a/resources/record/dinggong/42_我也没办法了呢…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/43_所以说我不想给别人看到啊!_.mp3 b/resources/record/dinggong/43_所以说我不想给别人看到啊!_.mp3 deleted file mode 100644 index 08625518..00000000 Binary files a/resources/record/dinggong/43_所以说我不想给别人看到啊!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/44_只想给你一个人…_.mp3 b/resources/record/dinggong/44_只想给你一个人…_.mp3 deleted file mode 100644 index 01abb82b..00000000 Binary files a/resources/record/dinggong/44_只想给你一个人…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/45_不准无视我!快给我道歉!_.mp3 b/resources/record/dinggong/45_不准无视我!快给我道歉!_.mp3 deleted file mode 100644 index dee1ed9b..00000000 Binary files a/resources/record/dinggong/45_不准无视我!快给我道歉!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/46_所以说只是从眼睛里流出来的汗啦!_.mp3 b/resources/record/dinggong/46_所以说只是从眼睛里流出来的汗啦!_.mp3 deleted file mode 100644 index 4b5de351..00000000 Binary files a/resources/record/dinggong/46_所以说只是从眼睛里流出来的汗啦!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/47_才~不~是眼泪呢!_.mp3 b/resources/record/dinggong/47_才~不~是眼泪呢!_.mp3 deleted file mode 100644 index c6b21564..00000000 Binary files a/resources/record/dinggong/47_才~不~是眼泪呢!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/48_真是的~笨蛋、笨蛋、笨蛋!!_.mp3 b/resources/record/dinggong/48_真是的~笨蛋、笨蛋、笨蛋!!_.mp3 deleted file mode 100644 index 34b91951..00000000 Binary files a/resources/record/dinggong/48_真是的~笨蛋、笨蛋、笨蛋!!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/49_不知道、不知道、不知道!_.mp3 b/resources/record/dinggong/49_不知道、不知道、不知道!_.mp3 deleted file mode 100644 index b446f1fb..00000000 Binary files a/resources/record/dinggong/49_不知道、不知道、不知道!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/50_才没有吃醋呢,你在说什么傻话啊!_.mp3 b/resources/record/dinggong/50_才没有吃醋呢,你在说什么傻话啊!_.mp3 deleted file mode 100644 index cc5e8f8e..00000000 Binary files a/resources/record/dinggong/50_才没有吃醋呢,你在说什么傻话啊!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/51_所以说我最讨厌优柔寡断的家伙了!_.mp3 b/resources/record/dinggong/51_所以说我最讨厌优柔寡断的家伙了!_.mp3 deleted file mode 100644 index 5cdaf551..00000000 Binary files a/resources/record/dinggong/51_所以说我最讨厌优柔寡断的家伙了!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/52_才不好呢。_.mp3 b/resources/record/dinggong/52_才不好呢。_.mp3 deleted file mode 100644 index 296af41d..00000000 Binary files a/resources/record/dinggong/52_才不好呢。_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/53_只、只准你看着我哦!_.mp3 b/resources/record/dinggong/53_只、只准你看着我哦!_.mp3 deleted file mode 100644 index 2902c7e2..00000000 Binary files a/resources/record/dinggong/53_只、只准你看着我哦!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/54_所以说别做些不正经的事了…对好不容易才和你在一起的我…_.mp3 b/resources/record/dinggong/54_所以说别做些不正经的事了…对好不容易才和你在一起的我…_.mp3 deleted file mode 100644 index fd0a0c24..00000000 Binary files a/resources/record/dinggong/54_所以说别做些不正经的事了…对好不容易才和你在一起的我…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/55_对你说我喜欢你还是不讨厌你什么的_.mp3 b/resources/record/dinggong/55_对你说我喜欢你还是不讨厌你什么的_.mp3 deleted file mode 100644 index 8ae13cf2..00000000 Binary files a/resources/record/dinggong/55_对你说我喜欢你还是不讨厌你什么的_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/56_拉面都泡糟了啦!快点吃掉啦!_.mp3 b/resources/record/dinggong/56_拉面都泡糟了啦!快点吃掉啦!_.mp3 deleted file mode 100644 index cbd38279..00000000 Binary files a/resources/record/dinggong/56_拉面都泡糟了啦!快点吃掉啦!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/57_诶,让我帮你吹吹?笨蛋啊你~_.mp3 b/resources/record/dinggong/57_诶,让我帮你吹吹?笨蛋啊你~_.mp3 deleted file mode 100644 index d2bf4353..00000000 Binary files a/resources/record/dinggong/57_诶,让我帮你吹吹?笨蛋啊你~_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/58_我才没有觉得你很有用呢…_.mp3 b/resources/record/dinggong/58_我才没有觉得你很有用呢…_.mp3 deleted file mode 100644 index 02c09cd0..00000000 Binary files a/resources/record/dinggong/58_我才没有觉得你很有用呢…_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/59_我才没有很开心呢!_.mp3 b/resources/record/dinggong/59_我才没有很开心呢!_.mp3 deleted file mode 100644 index 6c3a7ba3..00000000 Binary files a/resources/record/dinggong/59_我才没有很开心呢!_.mp3 and /dev/null differ diff --git a/resources/record/dinggong/60_要多多的和我联系!不然我会担心你的…_.mp3 b/resources/record/dinggong/60_要多多的和我联系!不然我会担心你的…_.mp3 deleted file mode 100644 index e27d74d8..00000000 Binary files a/resources/record/dinggong/60_要多多的和我联系!不然我会担心你的…_.mp3 and /dev/null differ diff --git a/resources/record/temp/bd_yysb.wav b/resources/record/temp/bd_yysb.wav deleted file mode 100644 index 53ed2a20..00000000 Binary files a/resources/record/temp/bd_yysb.wav and /dev/null differ diff --git a/resources/template/menu/colorList.json b/resources/template/menu/colorList.json deleted file mode 100644 index 451db7e5..00000000 --- a/resources/template/menu/colorList.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "color":[ - "#eea2a4", - "#621d34", - "#e0c8d1", - "#8b2671", - "#142334", - "#2b73af", - "#93b5cf", - "#2474b5", - "#baccd9", - "#1781b5", - "#5cb3cc", - "#57c3c2", - "#1ba784", - "#92b3a5", - "#2bae85", - "#83cbac", - "#41ae3c", - "#d0deaa", - "#d2b42c", - "#d2b116", - "#f8df72", - "#645822", - "#ddc871", - "#f9d770", - "#d9a40e", - "#b78b26", - "#5d3d21", - "#f8b37f", - "#945833", - "#e8b49a", - "#a6522c", - "#8b614d", - "#f68c60", - "#f6cec1", - "#eeaa9c", - "#862617", - "#f2b9b2", - "#f1908c" - ] -} diff --git a/resources/template/menu/data.json b/resources/template/menu/data.json deleted file mode 100644 index 647c0305..00000000 --- a/resources/template/menu/data.json +++ /dev/null @@ -1,381 +0,0 @@ -{ - "data": - [ - { - "plugin_type":"功能", - "items":[ - { - "plugin_name":"AI", - "plugin_sta":"0" - }, - { - "plugin_name":"B站订阅", - "plugin_sta":"0" - }, - { - "plugin_name":"apex查询", - "plugin_sta":"0" - }, - { - "plugin_name":"coser", - "plugin_sta":"0" - }, - { - "plugin_name":"epic免费游戏", - "plugin_sta":"0" - }, - { - "plugin_name":"pid搜索", - "plugin_sta":"0" - }, - { - "plugin_name":"roll", - "plugin_sta":"0" - }, - { - "plugin_name":"一言二次元语录", - "plugin_sta":"0" - }, - { - "plugin_name":"个人信息权限查看", - "plugin_sta":"0" - }, - { - "plugin_name":"古诗", - "plugin_sta":"0" - }, - { - "plugin_name":"吃饭小助手", - "plugin_sta":"0" - }, - { - "plugin_name":"微博热搜", - "plugin_sta":"0" - }, - { - "plugin_name":"我有一个朋友", - "plugin_sta":"0" - }, - { - "plugin_name":"昵称系统", - "plugin_sta":"0" - }, - { - "plugin_name":"更新信息", - "plugin_sta":"0" - }, - { - "plugin_name":"构造分享信息", - "plugin_sta":"0" - }, - { - "plugin_name":"查看pix图库", - "plugin_sta":"0" - }, - { - "plugin_name":"查看群欢迎消息", - "plugin_sta":"0" - }, - { - "plugin_name":"磁力搜索", - "plugin_sta":"0" - }, - { - "plugin_name":"签到", - "plugin_sta":"0" - }, - { - "plugin_name":"网易云热评", - "plugin_sta":"0" - }, - { - "plugin_name":"能不能好好说话", - "plugin_sta":"0" - }, - { - "plugin_name":"金币红包", - "plugin_sta":"0" - }, - { - "plugin_name":"骂我", - "plugin_sta":"0" - }, - { - "plugin_name":"鲁迅说", - "plugin_sta":"0" - }, - { - "plugin_name":"鸡汤", - "plugin_sta":"0" - }, - { - "plugin_name":"黑白草图", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"原神相关", - "items":[ - { - "plugin_name":"原神今日素材", - "plugin_sta":"1" - }, - { - "plugin_name":"原神便笺查询", - "plugin_sta":"1" - }, - { - "plugin_name":"原神树脂提醒", - "plugin_sta":"1" - }, - { - "plugin_name":"原神玩家查询", - "plugin_sta":"0" - }, - { - "plugin_name":"原神绑定", - "plugin_sta":"1" - }, - { - "plugin_name":"原神老黄历", - "plugin_sta":"0" - }, - { - "plugin_name":"原神自动签到", - "plugin_sta":"1" - }, - { - "plugin_name":"原神资源查询", - "plugin_sta":"1" - } - ] - }, - { - "plugin_type":"联系管理员", - "items":[ - { - "plugin_name":"联系管理员", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"功能", - "items":[ - { - "plugin_name":"每日发癫", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"常规插件", - "items":[ - { - "plugin_name":"消息防撤回", - "plugin_sta":"2" - } - ] - }, - { - "plugin_type":"抽卡相关", - "items":[ - { - "plugin_name":"开箱", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"来点好康的", - "items":[ - { - "plugin_name":"PIX", - "plugin_sta":"0" - }, - { - "plugin_name":"P站排行/搜图", - "plugin_sta":"0" - }, - { - "plugin_name":"本地图库", - "plugin_sta":"0" - }, - { - "plugin_name":"色图", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"数据统计", - "items":[ - { - "plugin_name":"功能调用统计可视化", - "plugin_sta":"0" - }, - { - "plugin_name":"消息统计", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"一些工具", - "items":[ - { - "plugin_name":"BUFF查询皮肤", - "plugin_sta":"0" - }, - { - "plugin_name":"b封面", - "plugin_sta":"0" - }, - { - "plugin_name":"各种图片简易操作", - "plugin_sta":"0" - }, - { - "plugin_name":"天气查询", - "plugin_sta":"0" - }, - { - "plugin_name":"小人举牌", - "plugin_sta":"0" - }, - { - "plugin_name":"搜番", - "plugin_sta":"0" - }, - { - "plugin_name":"来个猫猫", - "plugin_sta":"0" - }, - { - "plugin_name":"点歌", - "plugin_sta":"0" - }, - { - "plugin_name":"疫情查询", - "plugin_sta":"0" - }, - { - "plugin_name":"翻译", - "plugin_sta":"0" - }, - { - "plugin_name":"识图", - "plugin_sta":"0" - }, - { - "plugin_name":"识番", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"商店", - "items":[ - { - "plugin_name":"商店", - "plugin_sta":"0" - }, - { - "plugin_name":"商店 —— 使用道具", - "plugin_sta":"0" - }, - { - "plugin_name":"商店 —— 我的道具", - "plugin_sta":"0" - }, - { - "plugin_name":"商店 —— 我的金币", - "plugin_sta":"0" - }, - { - "plugin_name":"商店 —— 购买道具", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"其它", - "items":[ - { - "plugin_name":"关于", - "plugin_sta":"0" - }, - { - "plugin_name":"复读", - "plugin_sta":"0" - }, - { - "plugin_name":"戳一戳", - "plugin_sta":"0" - }, - { - "plugin_name":"敏感词检测", - "plugin_sta":"0" - } - ] - }, - { - "plugin_type":"群内小游戏", - "items":[ - { - "plugin_name":"21点", - "plugin_sta":"0" - }, - { - "plugin_name":"cp小故事", - "plugin_sta":"0" - }, - { - "plugin_name":"logo制作", - "plugin_sta":"0" - }, - { - "plugin_name":"乞讨福利金", - "plugin_sta":"0" - }, - { - "plugin_name":"人生重开 —— lifeRestart", - "plugin_sta":"0" - }, - { - "plugin_name":"俄罗斯轮盘", - "plugin_sta":"0" - }, - { - "plugin_name":"刮刮乐", - "plugin_sta":"0" - }, - { - "plugin_name":"头像表情包", - "plugin_sta":"0" - }, - { - "plugin_name":"打劫", - "plugin_sta":"0" - }, - { - "plugin_name":"猜单词", - "plugin_sta":"0" - }, - { - "plugin_name":"猜成语", - "plugin_sta":"0" - }, - { - "plugin_name":"表情包制作", - "plugin_sta":"0" - }, - { - "plugin_name":"银行", - "plugin_sta":"0" - } - ] - } - ] -} \ No newline at end of file diff --git a/resources/template/menu/res/font-awesome/css/font-awesome.css b/resources/template/menu/res/font-awesome/css/font-awesome.css deleted file mode 100644 index ee906a81..00000000 --- a/resources/template/menu/res/font-awesome/css/font-awesome.css +++ /dev/null @@ -1,2337 +0,0 @@ -/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */ -/* FONT PATH - * -------------------------- */ -@font-face { - font-family: 'FontAwesome'; - src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); - src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); - font-weight: normal; - font-style: normal; -} -.fa { - display: inline-block; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -/* makes the font 33% larger relative to the icon container */ -.fa-lg { - font-size: 1.33333333em; - line-height: 0.75em; - vertical-align: -15%; -} -.fa-2x { - font-size: 2em; -} -.fa-3x { - font-size: 3em; -} -.fa-4x { - font-size: 4em; -} -.fa-5x { - font-size: 5em; -} -.fa-fw { - width: 1.28571429em; - text-align: center; -} -.fa-ul { - padding-left: 0; - margin-left: 2.14285714em; - list-style-type: none; -} -.fa-ul > li { - position: relative; -} -.fa-li { - position: absolute; - left: -2.14285714em; - width: 2.14285714em; - top: 0.14285714em; - text-align: center; -} -.fa-li.fa-lg { - left: -1.85714286em; -} -.fa-border { - padding: .2em .25em .15em; - border: solid 0.08em #eeeeee; - border-radius: .1em; -} -.fa-pull-left { - float: left; -} -.fa-pull-right { - float: right; -} -.fa.fa-pull-left { - margin-right: .3em; -} -.fa.fa-pull-right { - margin-left: .3em; -} -/* Deprecated as of 4.4.0 */ -.pull-right { - float: right; -} -.pull-left { - float: left; -} -.fa.pull-left { - margin-right: .3em; -} -.fa.pull-right { - margin-left: .3em; -} -.fa-spin { - -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; -} -.fa-pulse { - -webkit-animation: fa-spin 1s infinite steps(8); - animation: fa-spin 1s infinite steps(8); -} -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -.fa-rotate-90 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); -} -.fa-rotate-180 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; - -webkit-transform: rotate(180deg); - -ms-transform: rotate(180deg); - transform: rotate(180deg); -} -.fa-rotate-270 { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; - -webkit-transform: rotate(270deg); - -ms-transform: rotate(270deg); - transform: rotate(270deg); -} -.fa-flip-horizontal { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; - -webkit-transform: scale(-1, 1); - -ms-transform: scale(-1, 1); - transform: scale(-1, 1); -} -.fa-flip-vertical { - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; - -webkit-transform: scale(1, -1); - -ms-transform: scale(1, -1); - transform: scale(1, -1); -} -:root .fa-rotate-90, -:root .fa-rotate-180, -:root .fa-rotate-270, -:root .fa-flip-horizontal, -:root .fa-flip-vertical { - filter: none; -} -.fa-stack { - position: relative; - display: inline-block; - width: 2em; - height: 2em; - line-height: 2em; - vertical-align: middle; -} -.fa-stack-1x, -.fa-stack-2x { - position: absolute; - left: 0; - width: 100%; - text-align: center; -} -.fa-stack-1x { - line-height: inherit; -} -.fa-stack-2x { - font-size: 2em; -} -.fa-inverse { - color: #ffffff; -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.fa-glass:before { - content: "\f000"; -} -.fa-music:before { - content: "\f001"; -} -.fa-search:before { - content: "\f002"; -} -.fa-envelope-o:before { - content: "\f003"; -} -.fa-heart:before { - content: "\f004"; -} -.fa-star:before { - content: "\f005"; -} -.fa-star-o:before { - content: "\f006"; -} -.fa-user:before { - content: "\f007"; -} -.fa-film:before { - content: "\f008"; -} -.fa-th-large:before { - content: "\f009"; -} -.fa-th:before { - content: "\f00a"; -} -.fa-th-list:before { - content: "\f00b"; -} -.fa-check:before { - content: "\f00c"; -} -.fa-remove:before, -.fa-close:before, -.fa-times:before { - content: "\f00d"; -} -.fa-search-plus:before { - content: "\f00e"; -} -.fa-search-minus:before { - content: "\f010"; -} -.fa-power-off:before { - content: "\f011"; -} -.fa-signal:before { - content: "\f012"; -} -.fa-gear:before, -.fa-cog:before { - content: "\f013"; -} -.fa-trash-o:before { - content: "\f014"; -} -.fa-home:before { - content: "\f015"; -} -.fa-file-o:before { - content: "\f016"; -} -.fa-clock-o:before { - content: "\f017"; -} -.fa-road:before { - content: "\f018"; -} -.fa-download:before { - content: "\f019"; -} -.fa-arrow-circle-o-down:before { - content: "\f01a"; -} -.fa-arrow-circle-o-up:before { - content: "\f01b"; -} -.fa-inbox:before { - content: "\f01c"; -} -.fa-play-circle-o:before { - content: "\f01d"; -} -.fa-rotate-right:before, -.fa-repeat:before { - content: "\f01e"; -} -.fa-refresh:before { - content: "\f021"; -} -.fa-list-alt:before { - content: "\f022"; -} -.fa-lock:before { - content: "\f023"; -} -.fa-flag:before { - content: "\f024"; -} -.fa-headphones:before { - content: "\f025"; -} -.fa-volume-off:before { - content: "\f026"; -} -.fa-volume-down:before { - content: "\f027"; -} -.fa-volume-up:before { - content: "\f028"; -} -.fa-qrcode:before { - content: "\f029"; -} -.fa-barcode:before { - content: "\f02a"; -} -.fa-tag:before { - content: "\f02b"; -} -.fa-tags:before { - content: "\f02c"; -} -.fa-book:before { - content: "\f02d"; -} -.fa-bookmark:before { - content: "\f02e"; -} -.fa-print:before { - content: "\f02f"; -} -.fa-camera:before { - content: "\f030"; -} -.fa-font:before { - content: "\f031"; -} -.fa-bold:before { - content: "\f032"; -} -.fa-italic:before { - content: "\f033"; -} -.fa-text-height:before { - content: "\f034"; -} -.fa-text-width:before { - content: "\f035"; -} -.fa-align-left:before { - content: "\f036"; -} -.fa-align-center:before { - content: "\f037"; -} -.fa-align-right:before { - content: "\f038"; -} -.fa-align-justify:before { - content: "\f039"; -} -.fa-list:before { - content: "\f03a"; -} -.fa-dedent:before, -.fa-outdent:before { - content: "\f03b"; -} -.fa-indent:before { - content: "\f03c"; -} -.fa-video-camera:before { - content: "\f03d"; -} -.fa-photo:before, -.fa-image:before, -.fa-picture-o:before { - content: "\f03e"; -} -.fa-pencil:before { - content: "\f040"; -} -.fa-map-marker:before { - content: "\f041"; -} -.fa-adjust:before { - content: "\f042"; -} -.fa-tint:before { - content: "\f043"; -} -.fa-edit:before, -.fa-pencil-square-o:before { - content: "\f044"; -} -.fa-share-square-o:before { - content: "\f045"; -} -.fa-check-square-o:before { - content: "\f046"; -} -.fa-arrows:before { - content: "\f047"; -} -.fa-step-backward:before { - content: "\f048"; -} -.fa-fast-backward:before { - content: "\f049"; -} -.fa-backward:before { - content: "\f04a"; -} -.fa-play:before { - content: "\f04b"; -} -.fa-pause:before { - content: "\f04c"; -} -.fa-stop:before { - content: "\f04d"; -} -.fa-forward:before { - content: "\f04e"; -} -.fa-fast-forward:before { - content: "\f050"; -} -.fa-step-forward:before { - content: "\f051"; -} -.fa-eject:before { - content: "\f052"; -} -.fa-chevron-left:before { - content: "\f053"; -} -.fa-chevron-right:before { - content: "\f054"; -} -.fa-plus-circle:before { - content: "\f055"; -} -.fa-minus-circle:before { - content: "\f056"; -} -.fa-times-circle:before { - content: "\f057"; -} -.fa-check-circle:before { - content: "\f058"; -} -.fa-question-circle:before { - content: "\f059"; -} -.fa-info-circle:before { - content: "\f05a"; -} -.fa-crosshairs:before { - content: "\f05b"; -} -.fa-times-circle-o:before { - content: "\f05c"; -} -.fa-check-circle-o:before { - content: "\f05d"; -} -.fa-ban:before { - content: "\f05e"; -} -.fa-arrow-left:before { - content: "\f060"; -} -.fa-arrow-right:before { - content: "\f061"; -} -.fa-arrow-up:before { - content: "\f062"; -} -.fa-arrow-down:before { - content: "\f063"; -} -.fa-mail-forward:before, -.fa-share:before { - content: "\f064"; -} -.fa-expand:before { - content: "\f065"; -} -.fa-compress:before { - content: "\f066"; -} -.fa-plus:before { - content: "\f067"; -} -.fa-minus:before { - content: "\f068"; -} -.fa-asterisk:before { - content: "\f069"; -} -.fa-exclamation-circle:before { - content: "\f06a"; -} -.fa-gift:before { - content: "\f06b"; -} -.fa-leaf:before { - content: "\f06c"; -} -.fa-fire:before { - content: "\f06d"; -} -.fa-eye:before { - content: "\f06e"; -} -.fa-eye-slash:before { - content: "\f070"; -} -.fa-warning:before, -.fa-exclamation-triangle:before { - content: "\f071"; -} -.fa-plane:before { - content: "\f072"; -} -.fa-calendar:before { - content: "\f073"; -} -.fa-random:before { - content: "\f074"; -} -.fa-comment:before { - content: "\f075"; -} -.fa-magnet:before { - content: "\f076"; -} -.fa-chevron-up:before { - content: "\f077"; -} -.fa-chevron-down:before { - content: "\f078"; -} -.fa-retweet:before { - content: "\f079"; -} -.fa-shopping-cart:before { - content: "\f07a"; -} -.fa-folder:before { - content: "\f07b"; -} -.fa-folder-open:before { - content: "\f07c"; -} -.fa-arrows-v:before { - content: "\f07d"; -} -.fa-arrows-h:before { - content: "\f07e"; -} -.fa-bar-chart-o:before, -.fa-bar-chart:before { - content: "\f080"; -} -.fa-twitter-square:before { - content: "\f081"; -} -.fa-facebook-square:before { - content: "\f082"; -} -.fa-camera-retro:before { - content: "\f083"; -} -.fa-key:before { - content: "\f084"; -} -.fa-gears:before, -.fa-cogs:before { - content: "\f085"; -} -.fa-comments:before { - content: "\f086"; -} -.fa-thumbs-o-up:before { - content: "\f087"; -} -.fa-thumbs-o-down:before { - content: "\f088"; -} -.fa-star-half:before { - content: "\f089"; -} -.fa-heart-o:before { - content: "\f08a"; -} -.fa-sign-out:before { - content: "\f08b"; -} -.fa-linkedin-square:before { - content: "\f08c"; -} -.fa-thumb-tack:before { - content: "\f08d"; -} -.fa-external-link:before { - content: "\f08e"; -} -.fa-sign-in:before { - content: "\f090"; -} -.fa-trophy:before { - content: "\f091"; -} -.fa-github-square:before { - content: "\f092"; -} -.fa-upload:before { - content: "\f093"; -} -.fa-lemon-o:before { - content: "\f094"; -} -.fa-phone:before { - content: "\f095"; -} -.fa-square-o:before { - content: "\f096"; -} -.fa-bookmark-o:before { - content: "\f097"; -} -.fa-phone-square:before { - content: "\f098"; -} -.fa-twitter:before { - content: "\f099"; -} -.fa-facebook-f:before, -.fa-facebook:before { - content: "\f09a"; -} -.fa-github:before { - content: "\f09b"; -} -.fa-unlock:before { - content: "\f09c"; -} -.fa-credit-card:before { - content: "\f09d"; -} -.fa-feed:before, -.fa-rss:before { - content: "\f09e"; -} -.fa-hdd-o:before { - content: "\f0a0"; -} -.fa-bullhorn:before { - content: "\f0a1"; -} -.fa-bell:before { - content: "\f0f3"; -} -.fa-certificate:before { - content: "\f0a3"; -} -.fa-hand-o-right:before { - content: "\f0a4"; -} -.fa-hand-o-left:before { - content: "\f0a5"; -} -.fa-hand-o-up:before { - content: "\f0a6"; -} -.fa-hand-o-down:before { - content: "\f0a7"; -} -.fa-arrow-circle-left:before { - content: "\f0a8"; -} -.fa-arrow-circle-right:before { - content: "\f0a9"; -} -.fa-arrow-circle-up:before { - content: "\f0aa"; -} -.fa-arrow-circle-down:before { - content: "\f0ab"; -} -.fa-globe:before { - content: "\f0ac"; -} -.fa-wrench:before { - content: "\f0ad"; -} -.fa-tasks:before { - content: "\f0ae"; -} -.fa-filter:before { - content: "\f0b0"; -} -.fa-briefcase:before { - content: "\f0b1"; -} -.fa-arrows-alt:before { - content: "\f0b2"; -} -.fa-group:before, -.fa-users:before { - content: "\f0c0"; -} -.fa-chain:before, -.fa-link:before { - content: "\f0c1"; -} -.fa-cloud:before { - content: "\f0c2"; -} -.fa-flask:before { - content: "\f0c3"; -} -.fa-cut:before, -.fa-scissors:before { - content: "\f0c4"; -} -.fa-copy:before, -.fa-files-o:before { - content: "\f0c5"; -} -.fa-paperclip:before { - content: "\f0c6"; -} -.fa-save:before, -.fa-floppy-o:before { - content: "\f0c7"; -} -.fa-square:before { - content: "\f0c8"; -} -.fa-navicon:before, -.fa-reorder:before, -.fa-bars:before { - content: "\f0c9"; -} -.fa-list-ul:before { - content: "\f0ca"; -} -.fa-list-ol:before { - content: "\f0cb"; -} -.fa-strikethrough:before { - content: "\f0cc"; -} -.fa-underline:before { - content: "\f0cd"; -} -.fa-table:before { - content: "\f0ce"; -} -.fa-magic:before { - content: "\f0d0"; -} -.fa-truck:before { - content: "\f0d1"; -} -.fa-pinterest:before { - content: "\f0d2"; -} -.fa-pinterest-square:before { - content: "\f0d3"; -} -.fa-google-plus-square:before { - content: "\f0d4"; -} -.fa-google-plus:before { - content: "\f0d5"; -} -.fa-money:before { - content: "\f0d6"; -} -.fa-caret-down:before { - content: "\f0d7"; -} -.fa-caret-up:before { - content: "\f0d8"; -} -.fa-caret-left:before { - content: "\f0d9"; -} -.fa-caret-right:before { - content: "\f0da"; -} -.fa-columns:before { - content: "\f0db"; -} -.fa-unsorted:before, -.fa-sort:before { - content: "\f0dc"; -} -.fa-sort-down:before, -.fa-sort-desc:before { - content: "\f0dd"; -} -.fa-sort-up:before, -.fa-sort-asc:before { - content: "\f0de"; -} -.fa-envelope:before { - content: "\f0e0"; -} -.fa-linkedin:before { - content: "\f0e1"; -} -.fa-rotate-left:before, -.fa-undo:before { - content: "\f0e2"; -} -.fa-legal:before, -.fa-gavel:before { - content: "\f0e3"; -} -.fa-dashboard:before, -.fa-tachometer:before { - content: "\f0e4"; -} -.fa-comment-o:before { - content: "\f0e5"; -} -.fa-comments-o:before { - content: "\f0e6"; -} -.fa-flash:before, -.fa-bolt:before { - content: "\f0e7"; -} -.fa-sitemap:before { - content: "\f0e8"; -} -.fa-umbrella:before { - content: "\f0e9"; -} -.fa-paste:before, -.fa-clipboard:before { - content: "\f0ea"; -} -.fa-lightbulb-o:before { - content: "\f0eb"; -} -.fa-exchange:before { - content: "\f0ec"; -} -.fa-cloud-download:before { - content: "\f0ed"; -} -.fa-cloud-upload:before { - content: "\f0ee"; -} -.fa-user-md:before { - content: "\f0f0"; -} -.fa-stethoscope:before { - content: "\f0f1"; -} -.fa-suitcase:before { - content: "\f0f2"; -} -.fa-bell-o:before { - content: "\f0a2"; -} -.fa-coffee:before { - content: "\f0f4"; -} -.fa-cutlery:before { - content: "\f0f5"; -} -.fa-file-text-o:before { - content: "\f0f6"; -} -.fa-building-o:before { - content: "\f0f7"; -} -.fa-hospital-o:before { - content: "\f0f8"; -} -.fa-ambulance:before { - content: "\f0f9"; -} -.fa-medkit:before { - content: "\f0fa"; -} -.fa-fighter-jet:before { - content: "\f0fb"; -} -.fa-beer:before { - content: "\f0fc"; -} -.fa-h-square:before { - content: "\f0fd"; -} -.fa-plus-square:before { - content: "\f0fe"; -} -.fa-angle-double-left:before { - content: "\f100"; -} -.fa-angle-double-right:before { - content: "\f101"; -} -.fa-angle-double-up:before { - content: "\f102"; -} -.fa-angle-double-down:before { - content: "\f103"; -} -.fa-angle-left:before { - content: "\f104"; -} -.fa-angle-right:before { - content: "\f105"; -} -.fa-angle-up:before { - content: "\f106"; -} -.fa-angle-down:before { - content: "\f107"; -} -.fa-desktop:before { - content: "\f108"; -} -.fa-laptop:before { - content: "\f109"; -} -.fa-tablet:before { - content: "\f10a"; -} -.fa-mobile-phone:before, -.fa-mobile:before { - content: "\f10b"; -} -.fa-circle-o:before { - content: "\f10c"; -} -.fa-quote-left:before { - content: "\f10d"; -} -.fa-quote-right:before { - content: "\f10e"; -} -.fa-spinner:before { - content: "\f110"; -} -.fa-circle:before { - content: "\f111"; -} -.fa-mail-reply:before, -.fa-reply:before { - content: "\f112"; -} -.fa-github-alt:before { - content: "\f113"; -} -.fa-folder-o:before { - content: "\f114"; -} -.fa-folder-open-o:before { - content: "\f115"; -} -.fa-smile-o:before { - content: "\f118"; -} -.fa-frown-o:before { - content: "\f119"; -} -.fa-meh-o:before { - content: "\f11a"; -} -.fa-gamepad:before { - content: "\f11b"; -} -.fa-keyboard-o:before { - content: "\f11c"; -} -.fa-flag-o:before { - content: "\f11d"; -} -.fa-flag-checkered:before { - content: "\f11e"; -} -.fa-terminal:before { - content: "\f120"; -} -.fa-code:before { - content: "\f121"; -} -.fa-mail-reply-all:before, -.fa-reply-all:before { - content: "\f122"; -} -.fa-star-half-empty:before, -.fa-star-half-full:before, -.fa-star-half-o:before { - content: "\f123"; -} -.fa-location-arrow:before { - content: "\f124"; -} -.fa-crop:before { - content: "\f125"; -} -.fa-code-fork:before { - content: "\f126"; -} -.fa-unlink:before, -.fa-chain-broken:before { - content: "\f127"; -} -.fa-question:before { - content: "\f128"; -} -.fa-info:before { - content: "\f129"; -} -.fa-exclamation:before { - content: "\f12a"; -} -.fa-superscript:before { - content: "\f12b"; -} -.fa-subscript:before { - content: "\f12c"; -} -.fa-eraser:before { - content: "\f12d"; -} -.fa-puzzle-piece:before { - content: "\f12e"; -} -.fa-microphone:before { - content: "\f130"; -} -.fa-microphone-slash:before { - content: "\f131"; -} -.fa-shield:before { - content: "\f132"; -} -.fa-calendar-o:before { - content: "\f133"; -} -.fa-fire-extinguisher:before { - content: "\f134"; -} -.fa-rocket:before { - content: "\f135"; -} -.fa-maxcdn:before { - content: "\f136"; -} -.fa-chevron-circle-left:before { - content: "\f137"; -} -.fa-chevron-circle-right:before { - content: "\f138"; -} -.fa-chevron-circle-up:before { - content: "\f139"; -} -.fa-chevron-circle-down:before { - content: "\f13a"; -} -.fa-html5:before { - content: "\f13b"; -} -.fa-css3:before { - content: "\f13c"; -} -.fa-anchor:before { - content: "\f13d"; -} -.fa-unlock-alt:before { - content: "\f13e"; -} -.fa-bullseye:before { - content: "\f140"; -} -.fa-ellipsis-h:before { - content: "\f141"; -} -.fa-ellipsis-v:before { - content: "\f142"; -} -.fa-rss-square:before { - content: "\f143"; -} -.fa-play-circle:before { - content: "\f144"; -} -.fa-ticket:before { - content: "\f145"; -} -.fa-minus-square:before { - content: "\f146"; -} -.fa-minus-square-o:before { - content: "\f147"; -} -.fa-level-up:before { - content: "\f148"; -} -.fa-level-down:before { - content: "\f149"; -} -.fa-check-square:before { - content: "\f14a"; -} -.fa-pencil-square:before { - content: "\f14b"; -} -.fa-external-link-square:before { - content: "\f14c"; -} -.fa-share-square:before { - content: "\f14d"; -} -.fa-compass:before { - content: "\f14e"; -} -.fa-toggle-down:before, -.fa-caret-square-o-down:before { - content: "\f150"; -} -.fa-toggle-up:before, -.fa-caret-square-o-up:before { - content: "\f151"; -} -.fa-toggle-right:before, -.fa-caret-square-o-right:before { - content: "\f152"; -} -.fa-euro:before, -.fa-eur:before { - content: "\f153"; -} -.fa-gbp:before { - content: "\f154"; -} -.fa-dollar:before, -.fa-usd:before { - content: "\f155"; -} -.fa-rupee:before, -.fa-inr:before { - content: "\f156"; -} -.fa-cny:before, -.fa-rmb:before, -.fa-yen:before, -.fa-jpy:before { - content: "\f157"; -} -.fa-ruble:before, -.fa-rouble:before, -.fa-rub:before { - content: "\f158"; -} -.fa-won:before, -.fa-krw:before { - content: "\f159"; -} -.fa-bitcoin:before, -.fa-btc:before { - content: "\f15a"; -} -.fa-file:before { - content: "\f15b"; -} -.fa-file-text:before { - content: "\f15c"; -} -.fa-sort-alpha-asc:before { - content: "\f15d"; -} -.fa-sort-alpha-desc:before { - content: "\f15e"; -} -.fa-sort-amount-asc:before { - content: "\f160"; -} -.fa-sort-amount-desc:before { - content: "\f161"; -} -.fa-sort-numeric-asc:before { - content: "\f162"; -} -.fa-sort-numeric-desc:before { - content: "\f163"; -} -.fa-thumbs-up:before { - content: "\f164"; -} -.fa-thumbs-down:before { - content: "\f165"; -} -.fa-youtube-square:before { - content: "\f166"; -} -.fa-youtube:before { - content: "\f167"; -} -.fa-xing:before { - content: "\f168"; -} -.fa-xing-square:before { - content: "\f169"; -} -.fa-youtube-play:before { - content: "\f16a"; -} -.fa-dropbox:before { - content: "\f16b"; -} -.fa-stack-overflow:before { - content: "\f16c"; -} -.fa-instagram:before { - content: "\f16d"; -} -.fa-flickr:before { - content: "\f16e"; -} -.fa-adn:before { - content: "\f170"; -} -.fa-bitbucket:before { - content: "\f171"; -} -.fa-bitbucket-square:before { - content: "\f172"; -} -.fa-tumblr:before { - content: "\f173"; -} -.fa-tumblr-square:before { - content: "\f174"; -} -.fa-long-arrow-down:before { - content: "\f175"; -} -.fa-long-arrow-up:before { - content: "\f176"; -} -.fa-long-arrow-left:before { - content: "\f177"; -} -.fa-long-arrow-right:before { - content: "\f178"; -} -.fa-apple:before { - content: "\f179"; -} -.fa-windows:before { - content: "\f17a"; -} -.fa-android:before { - content: "\f17b"; -} -.fa-linux:before { - content: "\f17c"; -} -.fa-dribbble:before { - content: "\f17d"; -} -.fa-skype:before { - content: "\f17e"; -} -.fa-foursquare:before { - content: "\f180"; -} -.fa-trello:before { - content: "\f181"; -} -.fa-female:before { - content: "\f182"; -} -.fa-male:before { - content: "\f183"; -} -.fa-gittip:before, -.fa-gratipay:before { - content: "\f184"; -} -.fa-sun-o:before { - content: "\f185"; -} -.fa-moon-o:before { - content: "\f186"; -} -.fa-archive:before { - content: "\f187"; -} -.fa-bug:before { - content: "\f188"; -} -.fa-vk:before { - content: "\f189"; -} -.fa-weibo:before { - content: "\f18a"; -} -.fa-renren:before { - content: "\f18b"; -} -.fa-pagelines:before { - content: "\f18c"; -} -.fa-stack-exchange:before { - content: "\f18d"; -} -.fa-arrow-circle-o-right:before { - content: "\f18e"; -} -.fa-arrow-circle-o-left:before { - content: "\f190"; -} -.fa-toggle-left:before, -.fa-caret-square-o-left:before { - content: "\f191"; -} -.fa-dot-circle-o:before { - content: "\f192"; -} -.fa-wheelchair:before { - content: "\f193"; -} -.fa-vimeo-square:before { - content: "\f194"; -} -.fa-turkish-lira:before, -.fa-try:before { - content: "\f195"; -} -.fa-plus-square-o:before { - content: "\f196"; -} -.fa-space-shuttle:before { - content: "\f197"; -} -.fa-slack:before { - content: "\f198"; -} -.fa-envelope-square:before { - content: "\f199"; -} -.fa-wordpress:before { - content: "\f19a"; -} -.fa-openid:before { - content: "\f19b"; -} -.fa-institution:before, -.fa-bank:before, -.fa-university:before { - content: "\f19c"; -} -.fa-mortar-board:before, -.fa-graduation-cap:before { - content: "\f19d"; -} -.fa-yahoo:before { - content: "\f19e"; -} -.fa-google:before { - content: "\f1a0"; -} -.fa-reddit:before { - content: "\f1a1"; -} -.fa-reddit-square:before { - content: "\f1a2"; -} -.fa-stumbleupon-circle:before { - content: "\f1a3"; -} -.fa-stumbleupon:before { - content: "\f1a4"; -} -.fa-delicious:before { - content: "\f1a5"; -} -.fa-digg:before { - content: "\f1a6"; -} -.fa-pied-piper-pp:before { - content: "\f1a7"; -} -.fa-pied-piper-alt:before { - content: "\f1a8"; -} -.fa-drupal:before { - content: "\f1a9"; -} -.fa-joomla:before { - content: "\f1aa"; -} -.fa-language:before { - content: "\f1ab"; -} -.fa-fax:before { - content: "\f1ac"; -} -.fa-building:before { - content: "\f1ad"; -} -.fa-child:before { - content: "\f1ae"; -} -.fa-paw:before { - content: "\f1b0"; -} -.fa-spoon:before { - content: "\f1b1"; -} -.fa-cube:before { - content: "\f1b2"; -} -.fa-cubes:before { - content: "\f1b3"; -} -.fa-behance:before { - content: "\f1b4"; -} -.fa-behance-square:before { - content: "\f1b5"; -} -.fa-steam:before { - content: "\f1b6"; -} -.fa-steam-square:before { - content: "\f1b7"; -} -.fa-recycle:before { - content: "\f1b8"; -} -.fa-automobile:before, -.fa-car:before { - content: "\f1b9"; -} -.fa-cab:before, -.fa-taxi:before { - content: "\f1ba"; -} -.fa-tree:before { - content: "\f1bb"; -} -.fa-spotify:before { - content: "\f1bc"; -} -.fa-deviantart:before { - content: "\f1bd"; -} -.fa-soundcloud:before { - content: "\f1be"; -} -.fa-database:before { - content: "\f1c0"; -} -.fa-file-pdf-o:before { - content: "\f1c1"; -} -.fa-file-word-o:before { - content: "\f1c2"; -} -.fa-file-excel-o:before { - content: "\f1c3"; -} -.fa-file-powerpoint-o:before { - content: "\f1c4"; -} -.fa-file-photo-o:before, -.fa-file-picture-o:before, -.fa-file-image-o:before { - content: "\f1c5"; -} -.fa-file-zip-o:before, -.fa-file-archive-o:before { - content: "\f1c6"; -} -.fa-file-sound-o:before, -.fa-file-audio-o:before { - content: "\f1c7"; -} -.fa-file-movie-o:before, -.fa-file-video-o:before { - content: "\f1c8"; -} -.fa-file-code-o:before { - content: "\f1c9"; -} -.fa-vine:before { - content: "\f1ca"; -} -.fa-codepen:before { - content: "\f1cb"; -} -.fa-jsfiddle:before { - content: "\f1cc"; -} -.fa-life-bouy:before, -.fa-life-buoy:before, -.fa-life-saver:before, -.fa-support:before, -.fa-life-ring:before { - content: "\f1cd"; -} -.fa-circle-o-notch:before { - content: "\f1ce"; -} -.fa-ra:before, -.fa-resistance:before, -.fa-rebel:before { - content: "\f1d0"; -} -.fa-ge:before, -.fa-empire:before { - content: "\f1d1"; -} -.fa-git-square:before { - content: "\f1d2"; -} -.fa-git:before { - content: "\f1d3"; -} -.fa-y-combinator-square:before, -.fa-yc-square:before, -.fa-hacker-news:before { - content: "\f1d4"; -} -.fa-tencent-weibo:before { - content: "\f1d5"; -} -.fa-qq:before { - content: "\f1d6"; -} -.fa-wechat:before, -.fa-weixin:before { - content: "\f1d7"; -} -.fa-send:before, -.fa-paper-plane:before { - content: "\f1d8"; -} -.fa-send-o:before, -.fa-paper-plane-o:before { - content: "\f1d9"; -} -.fa-history:before { - content: "\f1da"; -} -.fa-circle-thin:before { - content: "\f1db"; -} -.fa-header:before { - content: "\f1dc"; -} -.fa-paragraph:before { - content: "\f1dd"; -} -.fa-sliders:before { - content: "\f1de"; -} -.fa-share-alt:before { - content: "\f1e0"; -} -.fa-share-alt-square:before { - content: "\f1e1"; -} -.fa-bomb:before { - content: "\f1e2"; -} -.fa-soccer-ball-o:before, -.fa-futbol-o:before { - content: "\f1e3"; -} -.fa-tty:before { - content: "\f1e4"; -} -.fa-binoculars:before { - content: "\f1e5"; -} -.fa-plug:before { - content: "\f1e6"; -} -.fa-slideshare:before { - content: "\f1e7"; -} -.fa-twitch:before { - content: "\f1e8"; -} -.fa-yelp:before { - content: "\f1e9"; -} -.fa-newspaper-o:before { - content: "\f1ea"; -} -.fa-wifi:before { - content: "\f1eb"; -} -.fa-calculator:before { - content: "\f1ec"; -} -.fa-paypal:before { - content: "\f1ed"; -} -.fa-google-wallet:before { - content: "\f1ee"; -} -.fa-cc-visa:before { - content: "\f1f0"; -} -.fa-cc-mastercard:before { - content: "\f1f1"; -} -.fa-cc-discover:before { - content: "\f1f2"; -} -.fa-cc-amex:before { - content: "\f1f3"; -} -.fa-cc-paypal:before { - content: "\f1f4"; -} -.fa-cc-stripe:before { - content: "\f1f5"; -} -.fa-bell-slash:before { - content: "\f1f6"; -} -.fa-bell-slash-o:before { - content: "\f1f7"; -} -.fa-trash:before { - content: "\f1f8"; -} -.fa-copyright:before { - content: "\f1f9"; -} -.fa-at:before { - content: "\f1fa"; -} -.fa-eyedropper:before { - content: "\f1fb"; -} -.fa-paint-brush:before { - content: "\f1fc"; -} -.fa-birthday-cake:before { - content: "\f1fd"; -} -.fa-area-chart:before { - content: "\f1fe"; -} -.fa-pie-chart:before { - content: "\f200"; -} -.fa-line-chart:before { - content: "\f201"; -} -.fa-lastfm:before { - content: "\f202"; -} -.fa-lastfm-square:before { - content: "\f203"; -} -.fa-toggle-off:before { - content: "\f204"; -} -.fa-toggle-on:before { - content: "\f205"; -} -.fa-bicycle:before { - content: "\f206"; -} -.fa-bus:before { - content: "\f207"; -} -.fa-ioxhost:before { - content: "\f208"; -} -.fa-angellist:before { - content: "\f209"; -} -.fa-cc:before { - content: "\f20a"; -} -.fa-shekel:before, -.fa-sheqel:before, -.fa-ils:before { - content: "\f20b"; -} -.fa-meanpath:before { - content: "\f20c"; -} -.fa-buysellads:before { - content: "\f20d"; -} -.fa-connectdevelop:before { - content: "\f20e"; -} -.fa-dashcube:before { - content: "\f210"; -} -.fa-forumbee:before { - content: "\f211"; -} -.fa-leanpub:before { - content: "\f212"; -} -.fa-sellsy:before { - content: "\f213"; -} -.fa-shirtsinbulk:before { - content: "\f214"; -} -.fa-simplybuilt:before { - content: "\f215"; -} -.fa-skyatlas:before { - content: "\f216"; -} -.fa-cart-plus:before { - content: "\f217"; -} -.fa-cart-arrow-down:before { - content: "\f218"; -} -.fa-diamond:before { - content: "\f219"; -} -.fa-ship:before { - content: "\f21a"; -} -.fa-user-secret:before { - content: "\f21b"; -} -.fa-motorcycle:before { - content: "\f21c"; -} -.fa-street-view:before { - content: "\f21d"; -} -.fa-heartbeat:before { - content: "\f21e"; -} -.fa-venus:before { - content: "\f221"; -} -.fa-mars:before { - content: "\f222"; -} -.fa-mercury:before { - content: "\f223"; -} -.fa-intersex:before, -.fa-transgender:before { - content: "\f224"; -} -.fa-transgender-alt:before { - content: "\f225"; -} -.fa-venus-double:before { - content: "\f226"; -} -.fa-mars-double:before { - content: "\f227"; -} -.fa-venus-mars:before { - content: "\f228"; -} -.fa-mars-stroke:before { - content: "\f229"; -} -.fa-mars-stroke-v:before { - content: "\f22a"; -} -.fa-mars-stroke-h:before { - content: "\f22b"; -} -.fa-neuter:before { - content: "\f22c"; -} -.fa-genderless:before { - content: "\f22d"; -} -.fa-facebook-official:before { - content: "\f230"; -} -.fa-pinterest-p:before { - content: "\f231"; -} -.fa-whatsapp:before { - content: "\f232"; -} -.fa-server:before { - content: "\f233"; -} -.fa-user-plus:before { - content: "\f234"; -} -.fa-user-times:before { - content: "\f235"; -} -.fa-hotel:before, -.fa-bed:before { - content: "\f236"; -} -.fa-viacoin:before { - content: "\f237"; -} -.fa-train:before { - content: "\f238"; -} -.fa-subway:before { - content: "\f239"; -} -.fa-medium:before { - content: "\f23a"; -} -.fa-yc:before, -.fa-y-combinator:before { - content: "\f23b"; -} -.fa-optin-monster:before { - content: "\f23c"; -} -.fa-opencart:before { - content: "\f23d"; -} -.fa-expeditedssl:before { - content: "\f23e"; -} -.fa-battery-4:before, -.fa-battery:before, -.fa-battery-full:before { - content: "\f240"; -} -.fa-battery-3:before, -.fa-battery-three-quarters:before { - content: "\f241"; -} -.fa-battery-2:before, -.fa-battery-half:before { - content: "\f242"; -} -.fa-battery-1:before, -.fa-battery-quarter:before { - content: "\f243"; -} -.fa-battery-0:before, -.fa-battery-empty:before { - content: "\f244"; -} -.fa-mouse-pointer:before { - content: "\f245"; -} -.fa-i-cursor:before { - content: "\f246"; -} -.fa-object-group:before { - content: "\f247"; -} -.fa-object-ungroup:before { - content: "\f248"; -} -.fa-sticky-note:before { - content: "\f249"; -} -.fa-sticky-note-o:before { - content: "\f24a"; -} -.fa-cc-jcb:before { - content: "\f24b"; -} -.fa-cc-diners-club:before { - content: "\f24c"; -} -.fa-clone:before { - content: "\f24d"; -} -.fa-balance-scale:before { - content: "\f24e"; -} -.fa-hourglass-o:before { - content: "\f250"; -} -.fa-hourglass-1:before, -.fa-hourglass-start:before { - content: "\f251"; -} -.fa-hourglass-2:before, -.fa-hourglass-half:before { - content: "\f252"; -} -.fa-hourglass-3:before, -.fa-hourglass-end:before { - content: "\f253"; -} -.fa-hourglass:before { - content: "\f254"; -} -.fa-hand-grab-o:before, -.fa-hand-rock-o:before { - content: "\f255"; -} -.fa-hand-stop-o:before, -.fa-hand-paper-o:before { - content: "\f256"; -} -.fa-hand-scissors-o:before { - content: "\f257"; -} -.fa-hand-lizard-o:before { - content: "\f258"; -} -.fa-hand-spock-o:before { - content: "\f259"; -} -.fa-hand-pointer-o:before { - content: "\f25a"; -} -.fa-hand-peace-o:before { - content: "\f25b"; -} -.fa-trademark:before { - content: "\f25c"; -} -.fa-registered:before { - content: "\f25d"; -} -.fa-creative-commons:before { - content: "\f25e"; -} -.fa-gg:before { - content: "\f260"; -} -.fa-gg-circle:before { - content: "\f261"; -} -.fa-tripadvisor:before { - content: "\f262"; -} -.fa-odnoklassniki:before { - content: "\f263"; -} -.fa-odnoklassniki-square:before { - content: "\f264"; -} -.fa-get-pocket:before { - content: "\f265"; -} -.fa-wikipedia-w:before { - content: "\f266"; -} -.fa-safari:before { - content: "\f267"; -} -.fa-chrome:before { - content: "\f268"; -} -.fa-firefox:before { - content: "\f269"; -} -.fa-opera:before { - content: "\f26a"; -} -.fa-internet-explorer:before { - content: "\f26b"; -} -.fa-tv:before, -.fa-television:before { - content: "\f26c"; -} -.fa-contao:before { - content: "\f26d"; -} -.fa-500px:before { - content: "\f26e"; -} -.fa-amazon:before { - content: "\f270"; -} -.fa-calendar-plus-o:before { - content: "\f271"; -} -.fa-calendar-minus-o:before { - content: "\f272"; -} -.fa-calendar-times-o:before { - content: "\f273"; -} -.fa-calendar-check-o:before { - content: "\f274"; -} -.fa-industry:before { - content: "\f275"; -} -.fa-map-pin:before { - content: "\f276"; -} -.fa-map-signs:before { - content: "\f277"; -} -.fa-map-o:before { - content: "\f278"; -} -.fa-map:before { - content: "\f279"; -} -.fa-commenting:before { - content: "\f27a"; -} -.fa-commenting-o:before { - content: "\f27b"; -} -.fa-houzz:before { - content: "\f27c"; -} -.fa-vimeo:before { - content: "\f27d"; -} -.fa-black-tie:before { - content: "\f27e"; -} -.fa-fonticons:before { - content: "\f280"; -} -.fa-reddit-alien:before { - content: "\f281"; -} -.fa-edge:before { - content: "\f282"; -} -.fa-credit-card-alt:before { - content: "\f283"; -} -.fa-codiepie:before { - content: "\f284"; -} -.fa-modx:before { - content: "\f285"; -} -.fa-fort-awesome:before { - content: "\f286"; -} -.fa-usb:before { - content: "\f287"; -} -.fa-product-hunt:before { - content: "\f288"; -} -.fa-mixcloud:before { - content: "\f289"; -} -.fa-scribd:before { - content: "\f28a"; -} -.fa-pause-circle:before { - content: "\f28b"; -} -.fa-pause-circle-o:before { - content: "\f28c"; -} -.fa-stop-circle:before { - content: "\f28d"; -} -.fa-stop-circle-o:before { - content: "\f28e"; -} -.fa-shopping-bag:before { - content: "\f290"; -} -.fa-shopping-basket:before { - content: "\f291"; -} -.fa-hashtag:before { - content: "\f292"; -} -.fa-bluetooth:before { - content: "\f293"; -} -.fa-bluetooth-b:before { - content: "\f294"; -} -.fa-percent:before { - content: "\f295"; -} -.fa-gitlab:before { - content: "\f296"; -} -.fa-wpbeginner:before { - content: "\f297"; -} -.fa-wpforms:before { - content: "\f298"; -} -.fa-envira:before { - content: "\f299"; -} -.fa-universal-access:before { - content: "\f29a"; -} -.fa-wheelchair-alt:before { - content: "\f29b"; -} -.fa-question-circle-o:before { - content: "\f29c"; -} -.fa-blind:before { - content: "\f29d"; -} -.fa-audio-description:before { - content: "\f29e"; -} -.fa-volume-control-phone:before { - content: "\f2a0"; -} -.fa-braille:before { - content: "\f2a1"; -} -.fa-assistive-listening-systems:before { - content: "\f2a2"; -} -.fa-asl-interpreting:before, -.fa-american-sign-language-interpreting:before { - content: "\f2a3"; -} -.fa-deafness:before, -.fa-hard-of-hearing:before, -.fa-deaf:before { - content: "\f2a4"; -} -.fa-glide:before { - content: "\f2a5"; -} -.fa-glide-g:before { - content: "\f2a6"; -} -.fa-signing:before, -.fa-sign-language:before { - content: "\f2a7"; -} -.fa-low-vision:before { - content: "\f2a8"; -} -.fa-viadeo:before { - content: "\f2a9"; -} -.fa-viadeo-square:before { - content: "\f2aa"; -} -.fa-snapchat:before { - content: "\f2ab"; -} -.fa-snapchat-ghost:before { - content: "\f2ac"; -} -.fa-snapchat-square:before { - content: "\f2ad"; -} -.fa-pied-piper:before { - content: "\f2ae"; -} -.fa-first-order:before { - content: "\f2b0"; -} -.fa-yoast:before { - content: "\f2b1"; -} -.fa-themeisle:before { - content: "\f2b2"; -} -.fa-google-plus-circle:before, -.fa-google-plus-official:before { - content: "\f2b3"; -} -.fa-fa:before, -.fa-font-awesome:before { - content: "\f2b4"; -} -.fa-handshake-o:before { - content: "\f2b5"; -} -.fa-envelope-open:before { - content: "\f2b6"; -} -.fa-envelope-open-o:before { - content: "\f2b7"; -} -.fa-linode:before { - content: "\f2b8"; -} -.fa-address-book:before { - content: "\f2b9"; -} -.fa-address-book-o:before { - content: "\f2ba"; -} -.fa-vcard:before, -.fa-address-card:before { - content: "\f2bb"; -} -.fa-vcard-o:before, -.fa-address-card-o:before { - content: "\f2bc"; -} -.fa-user-circle:before { - content: "\f2bd"; -} -.fa-user-circle-o:before { - content: "\f2be"; -} -.fa-user-o:before { - content: "\f2c0"; -} -.fa-id-badge:before { - content: "\f2c1"; -} -.fa-drivers-license:before, -.fa-id-card:before { - content: "\f2c2"; -} -.fa-drivers-license-o:before, -.fa-id-card-o:before { - content: "\f2c3"; -} -.fa-quora:before { - content: "\f2c4"; -} -.fa-free-code-camp:before { - content: "\f2c5"; -} -.fa-telegram:before { - content: "\f2c6"; -} -.fa-thermometer-4:before, -.fa-thermometer:before, -.fa-thermometer-full:before { - content: "\f2c7"; -} -.fa-thermometer-3:before, -.fa-thermometer-three-quarters:before { - content: "\f2c8"; -} -.fa-thermometer-2:before, -.fa-thermometer-half:before { - content: "\f2c9"; -} -.fa-thermometer-1:before, -.fa-thermometer-quarter:before { - content: "\f2ca"; -} -.fa-thermometer-0:before, -.fa-thermometer-empty:before { - content: "\f2cb"; -} -.fa-shower:before { - content: "\f2cc"; -} -.fa-bathtub:before, -.fa-s15:before, -.fa-bath:before { - content: "\f2cd"; -} -.fa-podcast:before { - content: "\f2ce"; -} -.fa-window-maximize:before { - content: "\f2d0"; -} -.fa-window-minimize:before { - content: "\f2d1"; -} -.fa-window-restore:before { - content: "\f2d2"; -} -.fa-times-rectangle:before, -.fa-window-close:before { - content: "\f2d3"; -} -.fa-times-rectangle-o:before, -.fa-window-close-o:before { - content: "\f2d4"; -} -.fa-bandcamp:before { - content: "\f2d5"; -} -.fa-grav:before { - content: "\f2d6"; -} -.fa-etsy:before { - content: "\f2d7"; -} -.fa-imdb:before { - content: "\f2d8"; -} -.fa-ravelry:before { - content: "\f2d9"; -} -.fa-eercast:before { - content: "\f2da"; -} -.fa-microchip:before { - content: "\f2db"; -} -.fa-snowflake-o:before { - content: "\f2dc"; -} -.fa-superpowers:before { - content: "\f2dd"; -} -.fa-wpexplorer:before { - content: "\f2de"; -} -.fa-meetup:before { - content: "\f2e0"; -} -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} -.sr-only-focusable:active, -.sr-only-focusable:focus { - position: static; - width: auto; - height: auto; - margin: 0; - overflow: visible; - clip: auto; -} diff --git a/resources/template/menu/res/font-awesome/css/font-awesome.min.css b/resources/template/menu/res/font-awesome/css/font-awesome.min.css deleted file mode 100644 index 540440ce..00000000 --- a/resources/template/menu/res/font-awesome/css/font-awesome.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/resources/template/menu/res/font-awesome/fonts/FontAwesome.otf b/resources/template/menu/res/font-awesome/fonts/FontAwesome.otf deleted file mode 100644 index 401ec0f3..00000000 Binary files a/resources/template/menu/res/font-awesome/fonts/FontAwesome.otf and /dev/null differ diff --git a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.eot b/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.svg b/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.ttf b/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.woff b/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.woff2 b/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/resources/template/menu/res/font-awesome/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/resources/template/menu/res/font/CJGaoDeGuo.otf b/resources/template/menu/res/font/CJGaoDeGuo.otf deleted file mode 100644 index 1e7d1195..00000000 Binary files a/resources/template/menu/res/font/CJGaoDeGuo.otf and /dev/null differ diff --git a/resources/template/menu/res/img/bg1.jpg b/resources/template/menu/res/img/bg1.jpg deleted file mode 100644 index 29d0d1f1..00000000 Binary files a/resources/template/menu/res/img/bg1.jpg and /dev/null differ diff --git a/resources/template/menu/res/img/bg2.jpg b/resources/template/menu/res/img/bg2.jpg deleted file mode 100644 index 61417d27..00000000 Binary files a/resources/template/menu/res/img/bg2.jpg and /dev/null differ diff --git a/resources/template/menu/res/img/bg3.jpg b/resources/template/menu/res/img/bg3.jpg deleted file mode 100644 index 52fd00b4..00000000 Binary files a/resources/template/menu/res/img/bg3.jpg and /dev/null differ diff --git a/resources/template/menu/res/img/sider_left_t.png b/resources/template/menu/res/img/sider_left_t.png deleted file mode 100644 index 359fe111..00000000 Binary files a/resources/template/menu/res/img/sider_left_t.png and /dev/null differ diff --git a/resources/template/menu/res/img/sider_right_t.png b/resources/template/menu/res/img/sider_right_t.png deleted file mode 100644 index a4326867..00000000 Binary files a/resources/template/menu/res/img/sider_right_t.png and /dev/null differ diff --git a/resources/template/menu/res/img/texture.png b/resources/template/menu/res/img/texture.png deleted file mode 100644 index 39a084a4..00000000 Binary files a/resources/template/menu/res/img/texture.png and /dev/null differ diff --git a/resources/template/menu/res/logo/1.png b/resources/template/menu/res/logo/1.png deleted file mode 100644 index 75f4240b..00000000 Binary files a/resources/template/menu/res/logo/1.png and /dev/null differ diff --git a/resources/template/menu/res/logo/10.png b/resources/template/menu/res/logo/10.png deleted file mode 100644 index 1ddb8f0e..00000000 Binary files a/resources/template/menu/res/logo/10.png and /dev/null differ diff --git a/resources/template/menu/res/logo/11.png b/resources/template/menu/res/logo/11.png deleted file mode 100644 index d050b2bd..00000000 Binary files a/resources/template/menu/res/logo/11.png and /dev/null differ diff --git a/resources/template/menu/res/logo/12.png b/resources/template/menu/res/logo/12.png deleted file mode 100644 index 35c6f269..00000000 Binary files a/resources/template/menu/res/logo/12.png and /dev/null differ diff --git a/resources/template/menu/res/logo/13.png b/resources/template/menu/res/logo/13.png deleted file mode 100644 index 510bdaea..00000000 Binary files a/resources/template/menu/res/logo/13.png and /dev/null differ diff --git a/resources/template/menu/res/logo/14.png b/resources/template/menu/res/logo/14.png deleted file mode 100644 index 2865d623..00000000 Binary files a/resources/template/menu/res/logo/14.png and /dev/null differ diff --git a/resources/template/menu/res/logo/15.png b/resources/template/menu/res/logo/15.png deleted file mode 100644 index 4b390fb9..00000000 Binary files a/resources/template/menu/res/logo/15.png and /dev/null differ diff --git a/resources/template/menu/res/logo/16.png b/resources/template/menu/res/logo/16.png deleted file mode 100644 index 4d48e4a5..00000000 Binary files a/resources/template/menu/res/logo/16.png and /dev/null differ diff --git a/resources/template/menu/res/logo/17.png b/resources/template/menu/res/logo/17.png deleted file mode 100644 index b7b96e6a..00000000 Binary files a/resources/template/menu/res/logo/17.png and /dev/null differ diff --git a/resources/template/menu/res/logo/18.png b/resources/template/menu/res/logo/18.png deleted file mode 100644 index 718f321a..00000000 Binary files a/resources/template/menu/res/logo/18.png and /dev/null differ diff --git a/resources/template/menu/res/logo/19.png b/resources/template/menu/res/logo/19.png deleted file mode 100644 index cdd22bac..00000000 Binary files a/resources/template/menu/res/logo/19.png and /dev/null differ diff --git a/resources/template/menu/res/logo/2.png b/resources/template/menu/res/logo/2.png deleted file mode 100644 index 7a45e5af..00000000 Binary files a/resources/template/menu/res/logo/2.png and /dev/null differ diff --git a/resources/template/menu/res/logo/20.png b/resources/template/menu/res/logo/20.png deleted file mode 100644 index 2af15a45..00000000 Binary files a/resources/template/menu/res/logo/20.png and /dev/null differ diff --git a/resources/template/menu/res/logo/21.png b/resources/template/menu/res/logo/21.png deleted file mode 100644 index 6ac1ae56..00000000 Binary files a/resources/template/menu/res/logo/21.png and /dev/null differ diff --git a/resources/template/menu/res/logo/22.png b/resources/template/menu/res/logo/22.png deleted file mode 100644 index 487879d9..00000000 Binary files a/resources/template/menu/res/logo/22.png and /dev/null differ diff --git a/resources/template/menu/res/logo/23.png b/resources/template/menu/res/logo/23.png deleted file mode 100644 index 71d2f394..00000000 Binary files a/resources/template/menu/res/logo/23.png and /dev/null differ diff --git a/resources/template/menu/res/logo/24.png b/resources/template/menu/res/logo/24.png deleted file mode 100644 index 9f959987..00000000 Binary files a/resources/template/menu/res/logo/24.png and /dev/null differ diff --git a/resources/template/menu/res/logo/3.png b/resources/template/menu/res/logo/3.png deleted file mode 100644 index 95bdc9db..00000000 Binary files a/resources/template/menu/res/logo/3.png and /dev/null differ diff --git a/resources/template/menu/res/logo/4.png b/resources/template/menu/res/logo/4.png deleted file mode 100644 index ba71f6a5..00000000 Binary files a/resources/template/menu/res/logo/4.png and /dev/null differ diff --git a/resources/template/menu/res/logo/5.png b/resources/template/menu/res/logo/5.png deleted file mode 100644 index 26295329..00000000 Binary files a/resources/template/menu/res/logo/5.png and /dev/null differ diff --git a/resources/template/menu/res/logo/6.png b/resources/template/menu/res/logo/6.png deleted file mode 100644 index c35a73aa..00000000 Binary files a/resources/template/menu/res/logo/6.png and /dev/null differ diff --git a/resources/template/menu/res/logo/7.png b/resources/template/menu/res/logo/7.png deleted file mode 100644 index ea837704..00000000 Binary files a/resources/template/menu/res/logo/7.png and /dev/null differ diff --git a/resources/template/menu/res/logo/8.png b/resources/template/menu/res/logo/8.png deleted file mode 100644 index fe505262..00000000 Binary files a/resources/template/menu/res/logo/8.png and /dev/null differ diff --git a/resources/template/menu/res/logo/9.png b/resources/template/menu/res/logo/9.png deleted file mode 100644 index dabb8f6c..00000000 Binary files a/resources/template/menu/res/logo/9.png and /dev/null differ diff --git a/resources/template/menu/zhenxun_menu.css b/resources/template/menu/zhenxun_menu.css deleted file mode 100644 index 76c59da6..00000000 --- a/resources/template/menu/zhenxun_menu.css +++ /dev/null @@ -1,243 +0,0 @@ -:root{ - /* 整图背景颜色 (#bfd5ea) */ - --bgcolor:#FFC8A8; - /* 停用颜色 */ - --bancolor:#b5b2b2; - /* 禁用颜色 */ - --stopcolor:red; - /* 背景图高斯模糊 */ - --bgblur:6px; - /* 插件框高斯模糊 */ - --blur:4px; - /* 插件背景色 */ - --itemcolor:hsla(0,0%,100%,.38); - /* 标题背景色 */ - --titlecolor:#8b2671; - /* 标题正文 */ - --titletext:"真寻的功能捏~"; - /* 功能项背景色 */ - --optcolor:rgba( 255, 255, 255, 0.25 ); - /* 默认字体大小 */ - --mfsize:40px; - /* 功能项标题字体大小 */ - --ofsize:48px; - /* 主题背景颜色 */ - --themebgcolor:#FE96000a; - /* 插件背景颜色 */ - --pluginbgcolor:#FE9600; - /* 主题边框颜色 */ - --themebordercolor:#FE9600; - /* 备选颜色 */ - --testcolor:#FE9600; - /* 偶数列倾斜角 */ - --cdeg:20deg -} -i{ - margin-right: 5px; -} -.content > div{ - background-color: var(--themebgcolor) !important; -} - -*{ - margin: 0; - padding: 0; - box-sizing: border-box; -} -@font-face { - font-family: myFont; - /* 导入的字体文件 */ - src: url("./res/font/CJGaoDeGuo.otf"); -} -body{ - font-family:'myFont'; - font-size:var(--mfsize); -} -header{ - position: relative; - width: 100%; - height: 100vh; - background-size: cover; - background-image: url(./res/img/bg3.jpg); - background-position-y: -55px; -} -header::after{ - content: ""; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 5vh; - filter: blur(5px); -} -.transition{ - width: 100%; - height: 10vh; - filter: blur(3px); - transform: translateY(-10px) scaleY(1.5); - background-image: linear-gradient(to top, #ffc8a8 0%, rgb(255 255 255 / 99%) 100%); -} -.wrapper{ - position: relative; - padding: 0.5rem 1.5rem 1.5rem 1.5rem; - margin: 0 auto; - /*width: 80%;*/ - border-radius: 25px; - user-select: none; -} -.wrapper::before{ - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - filter: blur(3px); - background-color: var(--bgcolor); - background-repeat: no-repeat; - background-position: center; - background-size: cover; - z-index: -1; -} - -.content { - /* 瀑布流参数 */ - column-count: 4; - gap: 1rem; - column-gap: normal; -} -.des{ - /* width: 58rem; */ - margin-bottom: 1.5rem; - background: var(--optcolor); - box-shadow: 0px 0px 5px 0 rgb(31 38 135 / 37%); - backdrop-filter: blur(var(--blur)); - border-radius: 10px; - overflow: hidden; - -} -.des::before{ - content: var(--titletext); - position: absolute; - height: 60px; - line-height: 64px; - width: 100%; - display: block; - background: var(--titlecolor); - color: white; - text-align: center; - z-index: -1; -} -.des > p:nth-child(1){ - margin-top: 65px; -} -.des > p{ - text-align: left; - margin-left: 1.5rem; - margin-right: 1.5rem; - margin-bottom: 10px; -} -.content > div{ - position: relative; - margin-bottom: 1rem; - break-inside: avoid; - background: var(--optcolor); - box-shadow: 0px 0px 5px 0 rgb(31 38 135 / 37%); - backdrop-filter: blur(var(--blur)); - border-radius: 10px; - /* border: 1px solid rgba( 255, 255, 255, 0.18 ); */ - border: 2px solid var(--themebordercolor); - overflow: hidden; -} -.content > div::before{ - content: ''; - position: absolute; - height: 80px; - width: 110%; - display: block; - /* background: var(--titlecolor); */ - background: var(--themebordercolor); - background-image: url(./res/img/texture.png); - z-index: -1; -} - -.content > div > span{ - margin: 0.8rem 1.2rem; - display: block; - height: 80px; - line-height: 80px; - padding-left:2rem; - letter-spacing: 1px; - /* border: 1px solid #9a9999; */ - /* border: 1px solid #d6d6d6; */ - border: 2px solid var(--themebordercolor); - /* text-shadow: 0px 0px 3px #fff; */ - - text-shadow: 0.5px 0.5px #fff; - /* text-shadow: 0.5px 0.5px #cb6346; */ - border-radius: 50px; - backdrop-filter: blur(var(--blur)); - background: var(--itemcolor); - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -.splic{ - position: relative; - display: inline-block; - top: 28px; -} -.splic::before{ - content: ''; - width: 500px; - top: 0px; - height: 80px; - position: absolute; - border-radius: 100px; - background: var(--pluginbgcolor); - transform: translate(-95%, -100%); - z-index: -1; -} -.content > div > div { - display: flex; - justify-content: center; - align-items: center; - height:80px -} -.content > div > div >span:nth-child(1){ - color: white; - font-size: var(--ofsize); -} -.logo{ - margin-left: 20px; - display: inline-block; - height: 100%; - width: 80px; -} -.logo > img{ - width: 100%; - height: 100%; -} -.ban{ - color: var(--bancolor); - text-shadow: none; -} -del { - text-shadow: none; - color: var(--stopcolor); -} -.des-right,.content-right{ - width: 64%; -} -.des-left{ - margin-left: 36%; -} -.des-mid{ - margin: 0 auto; -} -.content-left{ - margin-left: 36%; -} -.content-mid{ - margin: 1rem auto; -} \ No newline at end of file diff --git a/resources/template/menu/zhenxun_menu.html b/resources/template/menu/zhenxun_menu.html deleted file mode 100644 index 9fa7114a..00000000 --- a/resources/template/menu/zhenxun_menu.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - zhenxun_menu - - - - -
-
-
-
-

可以通过 ‘帮助[功能名称]’ 来获取对应功能的使用方法。

-

注:横线字功能被群管理员禁用,浅色字代表功能正在维护    真寻机器人免费开源,如果你在任何渠道付费购买了真寻机器人,请退款。

-
-
- {% for plugin in plugin_list %} -
-
- - - - - {{plugin.name}} - - - -
- {% for item in plugin['items'] %} - {% if item.sta == 0 %} - - {{item.plugin_name}} -
-
- {% elif item.sta == 1 %} - - {{item.plugin_name}} -
-
- {% else %} - - {{item.plugin_name}} -
-
- {% endif %} - {% endfor %} -
- {% endfor %} -
-
- - - \ No newline at end of file diff --git a/resources/template/menu/zhenxun_menu.js b/resources/template/menu/zhenxun_menu.js deleted file mode 100644 index baf0124e..00000000 --- a/resources/template/menu/zhenxun_menu.js +++ /dev/null @@ -1,98 +0,0 @@ -let color = { - "color":[ - "#eea2a4", - "#621d34", - "#e0c8d1", - "#8b2671", - "#142334", - "#2b73af", - "#93b5cf", - "#2474b5", - "#baccd9", - "#1781b5", - "#5cb3cc", - "#57c3c2", - "#1ba784", - "#92b3a5", - "#2bae85", - "#83cbac", - "#41ae3c", - "#d0deaa", - "#d2b42c", - "#d2b116", - "#f8df72", - "#645822", - "#ddc871", - "#f9d770", - "#d9a40e", - "#b78b26", - "#5d3d21", - "#f8b37f", - "#945833", - "#e8b49a", - "#a6522c", - "#8b614d", - "#f68c60", - "#f6cec1", - "#eeaa9c", - "#862617", - "#f2b9b2", - "#f1908c" - ] -}; - -let colorlist = color.color; -let logoArr = Array.from(Array(24), (v,k) =>k+1); - -let fatherDom = document.querySelector(".content"); - -let childDom = fatherDom.children; - -//瀑布流列数统计 -let columnCount = 0; -//首列为最长列的情况下,作为被除数 -let firstColumnNum = childDom[0].childElementCount; - - - -for(let i = 0 ;i < childDom.length ;i++){ - // 随机取颜色 - let index = Math.floor((Math.random()*colorlist.length)); - let themecolor = colorlist[index]; - colorlist.splice(index, 1); - let colorStr = `--themebordercolor:${themecolor};--themebgcolor:${themecolor+'0a'};--pluginbgcolor:${themecolor+'5c'};`; - childDom[i].setAttribute('style',colorStr); - //固定图标 - let iconDom = childDom[i].querySelector("i"); - let plugin_type = childDom[i].querySelector("span").textContent; - - if(plugin_type == "功能")iconDom.classList.add("fa-cog"); - else if (plugin_type == "原神相关")iconDom.classList.add("fa-circle-o"); - else if (plugin_type == "联系管理员")iconDom.classList.add("fa-envelope-o"); - else if (plugin_type == "常规插件")iconDom.classList.add("fa-cubes"); - else if (plugin_type == "抽卡相关")iconDom.classList.add("fa-credit-card-alt"); - else if (plugin_type == "来点好康的")iconDom.classList.add("fa-picture-o"); - else if (plugin_type == "数据统计")iconDom.classList.add("fa-bar-chart"); - else if (plugin_type == "一些工具")iconDom.classList.add("fa-scissors"); - else if (plugin_type == "商店")iconDom.classList.add("fa-shopping-cart"); - else if (plugin_type == "其它")iconDom.classList.add("fa-tags"); - else if (plugin_type == "群内小游戏")iconDom.classList.add("fa-gamepad"); - else iconDom.classList.add("fa-pencil-square-o"); - - //添加真寻元素 - let imgDom = childDom[i].querySelector("img"); - let logoIndex = Math.floor((Math.random()*logoArr.length)); - let logoUrl = `./res/logo/${logoArr[logoIndex]}.png`; - logoArr.splice(logoIndex, 1); - imgDom.src=logoUrl; - - //暂时把所有的插件数量写入到列数中 - columnCount += childDom[i].childElementCount -} - -//计算瀑布流列数 -columnCount = Math.ceil(columnCount / firstColumnNum) < 5 ? Math.ceil(columnCount / firstColumnNum) : 4; -//修改瀑布流列数 -if(columnCount != 4){ - fatherDom.style.columnCount = columnCount; -} diff --git a/resources/text/cookie/buff.txt b/resources/text/cookie/buff.txt deleted file mode 100644 index 8b137891..00000000 --- a/resources/text/cookie/buff.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/scripts/bot_check.py b/scripts/bot_check.py new file mode 100644 index 00000000..4fa7493b --- /dev/null +++ b/scripts/bot_check.py @@ -0,0 +1,57 @@ +import asyncio +import os +import re + +import nonebot +from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter +from nonebot.log import logger + +nonebot.init() + +from zhenxun.services.db_context import disconnect, init + +driver = nonebot.get_driver() + +driver.register_adapter(OneBotV11Adapter) + + +driver.on_startup(init) +driver.on_shutdown(disconnect) + + +# nonebot.load_builtin_plugins("echo") +nonebot.load_plugins("zhenxun/builtin_plugins") +nonebot.load_plugins("zhenxun/plugins") + +all_plugins = [name.replace(":", ".") for name in nonebot.get_available_plugin_names()] +logger.info(f"所有插件:{all_plugins}") +loaded_plugins = tuple( + re.sub(r"^zhenxun\.(plugins|builtin_plugins)\.", "", plugin.module_name) + for plugin in nonebot.get_loaded_plugins() +) +logger.info(f"已加载插件:{loaded_plugins}") + +for plugin in all_plugins.copy(): + if plugin.startswith(("platform",)): + logger.info(f"平台插件:{plugin}") + elif plugin.endswith(loaded_plugins): + logger.info(f"已加载插件:{plugin}") + else: + logger.info(f"未加载插件:{plugin}") + continue + all_plugins.remove(plugin) + +if all_plugins: + logger.info(f"出现未加载的插件:{all_plugins}") + exit(1) +else: + logger.info("所有插件均已加载") + + +@driver.on_startup +async def _(): + task = asyncio.create_task(asyncio.sleep(1)) + task.add_done_callback(lambda _: driver._lifespan.on_ready(os._exit(0))) + + +nonebot.run() diff --git a/zhenxun/plugins/open_cases/models/__init__.py b/tests/__init__.py similarity index 100% rename from zhenxun/plugins/open_cases/models/__init__.py rename to tests/__init__.py diff --git a/tests/builtin_plugins/auto_update/test_check_update.py b/tests/builtin_plugins/auto_update/test_check_update.py new file mode 100644 index 00000000..0555806c --- /dev/null +++ b/tests/builtin_plugins/auto_update/test_check_update.py @@ -0,0 +1,439 @@ +from collections.abc import Callable +import io +import os +from pathlib import Path +import tarfile +from typing import cast +import zipfile + +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.message import Message +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event, _v11_private_message_send +from tests.utils import get_response_json as _get_response_json + + +def get_response_json(file: str) -> dict: + return _get_response_json(Path() / "auto_update", file) + + +def init_mocked_api(mocked_api: MockRouter) -> None: + mocked_api.get( + url="https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest", + name="release_latest", + ).respond(json=get_response_json("release_latest.json")) + + mocked_api.head( + url="https://raw.githubusercontent.com/", + name="head_raw", + ).respond(text="") + mocked_api.head( + url="https://github.com/", + name="head_github", + ).respond(text="") + mocked_api.head( + url="https://codeload.github.com/", + name="head_codeload", + ).respond(text="") + + mocked_api.get( + url="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/dev/__version__", + name="dev_branch_version", + ).respond(text="__version__: v0.2.2-e6f17c4") + mocked_api.get( + url="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/__version__", + name="main_branch_version", + ).respond(text="__version__: v0.2.2-e6f17c4") + mocked_api.get( + url="https://api.github.com/repos/HibiKier/zhenxun_bot/tarball/v0.2.2", + name="release_download_url", + ).respond( + status_code=302, + headers={ + "Location": "https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2" + }, + ) + + tar_buffer = io.BytesIO() + zip_bytes = io.BytesIO() + + from zhenxun.builtin_plugins.auto_update.config import ( + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE_STRING, + REPLACE_FOLDERS, + REQ_TXT_FILE_STRING, + ) + + # 指定要添加到压缩文件中的文件路径列表 + file_paths: list[str] = [ + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE_STRING, + REQ_TXT_FILE_STRING, + ] + + # 打开一个tarfile对象,写入到上面创建的BytesIO对象中 + with tarfile.open(mode="w:gz", fileobj=tar_buffer) as tar: + add_files_and_folders_to_tar(tar, file_paths, folders=REPLACE_FOLDERS) + + with zipfile.ZipFile(zip_bytes, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf: + add_files_and_folders_to_zip(zipf, file_paths, folders=REPLACE_FOLDERS) + + mocked_api.get( + url="https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2", + name="release_download_url_redirect", + ).respond( + content=tar_buffer.getvalue(), + ) + mocked_api.get( + url="https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip", + name="dev_download_url", + ).respond( + content=zip_bytes.getvalue(), + ) + mocked_api.get( + url="https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip", + name="main_download_url", + ).respond( + content=zip_bytes.getvalue(), + ) + + +# TODO Rename this here and in `init_mocked_api` +def add_files_and_folders_to_zip( + zipf: zipfile.ZipFile, file_paths: list[str], folders: list[str] = [] +): + """Add files and folders to a zip archive. + + This function creates a directory structure within the specified zip + archive and adds the provided files to it. It also creates additional + subdirectories as specified in the folders list. + + Args: + zipf: The zip archive to which files and folders will be added. + file_paths: A list of file names to be added to the zip archive. + folders: An optional list of subdirectory names to be created + within the base folder. + """ + + # 假设有一个文件夹名为 folder_name + folder_name = "my_folder/" + + # 添加文件夹到 ZIP 中,注意 ZIP 中文件夹路径应以 '/' 结尾 + zipf.writestr(folder_name, "") # 空内容表示这是一个文件夹 + + for file_path in file_paths: + # 将文件添加到 ZIP 中,路径为 folder_name + file_name + zipf.writestr(f"{folder_name}{os.path.basename(file_path)}", b"new") + base_folder = f"{folder_name}zhenxun/" + zipf.writestr(base_folder, "") + + for folder in folders: + zipf.writestr(f"{base_folder}{folder}/", "") + + +# TODO Rename this here and in `init_mocked_api` +def add_files_and_folders_to_tar( + tar: tarfile.TarFile, file_paths: list[str], folders: list[str] = [] +): + """Add files and folders to a tar archive. + + This function creates a directory structure within the specified tar + archive and adds the provided files to it. It also creates additional + subdirectories as specified in the folders list. + + Args: + tar: The tar archive to which files and folders will be added. + file_paths: A list of file names to be added to the tar archive. + folders: An optional list of subdirectory names to be created + within the base folder. + """ + + folder_name = "my_folder" + tarinfo = tarfile.TarInfo(folder_name) + add_directory_to_tar(tarinfo, tar) + # 读取并添加指定的文件 + for file_path in file_paths: + # 创建TarInfo对象 + tar_buffer = io.BytesIO(b"new") + tarinfo = tarfile.TarInfo( + f"{folder_name}/{file_path}" + ) # 使用文件名作为tar中的名字 + tarinfo.mode = 0o644 # 设置文件夹权限 + tarinfo.size = len(tar_buffer.getvalue()) # 设置文件大小 + + # 添加文件 + tar.addfile(tarinfo, fileobj=tar_buffer) + + base_folder = f"{folder_name}/zhenxun" + tarinfo = tarfile.TarInfo(base_folder) + add_directory_to_tar(tarinfo, tar) + for folder in folders: + tarinfo = tarfile.TarInfo(f"{base_folder}{folder}") + add_directory_to_tar(tarinfo, tar) + + +# TODO Rename this here and in `_extracted_from_init_mocked_api_43` +def add_directory_to_tar(tarinfo, tar): + """Add a directory entry to a tar archive. + + This function modifies the provided tarinfo object to set its type + as a directory and assigns the appropriate permissions before adding + it to the specified tar archive. + + Args: + tarinfo: The tarinfo object representing the directory. + tar: The tar archive to which the directory will be added. + """ + + tarinfo.type = tarfile.DIRTYPE + tarinfo.mode = 0o755 + tar.addfile(tarinfo) + + +def init_mocker_path(mocker: MockerFixture, tmp_path: Path): + from zhenxun.builtin_plugins.auto_update.config import ( + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE_STRING, + REQ_TXT_FILE_STRING, + VERSION_FILE_STRING, + ) + + mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.install_requirement", + return_value=None, + ) + mock_tmp_path = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.TMP_PATH", + new=tmp_path / "auto_update", + ) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mock_backup_path = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.BACKUP_PATH", + new=tmp_path / "backup", + ) + mock_download_gz_file = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_GZ_FILE", + new=mock_tmp_path / "download_latest_file.tar.gz", + ) + mock_download_zip_file = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_ZIP_FILE", + new=mock_tmp_path / "download_latest_file.zip", + ) + mock_pyproject_file = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_FILE", + new=tmp_path / PYPROJECT_FILE_STRING, + ) + mock_pyproject_lock_file = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_LOCK_FILE", + new=tmp_path / PYPROJECT_LOCK_FILE_STRING, + ) + mock_req_txt_file = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.REQ_TXT_FILE", + new=tmp_path / REQ_TXT_FILE_STRING, + ) + mock_version_file = mocker.patch( + "zhenxun.builtin_plugins.auto_update._data_source.VERSION_FILE", + new=tmp_path / VERSION_FILE_STRING, + ) + open(mock_version_file, "w").write("__version__: v0.2.2") + return ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + mock_version_file, + ) + + +async def test_check_update_release( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试检查更新(release) + """ + from zhenxun.builtin_plugins.auto_update import _matcher + from zhenxun.builtin_plugins.auto_update.config import ( + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE_STRING, + REPLACE_FOLDERS, + REQ_TXT_FILE_STRING, + ) + + init_mocked_api(mocked_api=mocked_api) + + ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + mock_version_file, + ) = init_mocker_path(mocker, tmp_path) + + # 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名 + mock_tmp_path.mkdir(parents=True, exist_ok=True) + + for folder in REPLACE_FOLDERS: + (mock_base_path / folder).mkdir(parents=True, exist_ok=True) + + mock_pyproject_file.write_bytes(b"") + mock_pyproject_lock_file.write_bytes(b"") + mock_req_txt_file.write_bytes(b"") + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot = cast(Bot, bot) + raw_message = "检查更新 release" + event = _v11_group_message_event( + raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot, event) + ctx.should_call_api( + "send_msg", + _v11_private_message_send( + message="检测真寻已更新,版本更新:v0.2.2 -> v0.2.2\n开始更新...", + user_id=UserId.SUPERUSER, + ), + ) + ctx.should_call_send( + event=event, + message=Message( + "版本更新完成\n版本: v0.2.2 -> v0.2.2\n请重新启动真寻以完成更新!" + ), + result=None, + bot=bot, + ) + ctx.should_finished(_matcher) + assert mocked_api["release_latest"].called + assert mocked_api["release_download_url_redirect"].called + + assert (mock_backup_path / PYPROJECT_FILE_STRING).exists() + assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists() + assert (mock_backup_path / REQ_TXT_FILE_STRING).exists() + + assert not mock_download_gz_file.exists() + assert not mock_download_zip_file.exists() + + assert mock_pyproject_file.read_bytes() == b"new" + assert mock_pyproject_lock_file.read_bytes() == b"new" + assert mock_req_txt_file.read_bytes() == b"new" + + for folder in REPLACE_FOLDERS: + assert not (mock_base_path / folder).exists() + for folder in REPLACE_FOLDERS: + assert (mock_backup_path / folder).exists() + + +async def test_check_update_main( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试检查更新(正式环境) + """ + from zhenxun.builtin_plugins.auto_update import _matcher + from zhenxun.builtin_plugins.auto_update.config import ( + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE_STRING, + REPLACE_FOLDERS, + REQ_TXT_FILE_STRING, + ) + + init_mocked_api(mocked_api=mocked_api) + + ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + mock_version_file, + ) = init_mocker_path(mocker, tmp_path) + + # 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名 + mock_tmp_path.mkdir(parents=True, exist_ok=True) + for folder in REPLACE_FOLDERS: + (mock_base_path / folder).mkdir(parents=True, exist_ok=True) + + mock_pyproject_file.write_bytes(b"") + mock_pyproject_lock_file.write_bytes(b"") + mock_req_txt_file.write_bytes(b"") + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot = cast(Bot, bot) + raw_message = "检查更新 main -r" + event = _v11_group_message_event( + raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot, event) + ctx.should_call_api( + "send_msg", + _v11_private_message_send( + message="检测真寻已更新,版本更新:v0.2.2 -> v0.2.2-e6f17c4\n" + "开始更新...", + user_id=UserId.SUPERUSER, + ), + ) + ctx.should_call_send( + event=event, + message=Message( + "版本更新完成\n" + "版本: v0.2.2 -> v0.2.2-e6f17c4\n" + "请重新启动真寻以完成更新!\n" + "资源文件更新成功!" + ), + result=None, + bot=bot, + ) + ctx.should_finished(_matcher) + assert mocked_api["main_download_url"].called + assert (mock_backup_path / PYPROJECT_FILE_STRING).exists() + assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists() + assert (mock_backup_path / REQ_TXT_FILE_STRING).exists() + + assert not mock_download_gz_file.exists() + assert not mock_download_zip_file.exists() + + assert mock_pyproject_file.read_bytes() == b"new" + assert mock_pyproject_lock_file.read_bytes() == b"new" + assert mock_req_txt_file.read_bytes() == b"new" + + for folder in REPLACE_FOLDERS: + assert (mock_base_path / folder).exists() + for folder in REPLACE_FOLDERS: + assert (mock_backup_path / folder).exists() diff --git a/tests/builtin_plugins/check/test_check.py b/tests/builtin_plugins/check/test_check.py new file mode 100644 index 00000000..2b2e8b99 --- /dev/null +++ b/tests/builtin_plugins/check/test_check.py @@ -0,0 +1,275 @@ +from collections import namedtuple +from collections.abc import Callable +from pathlib import Path +import platform +from typing import cast + +import nonebot +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + +platform_uname = platform.uname_result( + system="Linux", + node="zhenxun", + release="5.15.0-1027-azure", + version="#1 SMP Debian 5.15.0-1027-azure", + machine="x86_64", +) # type: ignore +cpuinfo_get_cpu_info = {"brand_raw": "Intel(R) Core(TM) i7-10700K"} + + +def init_mocker(mocker: MockerFixture, tmp_path: Path): + mock_psutil = mocker.patch("zhenxun.builtin_plugins.check.data_source.psutil") + + # Define namedtuples for complex return values + CpuFreqs = namedtuple("CpuFreqs", ["current"]) # noqa: PYI024 + VirtualMemoryInfo = namedtuple("VirtualMemoryInfo", ["used", "total", "percent"]) # noqa: PYI024 + SwapInfo = namedtuple("SwapInfo", ["used", "total", "percent"]) # noqa: PYI024 + DiskUsage = namedtuple("DiskUsage", ["used", "total", "free", "percent"]) # noqa: PYI024 + + # Set specific return values for psutil methods + mock_psutil.cpu_percent.return_value = 1.0 # CPU 使用率 + mock_psutil.cpu_freq.return_value = CpuFreqs(current=0.0) # CPU 频率 + mock_psutil.cpu_count.return_value = 1 # CPU 核心数 + + # Memory Info + mock_psutil.virtual_memory.return_value = VirtualMemoryInfo( + used=1 * 1024**3, # 1 GB in bytes for used memory + total=1 * 1024**3, # 1 GB in bytes for total memory + percent=100.0, # 100% of memory used + ) + + # Swap Info + mock_psutil.swap_memory.return_value = SwapInfo( + used=1 * 1024**3, # 1 GB in bytes for used swap space + total=1 * 1024**3, # 1 GB in bytes for total swap space + percent=100.0, # 100% of swap space used + ) + + # Disk Usage + mock_psutil.disk_usage.return_value = DiskUsage( + used=1 * 1024**3, # 1 GB in bytes for used disk space + total=1 * 1024**3, # 1 GB in bytes for total disk space + free=0, # No free space + percent=100.0, # 100% of disk space used + ) + + mock_cpuinfo = mocker.patch("zhenxun.builtin_plugins.check.data_source.cpuinfo") + mock_cpuinfo.get_cpu_info.return_value = cpuinfo_get_cpu_info + + mock_platform = mocker.patch("zhenxun.builtin_plugins.check.data_source.platform") + mock_platform.uname.return_value = platform_uname + + mock_template_to_pic = mocker.patch("zhenxun.builtin_plugins.check.template_to_pic") + mock_template_to_pic_return = mocker.AsyncMock() + mock_template_to_pic.return_value = mock_template_to_pic_return + + mock_build_message = mocker.patch( + "zhenxun.builtin_plugins.check.MessageUtils.build_message" + ) + mock_build_message_return = mocker.AsyncMock() + mock_build_message.return_value = mock_build_message_return + + mock_template_path_new = tmp_path / "resources" / "template" + mocker.patch( + "zhenxun.builtin_plugins.check.TEMPLATE_PATH", new=mock_template_path_new + ) + return ( + mock_psutil, + mock_cpuinfo, + mock_platform, + mock_template_to_pic, + mock_template_to_pic_return, + mock_build_message, + mock_build_message_return, + mock_template_path_new, + ) + + +async def test_check( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试自检 + """ + from zhenxun.builtin_plugins.check import _self_check_matcher + from zhenxun.builtin_plugins.check.data_source import __get_version + from zhenxun.configs.config import BotConfig + + ( + mock_psutil, + mock_cpuinfo, + mock_platform, + mock_template_to_pic, + mock_template_to_pic_return, + mock_build_message, + mock_build_message_return, + mock_template_path_new, + ) = init_mocker(mocker, tmp_path) + async with app.test_matcher(_self_check_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = "自检" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_ignore_rule(_self_check_matcher) + + data = { + "cpu_info": f"{mock_psutil.cpu_percent.return_value}% " + + f"- {mock_psutil.cpu_freq.return_value.current}Ghz " + + f"[{mock_psutil.cpu_count.return_value} core]", + "cpu_process": mock_psutil.cpu_percent.return_value, + "ram_info": f"{round(mock_psutil.virtual_memory.return_value.used / (1024 ** 3), 1)}" # noqa: E501 + + f" / {round(mock_psutil.virtual_memory.return_value.total / (1024 ** 3), 1)}" + + " GB", + "ram_process": mock_psutil.virtual_memory.return_value.percent, + "swap_info": f"{round(mock_psutil.swap_memory.return_value.used / (1024 ** 3), 1)}" # noqa: E501 + + f" / {round(mock_psutil.swap_memory.return_value.total / (1024 ** 3), 1)} GB", + "swap_process": mock_psutil.swap_memory.return_value.percent, + "disk_info": f"{round(mock_psutil.disk_usage.return_value.used / (1024 ** 3), 1)}" # noqa: E501 + + f" / {round(mock_psutil.disk_usage.return_value.total / (1024 ** 3), 1)} GB", + "disk_process": mock_psutil.disk_usage.return_value.percent, + "brand_raw": cpuinfo_get_cpu_info["brand_raw"], + "baidu": "red", + "google": "red", + "system": f"{platform_uname.system} " f"{platform_uname.release}", + "version": __get_version(), + "plugin_count": len(nonebot.get_loaded_plugins()), + "nickname": BotConfig.self_nickname, + } + + mock_template_to_pic.assert_awaited_once_with( + template_path=str((mock_template_path_new / "check").absolute()), + template_name="main.html", + templates={"data": data}, + pages={ + "viewport": {"width": 195, "height": 750}, + "base_url": f"file://{mock_template_path_new.absolute()}", + }, + wait=2, + ) + mock_template_to_pic.assert_awaited_once() + mock_build_message.assert_called_once_with(mock_template_to_pic_return) + mock_build_message_return.send.assert_awaited_once() + + +async def test_check_arm( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试自检(arm) + """ + from zhenxun.builtin_plugins.check import _self_check_matcher + from zhenxun.builtin_plugins.check.data_source import __get_version + from zhenxun.configs.config import BotConfig + + platform_uname_arm = platform.uname_result( + system="Linux", + node="zhenxun", + release="5.15.0-1017-oracle", + version="#22~20.04.1-Ubuntu SMP Wed Aug 24 11:13:15 UTC 2022", + machine="aarch64", + ) # type: ignore + mock_subprocess_check_output = mocker.patch( + "zhenxun.builtin_plugins.check.data_source.subprocess.check_output" + ) + mock_environ_copy = mocker.patch( + "zhenxun.builtin_plugins.check.data_source.os.environ.copy" + ) + mock_environ_copy_return = mocker.MagicMock() + mock_environ_copy.return_value = mock_environ_copy_return + ( + mock_psutil, + mock_cpuinfo, + mock_platform, + mock_template_to_pic, + mock_template_to_pic_return, + mock_build_message, + mock_build_message_return, + mock_template_path_new, + ) = init_mocker(mocker, tmp_path) + + mock_platform.uname.return_value = platform_uname_arm + mock_cpuinfo.get_cpu_info.return_value = {} + mock_psutil.cpu_freq.return_value = {} + + async with app.test_matcher(_self_check_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = "自检" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_ignore_rule(_self_check_matcher) + mock_template_to_pic.assert_awaited_once_with( + template_path=str((mock_template_path_new / "check").absolute()), + template_name="main.html", + templates={ + "data": { + "cpu_info": "1.0% - 0.0Ghz [1 core]", + "cpu_process": 1.0, + "ram_info": "1.0 / 1.0 GB", + "ram_process": 100.0, + "swap_info": "1.0 / 1.0 GB", + "swap_process": 100.0, + "disk_info": "1.0 / 1.0 GB", + "disk_process": 100.0, + "brand_raw": "", + "baidu": "red", + "google": "red", + "system": f"{platform_uname_arm.system} " + f"{platform_uname_arm.release}", + "version": __get_version(), + "plugin_count": len(nonebot.get_loaded_plugins()), + "nickname": BotConfig.self_nickname, + } + }, + pages={ + "viewport": {"width": 195, "height": 750}, + "base_url": f"file://{mock_template_path_new.absolute()}", + }, + wait=2, + ) + mock_subprocess_check_output.assert_has_calls( + [ + mocker.call(["lscpu"], env=mock_environ_copy_return), + mocker.call().decode(), + mocker.call().decode().splitlines(), + mocker.call().decode().splitlines().__iter__(), + mocker.call(["dmidecode", "-s", "processor-frequency"]), + mocker.call().decode(), + mocker.call().decode().split(), + mocker.call().decode().split().__getitem__(0), + mocker.call().decode().split().__getitem__().__float__(), + ] # type: ignore + ) + mock_template_to_pic.assert_awaited_once() + mock_build_message.assert_called_once_with(mock_template_to_pic_return) + mock_build_message_return.send.assert_awaited_once() diff --git a/tests/builtin_plugins/plugin_store/test_add_plugin.py b/tests/builtin_plugins/plugin_store/test_add_plugin.py new file mode 100644 index 00000000..9f2d6144 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_add_plugin.py @@ -0,0 +1,339 @@ +from collections.abc import Callable +from pathlib import Path +from typing import cast + +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebot.adapters.onebot.v11.message import Message +from nonebug import App +import pytest +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.builtin_plugins.plugin_store.utils import init_mocked_api +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + + +@pytest.mark.parametrize("package_api", ["jsd", "gh"]) +async def test_add_plugin_basic( + package_api: str, + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试添加基础插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + if package_api != "jsd": + mocked_api["zhenxun_bot_plugins_metadata"].respond(404) + if package_api != "gh": + mocked_api["zhenxun_bot_plugins_tree"].respond(404) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["search_image_plugin_file_init"].called + assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +@pytest.mark.parametrize("package_api", ["jsd", "gh"]) +async def test_add_plugin_basic_commit_version( + package_api: str, + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试添加基础插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + if package_api != "jsd": + mocked_api["zhenxun_bot_plugins_metadata_commit"].respond(404) + if package_api != "gh": + mocked_api["zhenxun_bot_plugins_tree_commit"].respond(404) + + plugin_id = 3 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 B站订阅 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + if package_api == "jsd": + assert mocked_api["zhenxun_bot_plugins_metadata_commit"].called + if package_api == "gh": + assert mocked_api["zhenxun_bot_plugins_tree_commit"].called + assert mocked_api["bilibili_sub_plugin_file_init"].called + assert (mock_base_path / "plugins" / "bilibili_sub" / "__init__.py").is_file() + + +@pytest.mark.parametrize("package_api", ["jsd", "gh"]) +async def test_add_plugin_basic_is_not_dir( + package_api: str, + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试添加基础插件,插件不是目录 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + if package_api != "jsd": + mocked_api["zhenxun_bot_plugins_metadata"].respond(404) + if package_api != "gh": + mocked_api["zhenxun_bot_plugins_tree"].respond(404) + + plugin_id = 0 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 鸡汤 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["jitang_plugin_file"].called + assert (mock_base_path / "plugins" / "alapi" / "jitang.py").is_file() + + +@pytest.mark.parametrize("package_api", ["jsd", "gh"]) +async def test_add_plugin_extra( + package_api: str, + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试添加额外插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + if package_api != "jsd": + mocked_api["zhenxun_github_sub_metadata"].respond(404) + if package_api != "gh": + mocked_api["zhenxun_github_sub_tree"].respond(404) + + plugin_id = 4 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message: str = f"添加插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 github订阅 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["github_sub_plugin_file_init"].called + assert (mock_base_path / "plugins" / "github_sub" / "__init__.py").is_file() + + +async def test_plugin_not_exist_add( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件不存在,添加插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + plugin_id = -1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件ID不存在..."), + result=None, + bot=bot, + ) + + +async def test_add_plugin_exist( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件已经存在,添加插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.1")], + ) + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 已安装,无需重复安装"), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/test_plugin_store.py b/tests/builtin_plugins/plugin_store/test_plugin_store.py new file mode 100644 index 00000000..90ce3f60 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_plugin_store.py @@ -0,0 +1,140 @@ +from collections.abc import Callable +from pathlib import Path +from typing import cast + +from nonebot.adapters.onebot.v11 import Bot, Message +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.builtin_plugins.plugin_store.utils import init_mocked_api +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + + +async def test_plugin_store( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件商店 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + from zhenxun.builtin_plugins.plugin_store.data_source import row_style + + init_mocked_api(mocked_api=mocked_api) + + mock_table_page = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page" + ) + mock_table_page_return = mocker.AsyncMock() + mock_table_page.return_value = mock_table_page_return + + mock_build_message = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.MessageUtils.build_message" + ) + mock_build_message_return = mocker.AsyncMock() + mock_build_message.return_value = mock_build_message_return + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = "插件商店" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + mock_table_page.assert_awaited_once_with( + "插件列表", + "通过添加/移除插件 ID 来管理插件", + ["-", "ID", "名称", "简介", "作者", "版本", "类型"], + [ + ["", 0, "鸡汤", "喏,亲手为你煮的鸡汤", "HibiKier", "0.1", "普通插件"], + ["", 1, "识图", "以图搜图,看破本源", "HibiKier", "0.1", "普通插件"], + ["", 2, "网易云热评", "生了个人,我很抱歉", "HibiKier", "0.1", "普通插件"], + [ + "", + 3, + "B站订阅", + "非常便利的B站订阅通知", + "HibiKier", + "0.3-b101fbc", + "普通插件", + ], + [ + "", + 4, + "github订阅", + "订阅github用户或仓库", + "xuanerwa", + "0.7", + "普通插件", + ], + [ + "", + 5, + "Minecraft查服", + "Minecraft服务器状态查询,支持IPv6", + "molanp", + "1.13", + "普通插件", + ], + ], + text_style=row_style, + ) + mock_build_message.assert_called_once_with(mock_table_page_return) + mock_build_message_return.send.assert_awaited_once() + + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + + +async def test_plugin_store_fail( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件商店 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins.json", + name="basic_plugins", + ).respond(404) + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = "插件商店" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message("获取插件列表失败..."), + result=None, + exception=None, + bot=bot, + ) + + assert mocked_api["basic_plugins"].called diff --git a/tests/builtin_plugins/plugin_store/test_remove_plugin.py b/tests/builtin_plugins/plugin_store/test_remove_plugin.py new file mode 100644 index 00000000..4d5e3ab1 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_remove_plugin.py @@ -0,0 +1,142 @@ +# ruff: noqa: ASYNC230 + +from collections.abc import Callable +from pathlib import Path +from typing import cast + +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebot.adapters.onebot.v11.message import Message +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.builtin_plugins.plugin_store.utils import get_content_bytes, init_mocked_api +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + + +async def test_remove_plugin( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试删除插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + plugin_path = mock_base_path / "plugins" / "search_image" + plugin_path.mkdir(parents=True, exist_ok=True) + + with open(plugin_path / "__init__.py", "wb") as f: + f.write(get_content_bytes("search_image.py")) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"移除插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 移除成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert not (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +async def test_plugin_not_exist_remove( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件不存在,移除插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + plugin_id = -1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"移除插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="插件ID不存在..."), + result=None, + bot=bot, + ) + + +async def test_remove_plugin_not_install( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件未安装,移除插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + _ = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"移除插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 不存在..."), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/test_search_plugin.py b/tests/builtin_plugins/plugin_store/test_search_plugin.py new file mode 100644 index 00000000..8bc6876e --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_search_plugin.py @@ -0,0 +1,182 @@ +from collections.abc import Callable +from pathlib import Path +from typing import cast + +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebot.adapters.onebot.v11.message import Message +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.builtin_plugins.plugin_store.utils import init_mocked_api +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + + +async def test_search_plugin_name( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试搜索插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + from zhenxun.builtin_plugins.plugin_store.data_source import row_style + + init_mocked_api(mocked_api=mocked_api) + + mock_table_page = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page" + ) + mock_table_page_return = mocker.AsyncMock() + mock_table_page.return_value = mock_table_page_return + + mock_build_message = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.MessageUtils.build_message" + ) + mock_build_message_return = mocker.AsyncMock() + mock_build_message.return_value = mock_build_message_return + + plugin_name = "github订阅" + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"搜索插件 {plugin_name}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + mock_table_page.assert_awaited_once_with( + "插件列表", + "通过添加/移除插件 ID 来管理插件", + ["-", "ID", "名称", "简介", "作者", "版本", "类型"], + [ + [ + "", + 4, + "github订阅", + "订阅github用户或仓库", + "xuanerwa", + "0.7", + "普通插件", + ] + ], + text_style=row_style, + ) + mock_build_message.assert_called_once_with(mock_table_page_return) + mock_build_message_return.send.assert_awaited_once() + + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + + +async def test_search_plugin_author( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试搜索插件,作者 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + from zhenxun.builtin_plugins.plugin_store.data_source import row_style + + init_mocked_api(mocked_api=mocked_api) + + mock_table_page = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page" + ) + mock_table_page_return = mocker.AsyncMock() + mock_table_page.return_value = mock_table_page_return + + mock_build_message = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.MessageUtils.build_message" + ) + mock_build_message_return = mocker.AsyncMock() + mock_build_message.return_value = mock_build_message_return + + author_name = "xuanerwa" + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"搜索插件 {author_name}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + mock_table_page.assert_awaited_once_with( + "插件列表", + "通过添加/移除插件 ID 来管理插件", + ["-", "ID", "名称", "简介", "作者", "版本", "类型"], + [ + [ + "", + 4, + "github订阅", + "订阅github用户或仓库", + "xuanerwa", + "0.7", + "普通插件", + ] + ], + text_style=row_style, + ) + mock_build_message.assert_called_once_with(mock_table_page_return) + mock_build_message_return.send.assert_awaited_once() + + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + + +async def test_plugin_not_exist_search( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件不存在,搜索插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + plugin_name = "not_exist_plugin_name" + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"搜索插件 {plugin_name}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_3, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="未找到相关插件..."), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/test_update_all_plugin.py b/tests/builtin_plugins/plugin_store/test_update_all_plugin.py new file mode 100644 index 00000000..7fc3b588 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_update_all_plugin.py @@ -0,0 +1,120 @@ +from collections.abc import Callable +from pathlib import Path +from typing import cast + +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebot.adapters.onebot.v11.message import Message +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.builtin_plugins.plugin_store.utils import init_mocked_api +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + + +async def test_update_all_plugin_basic_need_update( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试更新基础插件,插件需要更新 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.0")], + ) + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = "更新全部插件" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="正在更新全部插件"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message( + message="--已更新1个插件 0个失败 1个成功--\n* 以下插件更新成功:\n\t- 识图\n重启后生效" # noqa: E501 + ), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["search_image_plugin_file_init"].called + assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +async def test_update_all_plugin_basic_is_new( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试更新基础插件,插件是最新版 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.1")], + ) + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = "更新全部插件" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="正在更新全部插件"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="全部插件已是最新版本"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called diff --git a/tests/builtin_plugins/plugin_store/test_update_plugin.py b/tests/builtin_plugins/plugin_store/test_update_plugin.py new file mode 100644 index 00000000..59fb0bc3 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_update_plugin.py @@ -0,0 +1,206 @@ +from collections.abc import Callable +from pathlib import Path +from typing import cast + +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.event import GroupMessageEvent +from nonebot.adapters.onebot.v11.message import Message +from nonebug import App +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.builtin_plugins.plugin_store.utils import init_mocked_api +from tests.config import BotId, GroupId, MessageId, UserId +from tests.utils import _v11_group_message_event + + +async def test_update_plugin_basic_need_update( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试更新基础插件,插件需要更新 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.0")], + ) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 更新成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["search_image_plugin_file_init"].called + assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +async def test_update_plugin_basic_is_new( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试更新基础插件,插件是最新版 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.1")], + ) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 已是最新版本"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + + +async def test_plugin_not_exist_update( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件不存在,更新插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + plugin_id = -1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件ID不存在..."), + result=None, + bot=bot, + ) + + +async def test_update_plugin_not_install( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试插件不存在,更新插件 + """ + from zhenxun.builtin_plugins.plugin_store import _matcher + + init_mocked_api(mocked_api=mocked_api) + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + event: GroupMessageEvent = _v11_group_message_event( + message=raw_message, + self_id=BotId.QQ_BOT, + user_id=UserId.SUPERUSER, + group_id=GroupId.GROUP_ID_LEVEL_5, + message_id=MessageId.MESSAGE_ID_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 未安装,无法更新"), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/utils.py b/tests/builtin_plugins/plugin_store/utils.py new file mode 100644 index 00000000..83129071 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/utils.py @@ -0,0 +1,86 @@ +# ruff: noqa: ASYNC230 + +from pathlib import Path + +from respx import MockRouter + +from tests.utils import get_content_bytes as _get_content_bytes +from tests.utils import get_response_json as _get_response_json + + +def get_response_json(file: str) -> dict: + return _get_response_json(Path() / "plugin_store", file=file) + + +def get_content_bytes(file: str) -> bytes: + return _get_content_bytes(Path() / "plugin_store", file) + + +def init_mocked_api(mocked_api: MockRouter) -> None: + # metadata + mocked_api.get( + "https://data.jsdelivr.com/v1/packages/gh/zhenxun-org/zhenxun_bot_plugins@main", + name="zhenxun_bot_plugins_metadata", + ).respond(json=get_response_json("zhenxun_bot_plugins_metadata.json")) + mocked_api.get( + "https://data.jsdelivr.com/v1/packages/gh/xuanerwa/zhenxun_github_sub@main", + name="zhenxun_github_sub_metadata", + ).respond(json=get_response_json("zhenxun_github_sub_metadata.json")) + mocked_api.get( + "https://data.jsdelivr.com/v1/packages/gh/zhenxun-org/zhenxun_bot_plugins@b101fbc", + name="zhenxun_bot_plugins_metadata_commit", + ).respond(json=get_response_json("zhenxun_bot_plugins_metadata.json")) + + # tree + mocked_api.get( + "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/main?recursive=1", + name="zhenxun_bot_plugins_tree", + ).respond(json=get_response_json("zhenxun_bot_plugins_tree.json")) + mocked_api.get( + "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/main?recursive=1", + name="zhenxun_github_sub_tree", + ).respond(json=get_response_json("zhenxun_github_sub_tree.json")) + mocked_api.get( + "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/b101fbc?recursive=1", + name="zhenxun_bot_plugins_tree_commit", + ).respond(json=get_response_json("zhenxun_bot_plugins_tree.json")) + + mocked_api.head( + "https://raw.githubusercontent.com/", + name="head_raw", + ).respond(200, text="") + + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins.json", + name="basic_plugins", + ).respond(json=get_response_json("basic_plugins.json")) + mocked_api.get( + "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins@main/plugins.json", + name="basic_plugins_jsdelivr", + ).respond(200, json=get_response_json("basic_plugins.json")) + + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json", + name="extra_plugins", + ).respond(200, json=get_response_json("extra_plugins.json")) + mocked_api.get( + "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins_index@index/plugins.json", + name="extra_plugins_jsdelivr", + ).respond(200, json=get_response_json("extra_plugins.json")) + + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/search_image/__init__.py", + name="search_image_plugin_file_init", + ).respond(content=get_content_bytes("search_image.py")) + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/alapi/jitang.py", + name="jitang_plugin_file", + ).respond(content=get_content_bytes("jitang.py")) + mocked_api.get( + "https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py", + name="github_sub_plugin_file_init", + ).respond(content=get_content_bytes("github_sub.py")) + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins/bilibili_sub/__init__.py", + name="bilibili_sub_plugin_file_init", + ).respond(content=get_content_bytes("bilibili_sub.py")) diff --git a/tests/config.py b/tests/config.py new file mode 100644 index 00000000..d472c0a8 --- /dev/null +++ b/tests/config.py @@ -0,0 +1,22 @@ +class BotId: + QQ_BOT = 12345 + + +class UserId: + SUPERUSER = 10000 + SUPERUSER_QQ = 11000 + SUPERUSER_DODO = 12000 + USER_LEVEL_0 = 10010 + USER_LEVEL_5 = 10005 + + +class GroupId: + GROUP_ID_LEVEL_0 = 20000 + GROUP_ID_LEVEL_5 = 20005 + + +class MessageId: + MESSAGE_ID = 30001 + MESSAGE_ID_2 = 30002 + MESSAGE_ID_3 = 30003 + MESSAGE_ID_4 = 30004 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..0fce1583 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,143 @@ +from collections.abc import Callable +import json +import os +from pathlib import Path + +import nonebot +from nonebug import NONEBOT_INIT_KWARGS +from nonebug.app import App +from nonebug.mixin.process import MatcherContext +import pytest +from pytest_asyncio import is_async_test +from pytest_mock import MockerFixture +from respx import MockRouter + +from tests.config import BotId, GroupId, UserId + +nonebot.load_plugin("nonebot_plugin_session") + + +def get_response_json(path: str) -> dict: + return json.loads( + (Path(__file__).parent / "response" / path).read_text(encoding="utf8") + ) + + +def pytest_collection_modifyitems(items: list[pytest.Item]): + pytest_asyncio_tests = (item for item in items if is_async_test(item)) + session_scope_marker = pytest.mark.asyncio(loop_scope="session") + for async_test in pytest_asyncio_tests: + async_test.add_marker(session_scope_marker, append=False) + + +def pytest_configure(config: pytest.Config) -> None: + config.stash[NONEBOT_INIT_KWARGS] = { + "driver": "~fastapi+~httpx+~websockets", + "superusers": [UserId.SUPERUSER.__str__()], + "command_start": [""], + "session_running_expression": "别急呀,小真寻要宕机了!QAQ", + "image_to_bytes": False, + "nickname": ["真寻", "小真寻", "绪山真寻", "小寻子"], + "session_expire_timeout": 30, + "self_nickname": "小真寻", + "db_url": "sqlite://:memory:", + "platform_superusers": { + "qq": [UserId.SUPERUSER_QQ.__str__()], + "dodo": [UserId.SUPERUSER_DODO.__str__()], + }, + "host": "127.0.0.1", + "port": 8080, + "log_level": "INFO", + } + + +@pytest.fixture(scope="session", autouse=True) +def _init_bot(nonebug_init: None): + from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter + + driver = nonebot.get_driver() + driver.register_adapter(OneBotV11Adapter) + + nonebot.load_plugin("nonebot_plugin_alconna") + nonebot.load_plugin("nonebot_plugin_apscheduler") + nonebot.load_plugin("nonebot_plugin_htmlrender") + nonebot.load_plugins("zhenxun/builtin_plugins") + nonebot.load_plugins("zhenxun/plugins") + + # 手动缓存 uninfo 所需信息 + from nonebot_plugin_uninfo import ( + Scene, + SceneType, + Session, + SupportAdapter, + SupportScope, + User, + ) + from nonebot_plugin_uninfo.adapters.onebot11.main import fetcher as onebot11_fetcher + from nonebot_plugin_uninfo.adapters.onebot12.main import fetcher as onebot12_fetcher + + onebot11_fetcher.session_cache = { + f"group_{GroupId.GROUP_ID_LEVEL_5}_{UserId.SUPERUSER}": Session( + self_id="test", + adapter=SupportAdapter.onebot11, + scope=SupportScope.qq_client, + scene=Scene(str(GroupId.GROUP_ID_LEVEL_0), SceneType.GROUP), + user=User(str(UserId.SUPERUSER)), + ), + } + onebot12_fetcher.session_cache = { + f"group_{GroupId.GROUP_ID_LEVEL_5}_{UserId.SUPERUSER}": Session( + self_id="test", + adapter=SupportAdapter.onebot12, + scope=SupportScope.qq_client, + scene=Scene(str(GroupId.GROUP_ID_LEVEL_0), SceneType.GROUP), + user=User(str(UserId.SUPERUSER)), + ), + } + + +@pytest.fixture +async def app(app: App, tmp_path: Path, mocker: MockerFixture): + from zhenxun.services.db_context import disconnect, init + + driver = nonebot.get_driver() + # 清除连接钩子,现在 NoneBug 会自动触发 on_bot_connect + driver._bot_connection_hook.clear() + mock_config_path = mocker.MagicMock() + mock_config_path.LOG_PATH = tmp_path / "log" + # mock_config_path.LOG_PATH.mkdir(parents=True, exist_ok=True) + mock_config_path.DATA_PATH = tmp_path / "data" + # mock_config_path.DATA_PATH.mkdir(parents=True, exist_ok=True) + mock_config_path.TEMP_PATH = tmp_path / "resources" / "temp" + # mock_config_path.TEMP_PATH.mkdir(parents=True, exist_ok=True) + + mocker.patch("zhenxun.configs.path_config", new=mock_config_path) + + await init() + # await driver._lifespan.startup() + os.environ["AIOCACHE_DISABLE"] = "1" + + yield app + + del os.environ["AIOCACHE_DISABLE"] + # await driver._lifespan.shutdown() + await disconnect() + + +@pytest.fixture +def create_bot() -> Callable: + from nonebot.adapters.onebot.v11 import Adapter, Bot + + def _create_bot(context: MatcherContext) -> Bot: + return context.create_bot( + base=Bot, + adapter=nonebot.get_adapter(Adapter), + self_id=BotId.QQ_BOT.__str__(), + ) + + return _create_bot + + +@pytest.fixture +def mocked_api(respx_mock: MockRouter): + return respx_mock diff --git a/tests/content/plugin_store/bilibili_sub.py b/tests/content/plugin_store/bilibili_sub.py new file mode 100644 index 00000000..98da4ee4 --- /dev/null +++ b/tests/content/plugin_store/bilibili_sub.py @@ -0,0 +1,37 @@ +from nonebot.plugin import PluginMetadata + +from zhenxun.configs.utils import PluginExtraData + +__plugin_meta__ = PluginMetadata( + name="B站订阅", + description="非常便利的B站订阅通知", + usage=""" + usage: + B站直播,番剧,UP动态开播等提醒 + 主播订阅相当于 直播间订阅 + UP订阅 + 指令: + 添加订阅 ['主播'/'UP'/'番剧'] [id/链接/番名] + 删除订阅 ['主播'/'UP'/'id'] [id] + 查看订阅 + 示例: + 添加订阅主播 2345344 <-(直播房间id) + 添加订阅UP 2355543 <-(个人主页id) + 添加订阅番剧 史莱姆 <-(支持模糊搜索) + 添加订阅番剧 125344 <-(番剧id) + 删除订阅id 2324344 <-(任意id,通过查看订阅获取) + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.3-b101fbc", + superuser_help=""" + 登录b站获取cookie防止风控: + bil_check/检测b站 + bil_login/登录b站 + bil_logout/退出b站 uid + 示例: + 登录b站 + 检测b站 + bil_logout 12345<-(退出登录的b站uid,通过检测b站获取) + """, + ).to_dict(), +) diff --git a/tests/content/plugin_store/github_sub.py b/tests/content/plugin_store/github_sub.py new file mode 100644 index 00000000..89e68e9e --- /dev/null +++ b/tests/content/plugin_store/github_sub.py @@ -0,0 +1,24 @@ +from nonebot.plugin import PluginMetadata + +from zhenxun.configs.utils import PluginExtraData + +__plugin_meta__ = PluginMetadata( + name="github订阅", + description="订阅github用户或仓库", + usage=""" + usage: + github新Comment,PR,Issue等提醒 + 指令: + 添加github ['用户'/'仓库'] [用户名/{owner/repo}] + 删除github [用户名/{owner/repo}] + 查看github + 示例:添加github订阅 用户 HibiKier + 示例:添加gb订阅 仓库 HibiKier/zhenxun_bot + 示例:添加github 用户 HibiKier + 示例:删除gb订阅 HibiKier + """.strip(), + extra=PluginExtraData( + author="xuanerwa", + version="0.7", + ).to_dict(), +) diff --git a/tests/content/plugin_store/jitang.py b/tests/content/plugin_store/jitang.py new file mode 100644 index 00000000..a31045aa --- /dev/null +++ b/tests/content/plugin_store/jitang.py @@ -0,0 +1,17 @@ +from nonebot.plugin import PluginMetadata + +from zhenxun.configs.utils import PluginExtraData + +__plugin_meta__ = PluginMetadata( + name="鸡汤", + description="喏,亲手为你煮的鸡汤", + usage=""" + 不喝点什么感觉有点不舒服 + 指令: + 鸡汤 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + ).to_dict(), +) diff --git a/tests/content/plugin_store/search_image.py b/tests/content/plugin_store/search_image.py new file mode 100644 index 00000000..59c3bf5c --- /dev/null +++ b/tests/content/plugin_store/search_image.py @@ -0,0 +1,18 @@ +from nonebot.plugin import PluginMetadata + +from zhenxun.configs.utils import PluginExtraData + +__plugin_meta__ = PluginMetadata( + name="识图", + description="以图搜图,看破本源", + usage=""" + 识别图片 [二次元图片] + 指令: + 识图 [图片] + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + menu_type="一些工具", + ).to_dict(), +) diff --git a/tests/response/auto_update/release_latest.json b/tests/response/auto_update/release_latest.json new file mode 100644 index 00000000..099c928a --- /dev/null +++ b/tests/response/auto_update/release_latest.json @@ -0,0 +1,39 @@ +{ + "url": "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/172632135", + "assets_url": "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/172632135/assets", + "upload_url": "https://uploads.github.com/repos/HibiKier/zhenxun_bot/releases/172632135/assets{?name,label}", + "html_url": "https://github.com/HibiKier/zhenxun_bot/releases/tag/v0.2.2", + "id": 172632135, + "author": { + "login": "HibiKier", + "id": 45528451, + "node_id": "MDQ6VXNlcjQ1NTI4NDUx", + "avatar_url": "https://avatars.githubusercontent.com/u/45528451?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/HibiKier", + "html_url": "https://github.com/HibiKier", + "followers_url": "https://api.github.com/users/HibiKier/followers", + "following_url": "https://api.github.com/users/HibiKier/following{/other_user}", + "gists_url": "https://api.github.com/users/HibiKier/gists{/gist_id}", + "starred_url": "https://api.github.com/users/HibiKier/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/HibiKier/subscriptions", + "organizations_url": "https://api.github.com/users/HibiKier/orgs", + "repos_url": "https://api.github.com/users/HibiKier/repos", + "events_url": "https://api.github.com/users/HibiKier/events{/privacy}", + "received_events_url": "https://api.github.com/users/HibiKier/received_events", + "type": "User", + "site_admin": false + }, + "node_id": "RE_kwDOFe9cjs4KSihH", + "tag_name": "v0.2.2", + "target_commitish": "main", + "name": "v0.2.2", + "draft": false, + "prerelease": false, + "created_at": "2024-08-29T18:48:53Z", + "published_at": "2024-08-29T18:49:34Z", + "assets": [], + "tarball_url": "https://api.github.com/repos/HibiKier/zhenxun_bot/tarball/v0.2.2", + "zipball_url": "https://api.github.com/repos/HibiKier/zhenxun_bot/zipball/v0.2.2", + "body": "* 集成webui\r\n* 移除plugins\r\n* 签到和帮助ui改版\r\n* 修改bug" +} diff --git a/tests/response/plugin_store/basic_plugins.json b/tests/response/plugin_store/basic_plugins.json new file mode 100644 index 00000000..7459e2ec --- /dev/null +++ b/tests/response/plugin_store/basic_plugins.json @@ -0,0 +1,42 @@ +{ + "鸡汤": { + "module": "jitang", + "module_path": "plugins.alapi.jitang", + "description": "喏,亲手为你煮的鸡汤", + "usage": "不喝点什么感觉有点不舒服\n 指令:\n 鸡汤", + "author": "HibiKier", + "version": "0.1", + "plugin_type": "NORMAL", + "is_dir": false + }, + "识图": { + "module": "search_image", + "module_path": "plugins.search_image", + "description": "以图搜图,看破本源", + "usage": "识别图片 [二次元图片]\n 指令:\n 识图 [图片]", + "author": "HibiKier", + "version": "0.1", + "plugin_type": "NORMAL", + "is_dir": true + }, + "网易云热评": { + "module": "comments_163", + "module_path": "plugins.alapi.comments_163", + "description": "生了个人,我很抱歉", + "usage": "到点了,还是防不了下塔\n 指令:\n 网易云热评/到点了/12点了", + "author": "HibiKier", + "version": "0.1", + "plugin_type": "NORMAL", + "is_dir": false + }, + "B站订阅": { + "module": "bilibili_sub", + "module_path": "plugins.bilibili_sub", + "description": "非常便利的B站订阅通知", + "usage": "B站直播,番剧,UP动态开播等提醒", + "author": "HibiKier", + "version": "0.3-b101fbc", + "plugin_type": "NORMAL", + "is_dir": true + } +} diff --git a/tests/response/plugin_store/extra_plugins.json b/tests/response/plugin_store/extra_plugins.json new file mode 100644 index 00000000..9d92f859 --- /dev/null +++ b/tests/response/plugin_store/extra_plugins.json @@ -0,0 +1,24 @@ +{ + "github订阅": { + "module": "github_sub", + "module_path": "github_sub", + "description": "订阅github用户或仓库", + "usage": "usage:\n github新Comment,PR,Issue等提醒\n 指令:\n 添加github ['用户'/'仓库'] [用户名/{owner/repo}]\n 删除github [用户名/{owner/repo}]\n 查看github\n 示例:添加github订阅 用户 HibiKier\n 示例:添加gb订阅 仓库 HibiKier/zhenxun_bot\n 示例:添加github 用户 HibiKier\n 示例:删除gb订阅 HibiKier", + "author": "xuanerwa", + "version": "0.7", + "plugin_type": "NORMAL", + "is_dir": true, + "github_url": "https://github.com/xuanerwa/zhenxun_github_sub" + }, + "Minecraft查服": { + "module": "mc_check", + "module_path": "mc_check", + "description": "Minecraft服务器状态查询,支持IPv6", + "usage": "Minecraft服务器状态查询,支持IPv6\n用法:\n\t查服 [ip]:[端口] / 查服 [ip]\n\t设置语言 zh-cn\n\t当前语言\n\t语言列表\neg:\t\nmcheck ip:port / mcheck ip\n\tset_lang en\n\tlang_now\n\tlang_list", + "author": "molanp", + "version": "1.13", + "plugin_type": "NORMAL", + "is_dir": true, + "github_url": "https://github.com/molanp/zhenxun_check_Minecraft" + } +} diff --git a/tests/response/plugin_store/zhenxun_bot_plugins_metadata.json b/tests/response/plugin_store/zhenxun_bot_plugins_metadata.json new file mode 100644 index 00000000..49faa7ab --- /dev/null +++ b/tests/response/plugin_store/zhenxun_bot_plugins_metadata.json @@ -0,0 +1,83 @@ +{ + "type": "gh", + "name": "zhenxun-org/zhenxun_bot_plugins", + "version": "main", + "default": null, + "files": [ + { + "type": "directory", + "name": "plugins", + "files": [ + { + "type": "directory", + "name": "search_image", + "files": [ + { + "type": "file", + "name": "__init__.py", + "hash": "a4Yp9HPoBzMwvnQDT495u0yYqTQWofkOyHxEi1FdVb0=", + "size": 3010 + } + ] + }, + { + "type": "directory", + "name": "alapi", + "files": [ + { + "type": "file", + "name": "__init__.py", + "hash": "ndDxtO0pAq3ZTb4RdqW7FTDgOGC/RjS1dnwdaQfT0uQ=", + "size": 284 + }, + { + "type": "file", + "name": "_data_source.py", + "hash": "KOLqtj4TQWWQco5bA4tWFc7A0z1ruMyDk1RiKeqJHRA=", + "size": 919 + }, + { + "type": "file", + "name": "comments_163.py", + "hash": "Q5pZsj1Pj+EJMdKYcPtLqejcXAWUQIoXVQG49PZPaSI=", + "size": 1593 + }, + { + "type": "file", + "name": "cover.py", + "hash": "QSjtcy0oVrjaRiAWZKmUJlp0L4DQqEcdYNmExNo9mgc=", + "size": 1438 + }, + { + "type": "file", + "name": "jitang.py", + "hash": "xh43Osxt0xogTH448gUMC+/DaSGmCFme8DWUqC25IbU=", + "size": 1411 + }, + { + "type": "file", + "name": "poetry.py", + "hash": "Aj2unoNQboj3/0LhIrYU+dCa5jvMdpjMYXYUayhjuz4=", + "size": 1530 + } + ] + }, + { + "type": "directory", + "name": "bilibili_sub", + "files": [ + { + "type": "file", + "name": "__init__.py", + "hash": "407DCgNFcZnuEK+d716j8EWrFQc4Nlxa35V3yemy3WQ=", + "size": 14293 + } + ] + } + ] + } + ], + "links": { + "stats": "https://data.jsdelivr.com/v1/stats/packages/gh/zhenxun-org/zhenxun_bot_plugins@main" + } +} diff --git a/tests/response/plugin_store/zhenxun_bot_plugins_tree.json b/tests/response/plugin_store/zhenxun_bot_plugins_tree.json new file mode 100644 index 00000000..54a266e3 --- /dev/null +++ b/tests/response/plugin_store/zhenxun_bot_plugins_tree.json @@ -0,0 +1,1324 @@ +{ + "sha": "af93a5425c039ee176207d0aceeeb43221a06e46", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/af93a5425c039ee176207d0aceeeb43221a06e46", + "tree": [ + { + "path": ".gitignore", + "mode": "100644", + "type": "blob", + "sha": "98bf2bb61a79b9b0cd4a51aea8bd21243b1b5fe2", + "size": 2967, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/98bf2bb61a79b9b0cd4a51aea8bd21243b1b5fe2" + }, + { + "path": "LICENSE", + "mode": "100644", + "type": "blob", + "sha": "0ad25db4bd1d86c452db3f9602ccdbe172438f52", + "size": 34523, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/0ad25db4bd1d86c452db3f9602ccdbe172438f52" + }, + { + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "68498670cab18fb7c7beac28fa3aa917d772479b", + "size": 195, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/68498670cab18fb7c7beac28fa3aa917d772479b" + }, + { + "path": "plugins.json", + "mode": "100644", + "type": "blob", + "sha": "dcd858cc97fc06469b08b67d932fa143fd96275d", + "size": 23533, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/dcd858cc97fc06469b08b67d932fa143fd96275d" + }, + { + "path": "plugins", + "mode": "040000", + "type": "tree", + "sha": "780cade2ac406cd7ea33e1ed3915dfdc03151655", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/780cade2ac406cd7ea33e1ed3915dfdc03151655" + }, + { + "path": "plugins/ai", + "mode": "040000", + "type": "tree", + "sha": "60d1ed567fef497e99db3068f579812850e4f6a2", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/60d1ed567fef497e99db3068f579812850e4f6a2" + }, + { + "path": "plugins/ai/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "fec808b3349843eb3964d243a7574a52e13e5ae7", + "size": 2810, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/fec808b3349843eb3964d243a7574a52e13e5ae7" + }, + { + "path": "plugins/ai/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "8c813c7d6428e3bf09b4f3f85c4ebf241527f780", + "size": 7401, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/8c813c7d6428e3bf09b4f3f85c4ebf241527f780" + }, + { + "path": "plugins/ai/utils.py", + "mode": "100644", + "type": "blob", + "sha": "e22d1fde9481cb8813abf80dc8768ffb196b870d", + "size": 5511, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e22d1fde9481cb8813abf80dc8768ffb196b870d" + }, + { + "path": "plugins/alapi", + "mode": "040000", + "type": "tree", + "sha": "55ebd3fffa5b6830baf020901d7ccfdd1153064e", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/55ebd3fffa5b6830baf020901d7ccfdd1153064e" + }, + { + "path": "plugins/alapi/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "3efe41132d09b3195c32f0a31487eb8e4037cdcb", + "size": 284, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3efe41132d09b3195c32f0a31487eb8e4037cdcb" + }, + { + "path": "plugins/alapi/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "61037ab9c96c7d8ab67f784903071426c18faa12", + "size": 919, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/61037ab9c96c7d8ab67f784903071426c18faa12" + }, + { + "path": "plugins/alapi/comments_163.py", + "mode": "100644", + "type": "blob", + "sha": "d05a5aa97fec302a5c863bbc4c09500279321c93", + "size": 1593, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d05a5aa97fec302a5c863bbc4c09500279321c93" + }, + { + "path": "plugins/alapi/cover.py", + "mode": "100644", + "type": "blob", + "sha": "e26bf79cfd181ebe29ef07e89776d7a9b64dfa62", + "size": 1438, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e26bf79cfd181ebe29ef07e89776d7a9b64dfa62" + }, + { + "path": "plugins/alapi/jitang.py", + "mode": "100644", + "type": "blob", + "sha": "63dc93e29ede6397f3cb445c28deac71e9671fac", + "size": 1411, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/63dc93e29ede6397f3cb445c28deac71e9671fac" + }, + { + "path": "plugins/alapi/poetry.py", + "mode": "100644", + "type": "blob", + "sha": "4d35949805e5c4b271fe077ba7df293ac2436b75", + "size": 1530, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/4d35949805e5c4b271fe077ba7df293ac2436b75" + }, + { + "path": "plugins/bilibili_sub", + "mode": "040000", + "type": "tree", + "sha": "236b6a8ce846884fb7dbb10aab58f95782844c27", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/236b6a8ce846884fb7dbb10aab58f95782844c27" + }, + { + "path": "plugins/bilibili_sub/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "2eec6bd0c8208e4026b0fe1400838c161ac826c4", + "size": 14302, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/2eec6bd0c8208e4026b0fe1400838c161ac826c4" + }, + { + "path": "plugins/black_word", + "mode": "040000", + "type": "tree", + "sha": "8e2b6575f72316ae549999d32c82781dff8dbfbb", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/8e2b6575f72316ae549999d32c82781dff8dbfbb" + }, + { + "path": "plugins/black_word/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "3b45b804e6504536bb50589c777bb429be61d487", + "size": 465, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3b45b804e6504536bb50589c777bb429be61d487" + }, + { + "path": "plugins/black_word/black_watch.py", + "mode": "100644", + "type": "blob", + "sha": "133352a648f859c4ad9e95afe803744dd49cd016", + "size": 2116, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/133352a648f859c4ad9e95afe803744dd49cd016" + }, + { + "path": "plugins/black_word/black_word.py", + "mode": "100644", + "type": "blob", + "sha": "8164851f0cf4f9075fa6cea9525d44847739b7bc", + "size": 7451, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/8164851f0cf4f9075fa6cea9525d44847739b7bc" + }, + { + "path": "plugins/black_word/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "e985facc2bd751061cb7e28de7b0160d7aa7ac26", + "size": 2818, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e985facc2bd751061cb7e28de7b0160d7aa7ac26" + }, + { + "path": "plugins/black_word/model.py", + "mode": "100644", + "type": "blob", + "sha": "ef81c0ba751390cf9d500dfcb4c1a19a1c4944fe", + "size": 4754, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ef81c0ba751390cf9d500dfcb4c1a19a1c4944fe" + }, + { + "path": "plugins/black_word/utils.py", + "mode": "100644", + "type": "blob", + "sha": "53526bd0d12466c9694835d28dcc3ec7c95bf713", + "size": 12896, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/53526bd0d12466c9694835d28dcc3ec7c95bf713" + }, + { + "path": "plugins/bt", + "mode": "040000", + "type": "tree", + "sha": "662f698152b52f405f4e4eae2ae3ae829df6d84e", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/662f698152b52f405f4e4eae2ae3ae829df6d84e" + }, + { + "path": "plugins/bt/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "3aff4f8b1e77c80324240d0c1d602fb5cf594c90", + "size": 2390, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3aff4f8b1e77c80324240d0c1d602fb5cf594c90" + }, + { + "path": "plugins/bt/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "ad02a5d5961ada54e018f56c90f6842a598b912a", + "size": 1786, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ad02a5d5961ada54e018f56c90f6842a598b912a" + }, + { + "path": "plugins/check", + "mode": "040000", + "type": "tree", + "sha": "a334ab1bd11e8205294b054939bb08a3612ff627", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/a334ab1bd11e8205294b054939bb08a3612ff627" + }, + { + "path": "plugins/check/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "ee63b50dbee98d0bbfcad936fc8f2f688899b464", + "size": 1112, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ee63b50dbee98d0bbfcad936fc8f2f688899b464" + }, + { + "path": "plugins/check/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "78fbe7bae7c06cd2cd91bbc8cd47cfb83ec40833", + "size": 2612, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/78fbe7bae7c06cd2cd91bbc8cd47cfb83ec40833" + }, + { + "path": "plugins/coser.py", + "mode": "100644", + "type": "blob", + "sha": "90062d517b634456dd62bfdd9bab6fc5f65e14a7", + "size": 2803, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/90062d517b634456dd62bfdd9bab6fc5f65e14a7" + }, + { + "path": "plugins/dialogue", + "mode": "040000", + "type": "tree", + "sha": "612a62e9b02aa50821fb200d8c537720b5f05c2e", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/612a62e9b02aa50821fb200d8c537720b5f05c2e" + }, + { + "path": "plugins/dialogue/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "a99bc055851cb7b3c7f31fdb96d996d00ad0210a", + "size": 6551, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/a99bc055851cb7b3c7f31fdb96d996d00ad0210a" + }, + { + "path": "plugins/dialogue/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "440c8176ffed65e190afc6f51e363a586743d5af", + "size": 1098, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/440c8176ffed65e190afc6f51e363a586743d5af" + }, + { + "path": "plugins/draw_card", + "mode": "040000", + "type": "tree", + "sha": "e2d536d70d4f6290a0a4574c381715f5d581ee43", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/e2d536d70d4f6290a0a4574c381715f5d581ee43" + }, + { + "path": "plugins/draw_card/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "2aeeb30eb145b75fb6a0db9858e9be660e783849", + "size": 9788, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/2aeeb30eb145b75fb6a0db9858e9be660e783849" + }, + { + "path": "plugins/draw_card/config.py", + "mode": "100644", + "type": "blob", + "sha": "0aff3ef8b44f4489ec6a5e2caac2216e41e675c5", + "size": 5303, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/0aff3ef8b44f4489ec6a5e2caac2216e41e675c5" + }, + { + "path": "plugins/draw_card/count_manager.py", + "mode": "100644", + "type": "blob", + "sha": "7768b057c2ea57f272d792b8de59ca922f919e84", + "size": 4303, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7768b057c2ea57f272d792b8de59ca922f919e84" + }, + { + "path": "plugins/draw_card/handles", + "mode": "040000", + "type": "tree", + "sha": "4ac777c06c5e9d6f238da1060c5afb52c3d7a330", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/4ac777c06c5e9d6f238da1060c5afb52c3d7a330" + }, + { + "path": "plugins/draw_card/handles/azur_handle.py", + "mode": "100644", + "type": "blob", + "sha": "67242a774fe7fd30f2e3c490b687bf1492c7d689", + "size": 12045, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/67242a774fe7fd30f2e3c490b687bf1492c7d689" + }, + { + "path": "plugins/draw_card/handles/ba_handle.py", + "mode": "100644", + "type": "blob", + "sha": "d347504af4911bb5d641d439e22cae3cde55a333", + "size": 5424, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d347504af4911bb5d641d439e22cae3cde55a333" + }, + { + "path": "plugins/draw_card/handles/base_handle.py", + "mode": "100644", + "type": "blob", + "sha": "3483d246ed23aced34bab2873d2b2d08d898c4ed", + "size": 10314, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3483d246ed23aced34bab2873d2b2d08d898c4ed" + }, + { + "path": "plugins/draw_card/handles/fgo_handle.py", + "mode": "100644", + "type": "blob", + "sha": "5acb8c5f7480ffffa013d7131f27d6f146250f23", + "size": 8350, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/5acb8c5f7480ffffa013d7131f27d6f146250f23" + }, + { + "path": "plugins/draw_card/handles/genshin_handle.py", + "mode": "100644", + "type": "blob", + "sha": "61edcf30f6657fc65675fab39dfe33d200ef6d58", + "size": 18911, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/61edcf30f6657fc65675fab39dfe33d200ef6d58" + }, + { + "path": "plugins/draw_card/handles/guardian_handle.py", + "mode": "100644", + "type": "blob", + "sha": "517f126d91c2f23e2f6dbbb8fd8c9e493c9ad76d", + "size": 15892, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/517f126d91c2f23e2f6dbbb8fd8c9e493c9ad76d" + }, + { + "path": "plugins/draw_card/handles/onmyoji_handle.py", + "mode": "100644", + "type": "blob", + "sha": "25d05c3889d364edd4ed469cba04f41dd3804422", + "size": 6342, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/25d05c3889d364edd4ed469cba04f41dd3804422" + }, + { + "path": "plugins/draw_card/handles/pcr_handle.py", + "mode": "100644", + "type": "blob", + "sha": "666a684267ff5c99ae0c4c97a6f51e4e96994824", + "size": 5474, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/666a684267ff5c99ae0c4c97a6f51e4e96994824" + }, + { + "path": "plugins/draw_card/handles/pretty_handle.py", + "mode": "100644", + "type": "blob", + "sha": "535e2b19dc34ec9d700e42463b41226b3d064b7a", + "size": 16883, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/535e2b19dc34ec9d700e42463b41226b3d064b7a" + }, + { + "path": "plugins/draw_card/handles/prts_handle.py", + "mode": "100644", + "type": "blob", + "sha": "18a86fc3aae1b7fade1fba0207a6b3bc8eab8314", + "size": 14831, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/18a86fc3aae1b7fade1fba0207a6b3bc8eab8314" + }, + { + "path": "plugins/draw_card/rule.py", + "mode": "100644", + "type": "blob", + "sha": "49746d95b6a26ac507717cddf1559b552dcbcbe2", + "size": 233, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/49746d95b6a26ac507717cddf1559b552dcbcbe2" + }, + { + "path": "plugins/draw_card/util.py", + "mode": "100644", + "type": "blob", + "sha": "d0cefc91cb0eb3cb85e89f11434d4507ef0c6d89", + "size": 1736, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d0cefc91cb0eb3cb85e89f11434d4507ef0c6d89" + }, + { + "path": "plugins/epic", + "mode": "040000", + "type": "tree", + "sha": "a19b3af1c3af1f91f66eb9180678ecb39f5a2046", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/a19b3af1c3af1f91f66eb9180678ecb39f5a2046" + }, + { + "path": "plugins/epic/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "23c084aad72b9a550afb8d0a5235b1af43678235", + "size": 1442, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/23c084aad72b9a550afb8d0a5235b1af43678235" + }, + { + "path": "plugins/epic/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "583221fe8fae749209fd621be12cb9e548798ad1", + "size": 9035, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/583221fe8fae749209fd621be12cb9e548798ad1" + }, + { + "path": "plugins/fudu.py", + "mode": "100644", + "type": "blob", + "sha": "4b08ae12ce0c6b0a40f4e98505f114a8ec187a0a", + "size": 4779, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/4b08ae12ce0c6b0a40f4e98505f114a8ec187a0a" + }, + { + "path": "plugins/gold_redbag", + "mode": "040000", + "type": "tree", + "sha": "b70f26667d85b8a3c5d9c604092864a698ab09c0", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/b70f26667d85b8a3c5d9c604092864a698ab09c0" + }, + { + "path": "plugins/gold_redbag/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "7e6a1c28536e05a8b90740df579125c1eeaabcb4", + "size": 12756, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7e6a1c28536e05a8b90740df579125c1eeaabcb4" + }, + { + "path": "plugins/gold_redbag/config.py", + "mode": "100644", + "type": "blob", + "sha": "da8c0d396f330478b1ac97e80b1ff9037466f119", + "size": 12223, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/da8c0d396f330478b1ac97e80b1ff9037466f119" + }, + { + "path": "plugins/gold_redbag/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "ecc7dd3808bc78ec581cfb3d96bbb662821b9a0f", + "size": 8240, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ecc7dd3808bc78ec581cfb3d96bbb662821b9a0f" + }, + { + "path": "plugins/gold_redbag/model.py", + "mode": "100644", + "type": "blob", + "sha": "a8e9359a4a3233fb4b1f3731965108a14bcae6cd", + "size": 1995, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/a8e9359a4a3233fb4b1f3731965108a14bcae6cd" + }, + { + "path": "plugins/group_welcome_msg.py", + "mode": "100644", + "type": "blob", + "sha": "7148e8e9bfe6900911af1a3a8ac730365f7042bf", + "size": 1950, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7148e8e9bfe6900911af1a3a8ac730365f7042bf" + }, + { + "path": "plugins/image_management", + "mode": "040000", + "type": "tree", + "sha": "cc09c37a1e4e5b6b49b931b79ab4577c279e141b", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/cc09c37a1e4e5b6b49b931b79ab4577c279e141b" + }, + { + "path": "plugins/image_management/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "f6fdde85f99dad6ab35a82e198b883d6ec862b37", + "size": 1968, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/f6fdde85f99dad6ab35a82e198b883d6ec862b37" + }, + { + "path": "plugins/image_management/_config.py", + "mode": "100644", + "type": "blob", + "sha": "d5e01f587db050fc9c7d1aac91adcdcea55fd200", + "size": 215, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d5e01f587db050fc9c7d1aac91adcdcea55fd200" + }, + { + "path": "plugins/image_management/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "bc26b74f7133dc428cf03bee4a391bc1600bb560", + "size": 6322, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/bc26b74f7133dc428cf03bee4a391bc1600bb560" + }, + { + "path": "plugins/image_management/delete_image.py", + "mode": "100644", + "type": "blob", + "sha": "6cabb8e9df848690a4b81e79ffaf7754d0388b96", + "size": 3506, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/6cabb8e9df848690a4b81e79ffaf7754d0388b96" + }, + { + "path": "plugins/image_management/image_management_log.py", + "mode": "100644", + "type": "blob", + "sha": "756e58f25053ed4adeb2402e53eed69f10f54fce", + "size": 895, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/756e58f25053ed4adeb2402e53eed69f10f54fce" + }, + { + "path": "plugins/image_management/move_image.py", + "mode": "100644", + "type": "blob", + "sha": "6c1ec5e95fad95e4d728185345bde3873b529b42", + "size": 4697, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/6c1ec5e95fad95e4d728185345bde3873b529b42" + }, + { + "path": "plugins/image_management/send_image.py", + "mode": "100644", + "type": "blob", + "sha": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "size": 0, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" + }, + { + "path": "plugins/image_management/upload_image.py", + "mode": "100644", + "type": "blob", + "sha": "b69281a4f9eb781dd81324f5e6401c78ea211e81", + "size": 6690, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/b69281a4f9eb781dd81324f5e6401c78ea211e81" + }, + { + "path": "plugins/luxun.py", + "mode": "100644", + "type": "blob", + "sha": "1c1ab09c1353560e6dd4f092e45016076a34f648", + "size": 2254, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/1c1ab09c1353560e6dd4f092e45016076a34f648" + }, + { + "path": "plugins/mute", + "mode": "040000", + "type": "tree", + "sha": "04f58a6a69988418d0dbff1d8acbe2601fe51875", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/04f58a6a69988418d0dbff1d8acbe2601fe51875" + }, + { + "path": "plugins/mute/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "a11d4d39357fcf680d2c17cac8c49a2464a1b08b", + "size": 468, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/a11d4d39357fcf680d2c17cac8c49a2464a1b08b" + }, + { + "path": "plugins/mute/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "13c1cadf965c8d5c246480ac96c15e9e4187140d", + "size": 3891, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/13c1cadf965c8d5c246480ac96c15e9e4187140d" + }, + { + "path": "plugins/mute/mute_message.py", + "mode": "100644", + "type": "blob", + "sha": "e6946cd8440ea843fadbce348e3bb8064de3c46b", + "size": 2320, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e6946cd8440ea843fadbce348e3bb8064de3c46b" + }, + { + "path": "plugins/mute/mute_setting.py", + "mode": "100644", + "type": "blob", + "sha": "b7947ed69035869b887ab1e90afb0c1732a00dd9", + "size": 4043, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/b7947ed69035869b887ab1e90afb0c1732a00dd9" + }, + { + "path": "plugins/nbnhhsh.py", + "mode": "100644", + "type": "blob", + "sha": "7ab78aaaddf9b75516520aac0f74f7014df63118", + "size": 1918, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7ab78aaaddf9b75516520aac0f74f7014df63118" + }, + { + "path": "plugins/one_friend", + "mode": "040000", + "type": "tree", + "sha": "95c53cea06ec2bf1126704b5a9bd573838a27a9f", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/95c53cea06ec2bf1126704b5a9bd573838a27a9f" + }, + { + "path": "plugins/one_friend/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "7fa7c8bfd373f23fd515dee758c72244c6a2a307", + "size": 2952, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7fa7c8bfd373f23fd515dee758c72244c6a2a307" + }, + { + "path": "plugins/open_cases", + "mode": "040000", + "type": "tree", + "sha": "d85300068342d8725fa60a5bd3fbd1918124be73", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/d85300068342d8725fa60a5bd3fbd1918124be73" + }, + { + "path": "plugins/open_cases/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "2798942e805c62437ed5f424c6fe509fd56a3db1", + "size": 11529, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/2798942e805c62437ed5f424c6fe509fd56a3db1" + }, + { + "path": "plugins/open_cases/build_image.py", + "mode": "100644", + "type": "blob", + "sha": "8b8db8e2e4518c2689a8870ce3454fc015370ed4", + "size": 6798, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/8b8db8e2e4518c2689a8870ce3454fc015370ed4" + }, + { + "path": "plugins/open_cases/command.py", + "mode": "100644", + "type": "blob", + "sha": "ea86c2fc65b4f04ede6200fda8a8f140845190de", + "size": 1913, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ea86c2fc65b4f04ede6200fda8a8f140845190de" + }, + { + "path": "plugins/open_cases/config.py", + "mode": "100644", + "type": "blob", + "sha": "cefa7384d4dd783b6d6221238b072c4609d56e8b", + "size": 7343, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/cefa7384d4dd783b6d6221238b072c4609d56e8b" + }, + { + "path": "plugins/open_cases/models", + "mode": "040000", + "type": "tree", + "sha": "803a0682b01de9c453383a12ab24237e9657203d", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/803a0682b01de9c453383a12ab24237e9657203d" + }, + { + "path": "plugins/open_cases/models/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "size": 0, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" + }, + { + "path": "plugins/open_cases/models/buff_prices.py", + "mode": "100644", + "type": "blob", + "sha": "9f53de0e666453847d658a5a409696592292ef44", + "size": 536, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/9f53de0e666453847d658a5a409696592292ef44" + }, + { + "path": "plugins/open_cases/models/buff_skin.py", + "mode": "100644", + "type": "blob", + "sha": "7f51221a40d9d88344a31b814c68a46bbdc21e1c", + "size": 4211, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7f51221a40d9d88344a31b814c68a46bbdc21e1c" + }, + { + "path": "plugins/open_cases/models/buff_skin_log.py", + "mode": "100644", + "type": "blob", + "sha": "ac9fec952ca30d4cb2691820936c9c35b662debc", + "size": 1531, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ac9fec952ca30d4cb2691820936c9c35b662debc" + }, + { + "path": "plugins/open_cases/models/open_cases_log.py", + "mode": "100644", + "type": "blob", + "sha": "0c4f87bb6e061a19b039bc783fa8de0a72cc39f9", + "size": 1414, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/0c4f87bb6e061a19b039bc783fa8de0a72cc39f9" + }, + { + "path": "plugins/open_cases/models/open_cases_user.py", + "mode": "100644", + "type": "blob", + "sha": "3ed439372782ce4106c9b846ac29cda5c79d8e7c", + "size": 2160, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3ed439372782ce4106c9b846ac29cda5c79d8e7c" + }, + { + "path": "plugins/open_cases/open_cases_c.py", + "mode": "100644", + "type": "blob", + "sha": "f56aa9fef6d1c2ec098017a065c999639add45b2", + "size": 17693, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/f56aa9fef6d1c2ec098017a065c999639add45b2" + }, + { + "path": "plugins/open_cases/utils.py", + "mode": "100644", + "type": "blob", + "sha": "6fda2265c909c5ac5e9bc8f94bcb9651fa92fd11", + "size": 24033, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/6fda2265c909c5ac5e9bc8f94bcb9651fa92fd11" + }, + { + "path": "plugins/parse_bilibili", + "mode": "040000", + "type": "tree", + "sha": "f5cfedda6a64b13e02c16eff90ed61c23be168c7", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/f5cfedda6a64b13e02c16eff90ed61c23be168c7" + }, + { + "path": "plugins/parse_bilibili/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "dc43ec611c931c41176126a0a59e7d87791adab8", + "size": 7975, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/dc43ec611c931c41176126a0a59e7d87791adab8" + }, + { + "path": "plugins/parse_bilibili/get_image.py", + "mode": "100644", + "type": "blob", + "sha": "3b8c70c86aee56f7c33e070d4b7f8c7a62f2beda", + "size": 3958, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3b8c70c86aee56f7c33e070d4b7f8c7a62f2beda" + }, + { + "path": "plugins/parse_bilibili/information_container.py", + "mode": "100644", + "type": "blob", + "sha": "ddb685f8bcd9b5becd06dca6a4f07b7d63f40167", + "size": 1221, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ddb685f8bcd9b5becd06dca6a4f07b7d63f40167" + }, + { + "path": "plugins/parse_bilibili/parse_url.py", + "mode": "100644", + "type": "blob", + "sha": "b4e2a1fe4a63a79270be39d29462c11b11295111", + "size": 2249, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/b4e2a1fe4a63a79270be39d29462c11b11295111" + }, + { + "path": "plugins/pid_search.py", + "mode": "100644", + "type": "blob", + "sha": "97fc4d40f18877f7896471001d6f3b4a2cec2300", + "size": 4661, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/97fc4d40f18877f7896471001d6f3b4a2cec2300" + }, + { + "path": "plugins/pix_gallery", + "mode": "040000", + "type": "tree", + "sha": "aaa0152da0e592c4ef2e2971f5745754a71bb5c8", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/aaa0152da0e592c4ef2e2971f5745754a71bb5c8" + }, + { + "path": "plugins/pix_gallery/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "7dd5fa99506d5948d58301d3648ea94f974889e1", + "size": 2129, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7dd5fa99506d5948d58301d3648ea94f974889e1" + }, + { + "path": "plugins/pix_gallery/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "7e9db22194708c254bb8378f5d081ded231bd65f", + "size": 14012, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7e9db22194708c254bb8378f5d081ded231bd65f" + }, + { + "path": "plugins/pix_gallery/_model", + "mode": "040000", + "type": "tree", + "sha": "a4736e1bf36097384def6dae4bf4688567c168d7", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/a4736e1bf36097384def6dae4bf4688567c168d7" + }, + { + "path": "plugins/pix_gallery/_model/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "8b137891791fe96927ad78e64b0aad7bded08bdc", + "size": 1, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/8b137891791fe96927ad78e64b0aad7bded08bdc" + }, + { + "path": "plugins/pix_gallery/_model/omega_pixiv_illusts.py", + "mode": "100644", + "type": "blob", + "sha": "17e2156c4dda0b6d883b690a65449348cc91ad47", + "size": 2655, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/17e2156c4dda0b6d883b690a65449348cc91ad47" + }, + { + "path": "plugins/pix_gallery/_model/pixiv.py", + "mode": "100644", + "type": "blob", + "sha": "3451781df41ed3de925be513c2600c96c3c19e14", + "size": 2608, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3451781df41ed3de925be513c2600c96c3c19e14" + }, + { + "path": "plugins/pix_gallery/_model/pixiv_keyword_user.py", + "mode": "100644", + "type": "blob", + "sha": "5de544a5d5360dc33ec236b89e2f23f7b28775e4", + "size": 1717, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/5de544a5d5360dc33ec236b89e2f23f7b28775e4" + }, + { + "path": "plugins/pix_gallery/pix.py", + "mode": "100644", + "type": "blob", + "sha": "2f8d25c386012dcb2d56fc109e1df8d6f8b75e3a", + "size": 8915, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/2f8d25c386012dcb2d56fc109e1df8d6f8b75e3a" + }, + { + "path": "plugins/pix_gallery/pix_add_keyword.py", + "mode": "100644", + "type": "blob", + "sha": "452213e3bcc501f31b1454a061a6048f39921f2b", + "size": 4722, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/452213e3bcc501f31b1454a061a6048f39921f2b" + }, + { + "path": "plugins/pix_gallery/pix_pass_del_keyword.py", + "mode": "100644", + "type": "blob", + "sha": "9a8f2ea774446880612edc21d72c1502439f66ee", + "size": 7558, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/9a8f2ea774446880612edc21d72c1502439f66ee" + }, + { + "path": "plugins/pix_gallery/pix_show_info.py", + "mode": "100644", + "type": "blob", + "sha": "cb1cbf2a9efc5ff23d0f798124b7faf0a6125943", + "size": 3172, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/cb1cbf2a9efc5ff23d0f798124b7faf0a6125943" + }, + { + "path": "plugins/pix_gallery/pix_update.py", + "mode": "100644", + "type": "blob", + "sha": "b0f209dc016d610a5fb51c3fbbd6a47c251b9e3c", + "size": 8134, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/b0f209dc016d610a5fb51c3fbbd6a47c251b9e3c" + }, + { + "path": "plugins/pixiv_rank_search", + "mode": "040000", + "type": "tree", + "sha": "17665e6505c0255a9d4d4c1371641127b9a83eb9", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/17665e6505c0255a9d4d4c1371641127b9a83eb9" + }, + { + "path": "plugins/pixiv_rank_search/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "01945cd85acb629a20c08a9ac75030aa0865bba5", + "size": 7377, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/01945cd85acb629a20c08a9ac75030aa0865bba5" + }, + { + "path": "plugins/pixiv_rank_search/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "761a93f20eba886122c134c7e6d2df884a0d9dc7", + "size": 5585, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/761a93f20eba886122c134c7e6d2df884a0d9dc7" + }, + { + "path": "plugins/poke", + "mode": "040000", + "type": "tree", + "sha": "88decde53947fc3cae4d95b7ec9105e4c8bb37f8", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/88decde53947fc3cae4d95b7ec9105e4c8bb37f8" + }, + { + "path": "plugins/poke/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "38dafc82466013c5f3fa00d3609c55f000afd2cf", + "size": 3319, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/38dafc82466013c5f3fa00d3609c55f000afd2cf" + }, + { + "path": "plugins/quotations.py", + "mode": "100644", + "type": "blob", + "sha": "e213ee04d6196c5b34892c7699851e7477cafcb1", + "size": 1112, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e213ee04d6196c5b34892c7699851e7477cafcb1" + }, + { + "path": "plugins/roll.py", + "mode": "100644", + "type": "blob", + "sha": "1c21421b648123a3bbe29c093c63bddf9523cdb6", + "size": 2217, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/1c21421b648123a3bbe29c093c63bddf9523cdb6" + }, + { + "path": "plugins/russian", + "mode": "040000", + "type": "tree", + "sha": "a284de1ceeaa4c483a5e61e328d2722f15eb37bf", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/a284de1ceeaa4c483a5e61e328d2722f15eb37bf" + }, + { + "path": "plugins/russian/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "d91323c73ca65d6380d4556d542a2399c9da4728", + "size": 7545, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d91323c73ca65d6380d4556d542a2399c9da4728" + }, + { + "path": "plugins/russian/command.py", + "mode": "100644", + "type": "blob", + "sha": "de9d186cbad0316cac75e39d68526aa1605bf4b9", + "size": 2259, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/de9d186cbad0316cac75e39d68526aa1605bf4b9" + }, + { + "path": "plugins/russian/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "73cdb078bd700ecf5744eb73c1cdee5ce0839192", + "size": 20332, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/73cdb078bd700ecf5744eb73c1cdee5ce0839192" + }, + { + "path": "plugins/russian/model.py", + "mode": "100644", + "type": "blob", + "sha": "0fab9298c9ad713dac6292da0bec68c96ce883cc", + "size": 3633, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/0fab9298c9ad713dac6292da0bec68c96ce883cc" + }, + { + "path": "plugins/search_anime", + "mode": "040000", + "type": "tree", + "sha": "b65e7639bc2dc2e4b5383333af10034161b10388", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/b65e7639bc2dc2e4b5383333af10034161b10388" + }, + { + "path": "plugins/search_anime/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "d12ad03ecf39f9897aace7235ed2cf918f1e4540", + "size": 2291, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d12ad03ecf39f9897aace7235ed2cf918f1e4540" + }, + { + "path": "plugins/search_anime/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "59d0ac6159e429f1a31450559ab072d8ef8e483f", + "size": 1810, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/59d0ac6159e429f1a31450559ab072d8ef8e483f" + }, + { + "path": "plugins/search_buff_skin_price", + "mode": "040000", + "type": "tree", + "sha": "36b3f5e8a80056bc3e334f72640fec1c0af39418", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/36b3f5e8a80056bc3e334f72640fec1c0af39418" + }, + { + "path": "plugins/search_buff_skin_price/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "ca2a320fa66a8cb7a7ca9b91a1a061d3fa459a09", + "size": 3387, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/ca2a320fa66a8cb7a7ca9b91a1a061d3fa459a09" + }, + { + "path": "plugins/search_buff_skin_price/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "8dbe6a596286e8db8b9e026a1c81cf076c4b5448", + "size": 2188, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/8dbe6a596286e8db8b9e026a1c81cf076c4b5448" + }, + { + "path": "plugins/search_image", + "mode": "040000", + "type": "tree", + "sha": "53a699804e22730f01ae09b8cc8a1ebe1398c28d", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/53a699804e22730f01ae09b8cc8a1ebe1398c28d" + }, + { + "path": "plugins/search_image/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "38e86de0caafe6c3e88d973fb5c4bc9d1430d213", + "size": 3010, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/38e86de0caafe6c3e88d973fb5c4bc9d1430d213" + }, + { + "path": "plugins/send_setu_", + "mode": "040000", + "type": "tree", + "sha": "113028d9c4100b30b2996c39c0324d05b242464f", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/113028d9c4100b30b2996c39c0324d05b242464f" + }, + { + "path": "plugins/send_setu_/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "eb35e275dae8dab8d2bc4145146ee3f1fe5c1b78", + "size": 101, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/eb35e275dae8dab8d2bc4145146ee3f1fe5c1b78" + }, + { + "path": "plugins/send_setu_/_model.py", + "mode": "100644", + "type": "blob", + "sha": "865af7d13c368cfaf8a90e0747652ad912519810", + "size": 2580, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/865af7d13c368cfaf8a90e0747652ad912519810" + }, + { + "path": "plugins/send_setu_/send_setu", + "mode": "040000", + "type": "tree", + "sha": "e68acc01dbf796c0f1eee93e4d9992dfc09cbe36", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/e68acc01dbf796c0f1eee93e4d9992dfc09cbe36" + }, + { + "path": "plugins/send_setu_/send_setu/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "fea1ad41ef615c6d1cabd06a49c412075560cf4d", + "size": 8491, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/fea1ad41ef615c6d1cabd06a49c412075560cf4d" + }, + { + "path": "plugins/send_setu_/send_setu/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "a860aa0629678e7f774b7ef74951458c328ee81e", + "size": 12839, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/a860aa0629678e7f774b7ef74951458c328ee81e" + }, + { + "path": "plugins/send_setu_/update_setu", + "mode": "040000", + "type": "tree", + "sha": "ddcc0dfe51ba6a7a0c34c6acbbedb175c2cf8c6c", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/ddcc0dfe51ba6a7a0c34c6acbbedb175c2cf8c6c" + }, + { + "path": "plugins/send_setu_/update_setu/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "2b5b6ae93d952646fdbf271b64212018d6da7ce6", + "size": 1854, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/2b5b6ae93d952646fdbf271b64212018d6da7ce6" + }, + { + "path": "plugins/send_setu_/update_setu/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "07d217d6ed53e7e9ad7ba69c560bb1196d74d5f3", + "size": 7551, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/07d217d6ed53e7e9ad7ba69c560bb1196d74d5f3" + }, + { + "path": "plugins/send_voice", + "mode": "040000", + "type": "tree", + "sha": "9ebdbf817f193e02f99bfd40206cad3603749168", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/9ebdbf817f193e02f99bfd40206cad3603749168" + }, + { + "path": "plugins/send_voice/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "eb35e275dae8dab8d2bc4145146ee3f1fe5c1b78", + "size": 101, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/eb35e275dae8dab8d2bc4145146ee3f1fe5c1b78" + }, + { + "path": "plugins/send_voice/dinggong.py", + "mode": "100644", + "type": "blob", + "sha": "a01129ca1d0a6081df09e4411a7babc6f3c44a1a", + "size": 1595, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/a01129ca1d0a6081df09e4411a7babc6f3c44a1a" + }, + { + "path": "plugins/translate", + "mode": "040000", + "type": "tree", + "sha": "44d79a31ab9a00d2ea01801968bff5192bc81976", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/44d79a31ab9a00d2ea01801968bff5192bc81976" + }, + { + "path": "plugins/translate/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "372a57740d2341e9aa460e74d09548ebf317ae09", + "size": 3067, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/372a57740d2341e9aa460e74d09548ebf317ae09" + }, + { + "path": "plugins/translate/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "a7a3018d0d75379b6a5d74d5b8d808f1b2b39350", + "size": 2049, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/a7a3018d0d75379b6a5d74d5b8d808f1b2b39350" + }, + { + "path": "plugins/wbtop", + "mode": "040000", + "type": "tree", + "sha": "d87a6fab1133ca86eeade93cdd149a279385fcab", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/d87a6fab1133ca86eeade93cdd149a279385fcab" + }, + { + "path": "plugins/wbtop/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "fce1b99540d3b81903f356b9157b97fbe166c287", + "size": 1906, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/fce1b99540d3b81903f356b9157b97fbe166c287" + }, + { + "path": "plugins/wbtop/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "e9c206273fd1f46ceb6d2b1e276d80f0e97ad145", + "size": 2203, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/e9c206273fd1f46ceb6d2b1e276d80f0e97ad145" + }, + { + "path": "plugins/what_anime", + "mode": "040000", + "type": "tree", + "sha": "a43f876cccb0e60c5360b5382ce9f6311d924c3c", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/a43f876cccb0e60c5360b5382ce9f6311d924c3c" + }, + { + "path": "plugins/what_anime/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "d3b918760566cebae8685022923ee9cfdafa4d91", + "size": 1825, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/d3b918760566cebae8685022923ee9cfdafa4d91" + }, + { + "path": "plugins/what_anime/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "15801f624f6298e33b6f5423c68d1d7110be4a6f", + "size": 1883, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/15801f624f6298e33b6f5423c68d1d7110be4a6f" + }, + { + "path": "plugins/word_bank", + "mode": "040000", + "type": "tree", + "sha": "9437ca1b8da4360fb24aac46c037f98fdcc768ef", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/9437ca1b8da4360fb24aac46c037f98fdcc768ef" + }, + { + "path": "plugins/word_bank/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "519a269a7aa377a0be8755142e9223aeff96d5fd", + "size": 724, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/519a269a7aa377a0be8755142e9223aeff96d5fd" + }, + { + "path": "plugins/word_bank/_command.py", + "mode": "100644", + "type": "blob", + "sha": "7dae391a25b418530c539f16c61baa18f415d7ae", + "size": 1122, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/7dae391a25b418530c539f16c61baa18f415d7ae" + }, + { + "path": "plugins/word_bank/_config.py", + "mode": "100644", + "type": "blob", + "sha": "72c4c584f01e158e4d9f6b250ccf8078abf8abae", + "size": 763, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/72c4c584f01e158e4d9f6b250ccf8078abf8abae" + }, + { + "path": "plugins/word_bank/_data_source.py", + "mode": "100644", + "type": "blob", + "sha": "03bc28709c2c38408d130c218775f7f82ae02458", + "size": 9712, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/03bc28709c2c38408d130c218775f7f82ae02458" + }, + { + "path": "plugins/word_bank/_model.py", + "mode": "100644", + "type": "blob", + "sha": "eb00610d6863cc02dbf0390753213e2cd4fe3816", + "size": 20494, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/eb00610d6863cc02dbf0390753213e2cd4fe3816" + }, + { + "path": "plugins/word_bank/_rule.py", + "mode": "100644", + "type": "blob", + "sha": "f64daa75fe7b200c65af02b98f6198f5d1f492ba", + "size": 1979, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/f64daa75fe7b200c65af02b98f6198f5d1f492ba" + }, + { + "path": "plugins/word_bank/message_handle.py", + "mode": "100644", + "type": "blob", + "sha": "07a4b518f18a3c58076eb2e307d56ecb6e829722", + "size": 959, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/07a4b518f18a3c58076eb2e307d56ecb6e829722" + }, + { + "path": "plugins/word_bank/read_word_bank.py", + "mode": "100644", + "type": "blob", + "sha": "bc655e5d298c49a57079e49a5441b2ab4a4d1448", + "size": 2863, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/bc655e5d298c49a57079e49a5441b2ab4a4d1448" + }, + { + "path": "plugins/word_bank/word_handle.py", + "mode": "100644", + "type": "blob", + "sha": "4917229f3a71138b43b9d27a49144f0afcb404e3", + "size": 11914, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/4917229f3a71138b43b9d27a49144f0afcb404e3" + }, + { + "path": "plugins/word_clouds", + "mode": "040000", + "type": "tree", + "sha": "26ae75043fdc51b1e570d3db2df617e1f0e8540e", + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/trees/26ae75043fdc51b1e570d3db2df617e1f0e8540e" + }, + { + "path": "plugins/word_clouds/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "c9873adef7c988847bb23e95d0b0361eb336ef1c", + "size": 6787, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/c9873adef7c988847bb23e95d0b0361eb336ef1c" + }, + { + "path": "plugins/word_clouds/command.py", + "mode": "100644", + "type": "blob", + "sha": "3b03044fd1027cc52849e82ac0f5585f6268b184", + "size": 933, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/3b03044fd1027cc52849e82ac0f5585f6268b184" + }, + { + "path": "plugins/word_clouds/data_source.py", + "mode": "100644", + "type": "blob", + "sha": "fd6fda73fcece4f552c12bf639b3f10460befeab", + "size": 4089, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/fd6fda73fcece4f552c12bf639b3f10460befeab" + }, + { + "path": "plugins/word_clouds/requirement.txt", + "mode": "100644", + "type": "blob", + "sha": "8e022688417114d01ea440354dea9c60e1c50799", + "size": 30, + "url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/8e022688417114d01ea440354dea9c60e1c50799" + } + ], + "truncated": false +} diff --git a/tests/response/plugin_store/zhenxun_github_sub_metadata.json b/tests/response/plugin_store/zhenxun_github_sub_metadata.json new file mode 100644 index 00000000..421fd889 --- /dev/null +++ b/tests/response/plugin_store/zhenxun_github_sub_metadata.json @@ -0,0 +1,23 @@ +{ + "type": "gh", + "name": "xuanerwa/zhenxun_github_sub", + "version": "main", + "default": null, + "files": [ + { + "type": "directory", + "name": "github_sub", + "files": [ + { + "type": "file", + "name": "__init__.py", + "hash": "z1C5BBK0+atbDghbyRlF2xIDwk0HQdHM1yXQZkF7/t8=", + "size": 7551 + } + ] + } + ], + "links": { + "stats": "https://data.jsdelivr.com/v1/stats/packages/gh/xuanerwa/zhenxun_github_sub@main" + } +} diff --git a/tests/response/plugin_store/zhenxun_github_sub_tree.json b/tests/response/plugin_store/zhenxun_github_sub_tree.json new file mode 100644 index 00000000..75c38a54 --- /dev/null +++ b/tests/response/plugin_store/zhenxun_github_sub_tree.json @@ -0,0 +1,38 @@ +{ + "sha": "438298b9e88f9dafa7020e99d7c7b4c98f93aea6", + "url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/438298b9e88f9dafa7020e99d7c7b4c98f93aea6", + "tree": [ + { + "path": "LICENSE", + "mode": "100644", + "type": "blob", + "sha": "f288702d2fa16d3cdf0035b15a9fcbc552cd88e7", + "size": 35149, + "url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/f288702d2fa16d3cdf0035b15a9fcbc552cd88e7" + }, + { + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "e974cfc9b973d4a041f03e693ea20563a933b7ca", + "size": 955, + "url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/e974cfc9b973d4a041f03e693ea20563a933b7ca" + }, + { + "path": "github_sub", + "mode": "040000", + "type": "tree", + "sha": "0f7d76bcf472e2ab0610fa542b067633d6e3ae7e", + "url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/0f7d76bcf472e2ab0610fa542b067633d6e3ae7e" + }, + { + "path": "github_sub/__init__.py", + "mode": "100644", + "type": "blob", + "sha": "7d17fd49fe82fa3897afcef61b2c694ed93a4ba3", + "size": 7551, + "url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/7d17fd49fe82fa3897afcef61b2c694ed93a4ba3" + } + ], + "truncated": false +} diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..f05aa8e9 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,67 @@ +import json +from pathlib import Path + +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegment +from nonebot.adapters.onebot.v11.event import Sender + + +def get_response_json(base_path: Path, file: str) -> dict: + try: + return json.loads( + (Path(__file__).parent / "response" / base_path / file).read_text( + encoding="utf8" + ) + ) + except (FileNotFoundError, json.JSONDecodeError) as e: + raise ValueError(f"Error reading or parsing JSON file: {e}") from e + + +def get_content_bytes(base_path: Path, path: str) -> bytes: + try: + return (Path(__file__).parent / "content" / base_path / path).read_bytes() + except FileNotFoundError as e: + raise ValueError(f"Error reading file: {e}") from e + + +def _v11_group_message_event( + message: str, + self_id: int, + user_id: int, + group_id: int, + message_id: int, + to_me: bool = True, +) -> GroupMessageEvent: + return GroupMessageEvent( + time=1122, + self_id=self_id, + post_type="message", + sub_type="", + user_id=user_id, + message_id=message_id, + message=Message(message), + original_message=Message(message), + message_type="group", + raw_message=message, + font=1, + sender=Sender(user_id=user_id), + to_me=to_me, + group_id=group_id, + ) + + +def _v11_private_message_send( + message: str, + user_id: int, +): + return { + "message_type": "private", + "user_id": user_id, + "message": [ + MessageSegment( + type="text", + data={ + "text": message, + }, + ) + ], + } diff --git a/zhenxun/builtin_plugins/__init__.py b/zhenxun/builtin_plugins/__init__.py index 5e3d6f94..fbaeb280 100644 --- a/zhenxun/builtin_plugins/__init__.py +++ b/zhenxun/builtin_plugins/__init__.py @@ -1,34 +1,58 @@ +from datetime import datetime import uuid -from nonebot import require +import nonebot +from nonebot.adapters import Bot from nonebot.drivers import Driver from tortoise import Tortoise from tortoise.exceptions import OperationalError +import ujson as json +from zhenxun.models.bot_connect_log import BotConnectLog +from zhenxun.models.bot_console import BotConsole from zhenxun.models.goods_info import GoodsInfo from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.sign_user import SignUser from zhenxun.models.user_console import UserConsole from zhenxun.services.log import logger from zhenxun.utils.decorator.shop import shop_register - -require("nonebot_plugin_apscheduler") -require("nonebot_plugin_alconna") -require("nonebot_plugin_session") -require("nonebot_plugin_userinfo") -require("nonebot_plugin_htmlrender") - - -import nonebot -import ujson as json +from zhenxun.utils.manager.resource_manager import ResourceManager +from zhenxun.utils.platform import PlatformUtils driver: Driver = nonebot.get_driver() +@driver.on_bot_connect +async def _(bot: Bot): + logger.debug(f"Bot: {bot.self_id} 建立连接...") + await BotConnectLog.create( + bot_id=bot.self_id, platform=bot.adapter, connect_time=datetime.now(), type=1 + ) + if not await BotConsole.exists(bot_id=bot.self_id): + await BotConsole.create( + bot_id=bot.self_id, platform=PlatformUtils.get_platform(bot) + ) + + +@driver.on_bot_disconnect +async def _(bot: Bot): + logger.debug(f"Bot: {bot.self_id} 断开连接...") + try: + await BotConnectLog.create( + bot_id=bot.self_id, + platform=bot.adapter, + connect_time=datetime.now(), + type=0, + ) + except Exception as e: + logger.warning(f"记录bot: {bot.self_id} 断开连接失败", e=e) + + SIGN_SQL = """ -select distinct on("user_id") t1.user_id, t1.checkin_count, t1.add_probability, t1.specify_probability, t1.impression +select distinct on("user_id") t1.user_id, t1.checkin_count, t1.add_probability, +t1.specify_probability, t1.impression from public.sign_group_users t1 - join ( + join ( select user_id, max(t2.impression) as max_impression from public.sign_group_users t2 group by user_id @@ -38,7 +62,7 @@ from public.sign_group_users t1 BAG_SQL = """ select t1.user_id, t1.gold, t1.property from public.bag_users t1 - join ( + join ( select user_id, max(t2.gold) as max_gold from public.bag_users t2 group by user_id @@ -48,6 +72,7 @@ from public.bag_users t1 @driver.on_startup async def _(): + await ResourceManager.init_resources() """签到与用户的数据迁移""" if goods_list := await GoodsInfo.filter(uuid__isnull=True).all(): for goods in goods_list: @@ -62,8 +87,8 @@ async def _(): group_user = [] try: group_user = await GroupInfoUser.filter(uid__isnull=False).all() - except Exception: - logger.warning("获取GroupInfoUser数据uid失败...") + except Exception as e: + logger.warning("获取GroupInfoUser数据uid失败...", e=e) user2uid = {u.user_id: u.uid for u in group_user} db = Tortoise.get_connection("default") old_sign_list = await db.execute_query_dict(SIGN_SQL) @@ -74,15 +99,12 @@ async def _(): } create_list = [] sign_id_list = [] - max_uid = 0 - if user2uid: - max_uid = max(user2uid.values()) + 1 + max_uid = max(user2uid.values()) + 1 if user2uid else 0 for old_sign in old_sign_list: sign_id_list.append(old_sign["user_id"]) - old_bag = [ + if old_bag := [ b for b in old_bag_list if b["user_id"] == old_sign["user_id"] - ] - if old_bag: + ]: old_bag = old_bag[0] property = json.loads(old_bag["property"]) props = {} @@ -115,9 +137,9 @@ async def _(): create_list.clear() uc_dict = {u.user_id: u for u in await UserConsole.all()} for old_sign in old_sign_list: - user_console = uc_dict.get(old_sign["user_id"]) - if not user_console: - user_console = await UserConsole.get_user(old_sign["user_id"], "qq") + user_console = uc_dict.get( + old_sign["user_id"] + ) or await UserConsole.get_user(old_sign["user_id"], "qq") create_list.append( SignUser( user_id=old_sign["user_id"], diff --git a/zhenxun/plugins/about.py b/zhenxun/builtin_plugins/about.py similarity index 53% rename from zhenxun/plugins/about.py rename to zhenxun/builtin_plugins/about.py index f2bcbdb6..faa0ba0e 100644 --- a/zhenxun/plugins/about.py +++ b/zhenxun/builtin_plugins/about.py @@ -1,13 +1,16 @@ from pathlib import Path +import aiofiles from nonebot.plugin import PluginMetadata from nonebot.rule import to_me from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo +from zhenxun.configs.path_config import DATA_PATH from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils __plugin_meta__ = PluginMetadata( name="关于", @@ -16,7 +19,7 @@ __plugin_meta__ = PluginMetadata( 指令: 关于 """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1", menu_type="其他").dict(), + extra=PluginExtraData(author="HibiKier", version="0.1", menu_type="其他").to_dict(), ) @@ -24,18 +27,33 @@ _matcher = on_alconna(Alconna("关于"), priority=5, block=True, rule=to_me()) @_matcher.handle() -async def _(session: EventSession, arparma: Arparma): +async def _(session: Uninfo, arparma: Arparma): ver_file = Path() / "__version__" version = None if ver_file.exists(): - with open(ver_file, "r", encoding="utf8") as f: - version = f.read().split(":")[-1].strip() - info = f""" + async with aiofiles.open(ver_file, encoding="utf8") as f: + if text := await f.read(): + version = text.split(":")[-1].strip() + if PlatformUtils.is_qbot(session): + info: list[str | Path] = [ + f""" +『绪山真寻Bot』 +版本:{version} +简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处 + """.strip() + ] + path = DATA_PATH / "about.png" + if path.exists(): + info.append(path) + else: + info = [ + f""" 『绪山真寻Bot』 版本:{version} 简介:基于Nonebot2开发,支持多平台,是一个非常可爱的Bot呀,希望与大家要好好相处 项目地址:https://github.com/HibiKier/zhenxun_bot 文档地址:https://hibikier.github.io/zhenxun_bot/ - """.strip() - await MessageUtils.build_message(info).send() + """.strip() + ] + await MessageUtils.build_message(info).send() # type: ignore logger.info("查看关于", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/admin/admin_help.py b/zhenxun/builtin_plugins/admin/admin_help.py deleted file mode 100644 index e98df047..00000000 --- a/zhenxun/builtin_plugins/admin/admin_help.py +++ /dev/null @@ -1,160 +0,0 @@ -import nonebot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_alconna.matcher import AlconnaMatcher -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.exception import EmptyError -from zhenxun.utils.image_utils import ( - BuildImage, - build_sort_image, - group_image, - text2image, -) -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import admin_check, ensure_group - -__plugin_meta__ = PluginMetadata( - name="群组管理员帮助", - description="管理员帮助列表", - usage=""" - 管理员帮助 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=1, - ).dict(), -) - -_matcher = on_alconna( - Alconna("管理员帮助"), - rule=admin_check(1) & ensure_group, - priority=5, - block=True, -) - - -ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png" -if ADMIN_HELP_IMAGE.exists(): - ADMIN_HELP_IMAGE.unlink() - - -async def build_help() -> BuildImage: - """构造管理员帮助图片 - - 异常: - EmptyError: 管理员帮助为空 - - 返回: - BuildImage: 管理员帮助图片 - """ - plugin_list = await PluginInfo.filter( - plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN] - ).all() - data_list = [] - for plugin in plugin_list: - if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): - if _plugin.metadata: - data_list.append({"plugin": plugin, "metadata": _plugin.metadata}) - font = BuildImage.load_font("HYWenHei-85W.ttf", 20) - image_list = [] - for data in data_list: - plugin = data["plugin"] - metadata = data["metadata"] - try: - usage = None - description = None - if metadata.usage: - usage = await text2image( - metadata.usage, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - if metadata.description: - description = await text2image( - metadata.description, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - width = 0 - height = 100 - if usage: - width = usage.width - height += usage.height - if description and description.width > width: - width = description.width - height += description.height - font_width, font_height = BuildImage.get_text_size( - plugin.name + f"[{plugin.level}]", font - ) - if font_width > width: - width = font_width - A = BuildImage(width + 30, height + 120, "#EAEDF2") - await A.text((15, 10), plugin.name + f"[{plugin.level}]") - await A.text((15, 70), "简介:") - if not description: - description = BuildImage(A.width - 30, 30, (255, 255, 255)) - await description.circle_corner(10) - await A.paste(description, (15, 100)) - if not usage: - usage = BuildImage(A.width - 30, 30, (255, 255, 255)) - await usage.circle_corner(10) - await A.text((15, description.height + 115), "用法:") - await A.paste(usage, (15, description.height + 145)) - await A.circle_corner(10) - image_list.append(A) - except Exception as e: - logger.warning( - f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...", - "管理员帮助", - e=e, - ) - if task_list := await TaskInfo.all(): - task_str = "\n".join([task.name for task in task_list]) - task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str - task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) - await task_image.circle_corner(10) - A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") - await A.text((25, 10), "被动技能") - await A.paste(task_image, (25, 50)) - await A.circle_corner(10) - image_list.append(A) - if not image_list: - raise EmptyError() - image_group, _ = group_image(image_list) - A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) - text = await BuildImage.build_text_image( - "群管理员帮助", - size=40, - ) - tip = await BuildImage.build_text_image( - "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" - ) - await A.paste(text, (50, 30)) - await A.paste(tip, (50, 90)) - await A.save(ADMIN_HELP_IMAGE) - return BuildImage(1, 1) - - -@_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, -): - if not ADMIN_HELP_IMAGE.exists(): - try: - await build_help() - except EmptyError: - await MessageUtils.build_message("管理员帮助为空").finish(reply_to=True) - await MessageUtils.build_message(ADMIN_HELP_IMAGE).send() - logger.info("查看管理员帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/admin/admin_help/__init__.py b/zhenxun/builtin_plugins/admin/admin_help/__init__.py new file mode 100644 index 00000000..094facbe --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/__init__.py @@ -0,0 +1,63 @@ +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.config import Config +from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.rules import admin_check, ensure_group + +from .config import ADMIN_HELP_IMAGE +from .html_help import build_html_help +from .normal_help import build_help + +__plugin_meta__ = PluginMetadata( + name="群组管理员帮助", + description="管理员帮助列表", + usage=""" + 管理员帮助 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.ADMIN, + admin_level=1, + configs=[ + RegisterConfig( + key="type", + value="zhenxun", + help="管理员帮助样式,normal, zhenxun", + default_value="zhenxun", + ) + ], + ).to_dict(), +) + +_matcher = on_alconna( + Alconna("管理员帮助"), + rule=admin_check(1) & ensure_group, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _( + session: EventSession, + arparma: Arparma, +): + if not ADMIN_HELP_IMAGE.exists(): + try: + if Config.get_config("admin_help", "type") == "zhenxun": + await build_html_help() + else: + await build_help() + except EmptyError: + await MessageUtils.build_message("当前管理员帮助为空...").finish( + reply_to=True + ) + await MessageUtils.build_message(ADMIN_HELP_IMAGE).send() + logger.info("查看管理员帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/admin/admin_help/config.py b/zhenxun/builtin_plugins/admin/admin_help/config.py new file mode 100644 index 00000000..86a12daa --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/config.py @@ -0,0 +1,23 @@ +from nonebot.plugin import PluginMetadata +from pydantic import BaseModel + +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.plugin_info import PluginInfo + +ADMIN_HELP_IMAGE = IMAGE_PATH / "ADMIN_HELP.png" +if ADMIN_HELP_IMAGE.exists(): + ADMIN_HELP_IMAGE.unlink() + + +class PluginData(BaseModel): + """ + 插件信息 + """ + + plugin: PluginInfo + """插件信息""" + metadata: PluginMetadata + """元数据""" + + class Config: + arbitrary_types_allowed = True diff --git a/zhenxun/builtin_plugins/admin/admin_help/html_help.py b/zhenxun/builtin_plugins/admin/admin_help/html_help.py new file mode 100644 index 00000000..69699bbb --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/html_help.py @@ -0,0 +1,55 @@ +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.builtin_plugins.admin.admin_help.config import ADMIN_HELP_IMAGE +from zhenxun.configs.config import BotConfig +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.task_info import TaskInfo +from zhenxun.utils._build_image import BuildImage + +from .utils import get_plugins + + +async def get_task() -> dict[str, str] | None: + """获取被动技能帮助""" + if task_list := await TaskInfo.all(): + return { + "name": "被动技能", + "description": "控制群组中的被动技能状态", + "usage": "通过 开启/关闭群被动 来控制群被
----------
" + + "
".join([task.name for task in task_list]), + } + return None + + +async def build_html_help(): + """构建帮助图片""" + plugins = await get_plugins() + plugin_list = [ + { + "name": data.plugin.name, + "description": data.metadata.description.replace("\n", "
"), + "usage": data.metadata.usage.replace("\n", "
"), + } + for data in plugins + ] + if task := await get_task(): + plugin_list.append(task) + plugin_list.sort(key=lambda p: len(p["description"]) + len(p["usage"])) + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "help").absolute()), + template_name="main.html", + templates={ + "data": { + "plugin_list": plugin_list, + "nickname": BotConfig.self_nickname, + "help_name": "群管理员", + } + }, + pages={ + "viewport": {"width": 1024, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + result = await BuildImage.open(pic).resize(0.5) + await result.save(ADMIN_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/admin/admin_help/normal_help.py b/zhenxun/builtin_plugins/admin/admin_help/normal_help.py new file mode 100644 index 00000000..a677ba1b --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/normal_help.py @@ -0,0 +1,127 @@ +from nonebot.plugin import PluginMetadata +from PIL.ImageFont import FreeTypeFont + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import build_sort_image, group_image, text2image + +from .config import ADMIN_HELP_IMAGE +from .utils import get_plugins + + +async def build_usage_des_image( + metadata: PluginMetadata, +) -> tuple[BuildImage | None, BuildImage | None]: + """构建用法和描述图片 + + 参数: + metadata: PluginMetadata + + 返回: + tuple[BuildImage | None, BuildImage | None]: 用法和描述图片 + """ + usage = None + description = None + if metadata.usage: + usage = await text2image( + metadata.usage, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + if metadata.description: + description = await text2image( + metadata.description, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + return usage, description + + +async def build_image( + plugin: PluginInfo, metadata: PluginMetadata, font: FreeTypeFont +) -> BuildImage: + """构建帮助图片 + + 参数: + plugin: PluginInfo + metadata: PluginMetadata + font: FreeTypeFont + + 返回: + BuildImage: 帮助图片 + + """ + usage, description = await build_usage_des_image(metadata) + width = 0 + height = 100 + if usage: + width = usage.width + height += usage.height + if description and description.width > width: + width = description.width + height += description.height + font_width, _ = BuildImage.get_text_size(f"{plugin.name}[{plugin.level}]", font) + if font_width > width: + width = font_width + A = BuildImage(width + 30, height + 120, "#EAEDF2") + await A.text((15, 10), f"{plugin.name}[{plugin.level}]") + await A.text((15, 70), "简介:") + if not description: + description = BuildImage(A.width - 30, 30, (255, 255, 255)) + await description.circle_corner(10) + await A.paste(description, (15, 100)) + if not usage: + usage = BuildImage(A.width - 30, 30, (255, 255, 255)) + await usage.circle_corner(10) + await A.text((15, description.height + 115), "用法:") + await A.paste(usage, (15, description.height + 145)) + await A.circle_corner(10) + return A + + +async def build_help(): + """构造管理员帮助图片 + + 返回: + BuildImage: 管理员帮助图片 + """ + font = BuildImage.load_font("HYWenHei-85W.ttf", 20) + image_list = [] + for data in await get_plugins(): + plugin = data.plugin + metadata = data.metadata + try: + A = await build_image(plugin, metadata, font) + image_list.append(A) + except Exception as e: + logger.warning( + f"获取群管理员插件 {plugin.module}: {plugin.name} 设置失败...", + "管理员帮助", + e=e, + ) + if task_list := await TaskInfo.all(): + task_str = "\n".join([task.name for task in task_list]) + task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str + task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) + await task_image.circle_corner(10) + A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") + await A.text((25, 10), "被动技能") + await A.paste(task_image, (25, 50)) + await A.circle_corner(10) + image_list.append(A) + image_group, _ = group_image(image_list) + A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) + text = await BuildImage.build_text_image( + "群管理员帮助", + size=40, + ) + tip = await BuildImage.build_text_image( + "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" + ) + await A.paste(text, (50, 30)) + await A.paste(tip, (50, 90)) + await A.save(ADMIN_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/admin/admin_help/utils.py b/zhenxun/builtin_plugins/admin/admin_help/utils.py new file mode 100644 index 00000000..2385238e --- /dev/null +++ b/zhenxun/builtin_plugins/admin/admin_help/utils.py @@ -0,0 +1,22 @@ +import nonebot + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError + +from .config import PluginData + + +async def get_plugins() -> list[PluginData]: + """获取插件数据""" + plugin_list = await PluginInfo.filter( + plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN] + ).all() + data_list = [] + for plugin in plugin_list: + if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): + if _plugin.metadata: + data_list.append(PluginData(plugin=plugin, metadata=_plugin.metadata)) + if not data_list: + raise EmptyError() + return data_list diff --git a/zhenxun/builtin_plugins/admin/admin_watch.py b/zhenxun/builtin_plugins/admin/admin_watch.py index 7fe7fdb5..4f33a334 100644 --- a/zhenxun/builtin_plugins/admin/admin_watch.py +++ b/zhenxun/builtin_plugins/admin/admin_watch.py @@ -7,10 +7,12 @@ from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.models.level_user import LevelUser from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType +from zhenxun.utils.rules import notice_rule __plugin_meta__ = PluginMetadata( name="群管理员变动监测", - description="检测群管理员变动, 添加与删除管理员默认权限, 当配置项 ADMIN_DEFAULT_AUTH 为空时, 不会添加管理员权限", + description="""检测群管理员变动, 添加与删除管理员默认权限, + 当配置项 ADMIN_DEFAULT_AUTH 为空时, 不会添加管理员权限""", usage="", extra=PluginExtraData( author="HibiKier", @@ -25,11 +27,11 @@ __plugin_meta__ = PluginMetadata( default_value=5, ) ], - ).dict(), + ).to_dict(), ) -admin_notice = on_notice(priority=5) +admin_notice = on_notice(priority=5, rule=notice_rule(GroupAdminNoticeEvent)) base_config = Config.get("admin_bot_manage") @@ -52,7 +54,8 @@ async def _(event: GroupAdminNoticeEvent): ) else: logger.warning( - f"配置项 MODULE: [admin_bot_manage] | KEY: [ADMIN_DEFAULT_AUTH] 为空" + "配置项 MODULE: [admin_bot_manage] |" + " KEY: [ADMIN_DEFAULT_AUTH] 为空" ) elif event.sub_type == "unset": await LevelUser.delete_level(str(event.user_id), str(event.group_id)) diff --git a/zhenxun/builtin_plugins/admin/ban/__init__.py b/zhenxun/builtin_plugins/admin/ban/__init__.py index fcca3220..91bbf2ba 100644 --- a/zhenxun/builtin_plugins/admin/ban/__init__.py +++ b/zhenxun/builtin_plugins/admin/ban/__init__.py @@ -13,7 +13,7 @@ from nonebot_plugin_alconna import ( ) from nonebot_plugin_session import EventSession -from zhenxun.configs.config import Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType @@ -30,7 +30,7 @@ __plugin_meta__ = PluginMetadata( usage=""" 普通管理员 格式: - ban [At用户] -t [时长(分钟)] + ban [At用户] ?[-t [时长(分钟)]] 示例: ban @用户 : 永久拉黑用户 @@ -44,7 +44,8 @@ __plugin_meta__ = PluginMetadata( superuser_help=""" 超级管理员额外命令 格式: - ban [At用户/用户Id] [时长] + ban [At用户/用户Id] ?[-t [时长]] + unban --id [idx] : 通过id来进行unban操作 ban列表: 获取所有Ban数据 群组ban列表: 获取群组Ban数据 @@ -77,7 +78,7 @@ __plugin_meta__ = PluginMetadata( type=int, ) ], - ).dict(), + ).to_dict(), ) @@ -98,6 +99,7 @@ _unban_matcher = on_alconna( "unban", Args["user?", [str, At]], Option("-g|--group", Args["group_id", str]), + Option("--id", Args["idx", int]), ), rule=admin_check("ban", "BAN_LEVEL"), priority=5, @@ -160,7 +162,9 @@ async def _( duration: Match[int], group_id: Match[str], ): - user_id = None + user_id = "" + if not session.id1: + await MessageUtils.build_message("用户id为空...").finish(reply_to=True) if user.available: if isinstance(user.result, At): user_id = user.result.target @@ -169,14 +173,33 @@ async def _( await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) user_id = user.result _duration = duration.result * 60 if duration.available else -1 + _duration_text = f"{duration.result} 分钟" if duration.available else " 到世界湮灭" if (gid := session.id3 or session.id2) and not group_id.available: - if group_id.available: - gid = group_id.result + if not user_id or ( + user_id == bot.self_id and session.id1 not in bot.config.superusers + ): + _duration = 0.5 + await MessageUtils.build_message("倒反天罡,小小管理速速退下!").send() + await BanManage.ban(session.id1, gid, 30, session, True) + _duration_text = "半 分钟" + logger.info( + f"尝试ban {BotConfig.self_nickname} 反被拿下", + arparma.header_result, + session=session, + ) + await MessageUtils.build_message( + [ + "对 ", + At(flag="user", target=session.id1), + " 狠狠惩戒了一番,一脚踢进了小黑屋!" + f" 在里面乖乖呆 {_duration_text}吧!", + ] + ).finish(reply_to=True) await BanManage.ban( user_id, gid, _duration, session, session.id1 in bot.config.superusers ) logger.info( - f"管理员Ban", + "管理员Ban", arparma.header_result, session=session, target=f"{gid}:{user_id}", @@ -184,20 +207,25 @@ async def _( await MessageUtils.build_message( [ "对 ", - At(flag="user", target=user_id) if isinstance(user.result, At) else user_id, # type: ignore - f" 狠狠惩戒了一番,一脚踢进了小黑屋!", + ( + At(flag="user", target=user_id) + if isinstance(user.result, At) + else user_id + ), # type: ignore + " 狠狠惩戒了一番,一脚踢进了小黑屋!", + f" 在里面乖乖呆 {_duration_text} 吧!", ] ).finish(reply_to=True) elif session.id1 in bot.config.superusers: _group_id = group_id.result if group_id.available else None await BanManage.ban(user_id, _group_id, _duration, session, True) logger.info( - f"超级用户Ban", + "超级用户Ban", arparma.header_result, session=session, target=f"{_group_id}:{user_id}", ) - at_msg = user_id if user_id else f"群组:{_group_id}" + at_msg = user_id or f"群组:{_group_id}" await MessageUtils.build_message( f"对 {at_msg} 狠狠惩戒了一番,一脚踢进了小黑屋!" ).finish(reply_to=True) @@ -210,8 +238,10 @@ async def _( arparma: Arparma, user: Match[str | At], group_id: Match[str], + idx: Match[int], ): - user_id = None + user_id = "" + _idx = idx.result if idx.available else None if user.available: if isinstance(user.result, At): user_id = user.result.target @@ -220,44 +250,44 @@ async def _( await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) user_id = user.result if gid := session.id3 or session.id2: - u_d = user_id if group_id.available: - u_d = gid gid = group_id.result - if await BanManage.unban( - user_id, gid, session, session.id1 in bot.config.superusers - ): - logger.info( - f"管理员UnBan", - arparma.header_result, - session=session, - target=f"{gid}:{user_id}", - ) - await MessageUtils.build_message( - [ - "将 ", - At(flag="user", target=user_id) if isinstance(user.result, At) else u_d, # type: ignore - f" 从黑屋中拉了出来并急救了一下!", - ] - ).finish(reply_to=True) - else: - await MessageUtils.build_message(f"该用户不在黑名单中捏...").finish( - reply_to=True - ) + is_unban, result = await BanManage.unban( + user_id, gid, session, _idx, session.id1 in bot.config.superusers + ) + if not is_unban: + await MessageUtils.build_message(result).finish(reply_to=True) + logger.info( + "管理员UnBan", + arparma.header_result, + session=session, + target=f"{gid}:{result}", + ) + await MessageUtils.build_message( + [ + "将 ", + ( + At(flag="user", target=user_id) + if isinstance(user.result, At) + else result + ), # type: ignore + " 从黑屋中拉了出来并急救了一下!", + ] + ).finish(reply_to=True) elif session.id1 in bot.config.superusers: _group_id = group_id.result if group_id.available else None - if await BanManage.unban(user_id, _group_id, session, True): - logger.info( - f"超级用户UnBan", - arparma.header_result, - session=session, - target=f"{_group_id}:{user_id}", - ) - at_msg = user_id if user_id else f"群组:{_group_id}" - await MessageUtils.build_message( - f"对 {at_msg} 从黑屋中拉了出来并急救了一下!" - ).finish(reply_to=True) - else: - await MessageUtils.build_message(f"该用户不在黑名单中捏...").finish( - reply_to=True - ) + is_unban, result = await BanManage.unban( + user_id, _group_id, session, _idx, True + ) + if not is_unban: + await MessageUtils.build_message(result).finish(reply_to=True) + logger.info( + "超级用户UnBan", + arparma.header_result, + session=session, + target=f"{_group_id}:{user_id}", + ) + at_msg = user_id or f"群组:{result}" + await MessageUtils.build_message( + f"对 {at_msg} 从黑屋中拉了出来并急救了一下!" + ).finish(reply_to=True) diff --git a/zhenxun/builtin_plugins/admin/ban/_data_source.py b/zhenxun/builtin_plugins/admin/ban/_data_source.py index 3d8f704d..f38d2440 100644 --- a/zhenxun/builtin_plugins/admin/ban/_data_source.py +++ b/zhenxun/builtin_plugins/admin/ban/_data_source.py @@ -9,7 +9,6 @@ from zhenxun.utils.image_utils import BuildImage, ImageTemplate class BanManage: - @classmethod async def build_ban_image( cls, @@ -33,11 +32,10 @@ class BanManage: query = query.filter(user_id=user_id) elif group_id: query = query.filter(group_id=group_id) - else: - if filter_type == "user": - query = query.filter(group_id__isnull=True) - elif filter_type == "group": - query = query.filter(user_id__isnull=True) + elif filter_type == "user": + query = query.filter(group_id__isnull=True) + elif filter_type == "group": + query = query.filter(user_id__isnull=True) data_list = await query.all() if not data_list: return None @@ -86,26 +84,36 @@ class BanManage: user_id: str | None, group_id: str | None, session: EventSession, + idx: int | None = None, is_superuser: bool = False, - ) -> bool: + ) -> tuple[bool, str]: """unban目标用户 参数: user_id: 用户id group_id: 群组id session: Session + idx: 指定id is_superuser: 是否为超级用户操作 返回: - bool: 是否unban成功 + tuple[bool, str]: 是否unban成功, 群组/用户id或提示 """ user_level = 9999 if not is_superuser and user_id and session.id1: user_level = await LevelUser.get_user_level(session.id1, group_id) - if await BanConsole.check_ban_level(user_id, group_id, user_level): + if idx: + ban_data = await BanConsole.get_or_none(id=idx) + if not ban_data: + return False, "该用户/群组不在黑名单中捏..." + if ban_data.ban_level > user_level: + return False, "unBan权限等级不足捏..." + await ban_data.delete() + return True, str(ban_data.user_id or ban_data.group_id) + elif await BanConsole.check_ban_level(user_id, group_id, user_level): await BanConsole.unban(user_id, group_id) - return True - return False + return True, str(group_id) + return False, "该用户/群组不在黑名单中不足捏..." @classmethod async def ban( diff --git a/zhenxun/builtin_plugins/admin/group_member_update/__init__.py b/zhenxun/builtin_plugins/admin/group_member_update/__init__.py index 437b3aa9..7d9ab573 100644 --- a/zhenxun/builtin_plugins/admin/group_member_update/__init__.py +++ b/zhenxun/builtin_plugins/admin/group_member_update/__init__.py @@ -7,13 +7,13 @@ from nonebot_plugin_alconna import Alconna, Arparma, on_alconna from nonebot_plugin_apscheduler import scheduler from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.rules import admin_check, ensure_group +from zhenxun.utils.rules import admin_check, ensure_group, notice_rule from ._data_source import MemberUpdateManage @@ -30,7 +30,7 @@ __plugin_meta__ = PluginMetadata( version="0.1", plugin_type=PluginType.SUPER_AND_ADMIN, admin_level=1, - ).dict(), + ).to_dict(), ) @@ -42,27 +42,24 @@ _matcher = on_alconna( ) -_notice = on_notice(priority=1, block=False) +_notice = on_notice(priority=1, block=False, rule=notice_rule(GroupIncreaseNoticeEvent)) @_matcher.handle() async def _(bot: Bot, session: EventSession, arparma: Arparma): if gid := session.id3 or session.id2: logger.info("更新群组成员信息", arparma.header_result, session=session) - await MemberUpdateManage.update(bot, gid) - await MessageUtils.build_message("已经成功更新了群组成员信息!").finish( - reply_to=True - ) + result = await MemberUpdateManage.update_group_member(bot, gid) + await MessageUtils.build_message(result).finish(reply_to=True) await MessageUtils.build_message("群组id为空...").send() @_notice.handle() async def _(bot: Bot, event: GroupIncreaseNoticeEvent): - # TODO: 其他适配器的加群自动更新群组成员信息 if str(event.user_id) == bot.self_id: - await MemberUpdateManage.update(bot, str(event.group_id)) + await MemberUpdateManage.update_group_member(bot, str(event.group_id)) logger.info( - f"{NICKNAME}加入群聊更新群组信息", + f"{BotConfig.self_nickname}加入群聊更新群组信息", "更新群组成员列表", session=event.user_id, group_id=event.group_id, @@ -81,7 +78,9 @@ async def _(): if group_list: for group in group_list: try: - await MemberUpdateManage.update(bot, group.group_id) + await MemberUpdateManage.update_group_member( + bot, group.group_id + ) logger.debug("自动更新群组成员信息成功...") except Exception as e: logger.error( diff --git a/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py b/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py index a24741cd..5239857f 100644 --- a/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py +++ b/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py @@ -1,203 +1,146 @@ -import time -from datetime import datetime, timedelta, timezone +from datetime import datetime +import nonebot from nonebot.adapters import Bot - -# from nonebot.adapters.discord import Bot as DiscordBot -# from nonebot.adapters.dodo import Bot as DodoBot -from nonebot.adapters.dodo.models import MemberInfo - -# from nonebot.adapters.kaiheila import Bot as KaiheilaBot -from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v12 import Bot as v12Bot +from nonebot_plugin_uninfo import Member, SceneType, get_interface from zhenxun.configs.config import Config from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.level_user import LevelUser from zhenxun.services.log import logger +from zhenxun.utils.platform import PlatformUtils class MemberUpdateManage: - @classmethod - async def update(cls, bot: Bot, group_id: str): - if isinstance(bot, v11Bot): - await cls.v11(bot, group_id) - elif isinstance(bot, v12Bot): - await cls.v12(bot, group_id) - # elif isinstance(bot, KaiheilaBot): - # await cls.kaiheila(bot, group_id) - # elif isinstance(bot, DodoBot): - # await cls.dodo(bot, group_id) - # elif isinstance(bot, DiscordBot): - # await cls.discord(bot, group_id) + async def __handle_user( + cls, + member: Member, + db_user: list[GroupInfoUser], + group_id: str, + data_list: tuple[list, list, list], + platform: str | None, + ): + """单个成员操作 - # @classmethod - # async def discord(cls, bot: DiscordBot, group_id: str): - # # TODO: discord更新群组成员信息 - # pass - - # @classmethod - # async def dodo(cls, bot: DodoBot, group_id: str): - # page_size = 100 - # result_size = 100 - # max_id = 0 - # exist_member_list = [] - # group_member_list: list[MemberInfo] = [] - # while result_size == page_size: - # group_member_data = await bot.get_member_list( - # island_source_id=group_id, page_size=page_size - # ) - # result_size = len(group_member_data.list) - # group_member_list += group_member_data.list - # max_id = group_member_data.max_id - # if group_member_list: - # for user in group_member_list: - # exist_member_list.append(user.dodo_source_id) - # await GroupInfoUser.update_or_create( - # user_id=user.dodo_source_id, - # group_id=group_id, - # defaults={ - # "user_name": user.nick_name or user.personal_nick_name, - # "user_join_time": user.join_time, - # "platform": "dodo", - # }, - # ) - # if delete_member_list := list( - # set(exist_member_list).difference( - # set(await GroupInfoUser.get_group_member_id_list(group_id)) - # ) - # ): - # await GroupInfoUser.filter( - # user_id__in=delete_member_list, group_id=group_id - # ).delete() - # logger.info( - # f"删除已退群用户", - # "更新群组成员信息", - # group_id=group_id, - # platform="dodo", - # ) - - # @classmethod - # async def kaiheila(cls, bot: KaiheilaBot, group_id: str): - # # TODO: kaiheila 更新群组成员信息 - # pass - - @classmethod - async def v11(cls, bot: v11Bot, group_id: str): - exist_member_list = [] + 参数: + member: Member + db_user: db成员数据 + group_id: 群组id + data_list: 数据列表 + platform: 平台 + """ + driver = nonebot.get_driver() default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH") - group_member_list = await bot.get_group_member_list(group_id=int(group_id)) - db_user = await GroupInfoUser.filter(group_id=group_id).all() + nickname = member.nick or member.user.name or "" + role = member.role db_user_uid = [u.user_id for u in db_user] uid2name = {u.user_id: u.user_name for u in db_user} - create_list = [] - update_list = [] - delete_list = [] - for user_info in group_member_list: - user_id = str(user_info["user_id"]) - nickname = user_info["card"] or user_info["nickname"] - role = user_info["role"] - if default_auth: - if role in ["owner", "admin"] and not await LevelUser.is_group_flag( - user_id, group_id - ): - await LevelUser.set_level(user_id, group_id, default_auth) - if user_id in bot.config.superusers: - await LevelUser.set_level(user_id, group_id, 9) - join_time = datetime.strptime( - time.strftime( - "%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"]) - ), - "%Y-%m-%d %H:%M:%S", - ).replace(tzinfo=timezone(timedelta(hours=8))) - if cnt := db_user_uid.count(user_id): - users = [u for u in db_user if u.user_id == user_id] + if member.id in driver.config.superusers: + await LevelUser.set_level(member.id, group_id, 9) + elif role and default_auth: + if role.id != "MEMBER" and not await LevelUser.is_group_flag( + member.id, group_id + ): + if role.id == "OWNER": + await LevelUser.set_level(member.id, group_id, default_auth + 1) + elif role.id == "ADMINISTRATOR": + await LevelUser.set_level(member.id, group_id, default_auth) + if cnt := db_user_uid.count(member.id): + users = [u for u in db_user if u.user_id == member.id] + if cnt > 1: + for u in users[1:]: + data_list[2].append(u.id) + if nickname != uid2name.get(member.id): user = users[0] - if cnt > 1: - for u in users[1:]: - delete_list.append(u.id) - if nickname != uid2name.get(user_id): - user.user_name = nickname - update_list.append(user) - else: - create_list.append( - GroupInfoUser( - user_id=user_id, - group_id=group_id, - user_name=nickname, - user_join_time=join_time, - platform="qq", - ) + user.user_name = nickname + data_list[1].append(user) + else: + data_list[0].append( + GroupInfoUser( + user_id=member.id, + group_id=group_id, + user_name=nickname, + user_join_time=member.joined_at or datetime.now(), + platform=platform, ) - exist_member_list.append(user_id) - if create_list: - await GroupInfoUser.bulk_create(create_list, 30) - logger.debug( - f"创建用户数据 {len(create_list)} 条", - "更新群组成员信息", - target=group_id, - ) - if update_list: - await GroupInfoUser.bulk_update(update_list, ["user_name"], 30) - logger.debug( - f"更新户数据 {len(update_list)} 条", "更新群组成员信息", target=group_id - ) - if delete_list: - await GroupInfoUser.filter(id__in=delete_list).delete() - logger.debug(f"删除重复数据 Ids: {delete_list}", "更新群组成员信息") - if delete_member_list := list( - set(exist_member_list).difference(set(db_user_uid)) - ): - await GroupInfoUser.filter( - user_id__in=delete_member_list, group_id=group_id - ).delete() - logger.info( - f"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq" ) @classmethod - async def v12(cls, bot: v12Bot, group_id: str): - # TODO: v12更新群组成员信息 - pass - # exist_member_list = [] - # default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH") - # group_member_list: list[GetGroupMemberInfoResp] = await bot.get_group_member_list( - # group_id=group_id - # ) - # for user_info in group_member_list: - # user_id = user_info.user_id - # nickname = user_info.user_displayname or user_info.user_name - # role = user_info["role"] - # if default_auth: - # if role in ["owner", "admin"] and not LevelUser.is_group_flag( - # str(user_id), group_id - # ): - # await LevelUser.set_level(user_id, group_id, default_auth) - # if str(user_id) in bot.config.superusers: - # await LevelUser.set_level(str(user_id), group_id, 9) - # join_time = datetime.strptime( - # time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])), - # "%Y-%m-%d %H:%M:%S", - # ) - # await GroupInfoUser.update_or_create( - # user_id=str(user_id), - # group_id=group_id, - # defaults={ - # "user_name": nickname, - # "user_join_time": join_time.replace( - # tzinfo=timezone(timedelta(hours=8)) - # ), - # }, - # ) - # exist_member_list.append(str(user_id)) - # logger.debug("更新成功", "更新群组成员信息", session=user_id, group_id=group_id) - # if delete_member_list := list( - # set(exist_member_list).difference( - # set(await GroupInfoUser.get_group_member_id_list(group_id)) - # ) - # ): - # await GroupInfoUser.filter( - # user_id__in=delete_member_list, group_id=group_id - # ).delete() - # logger.info(f"删除已退群用户", "更新群组成员信息", group_id=group_id) + async def update_group_member(cls, bot: Bot, group_id: str) -> str: + """更新群组成员信息 + + 参数: + bot: Bot + group_id: 群组id + + 返回: + str: 返回消息 + """ + if not group_id: + logger.warning(f"bot: {bot.self_id},group_id为空,无法更新群成员信息...") + return "群组id为空..." + if interface := get_interface(bot): + scenes = await interface.get_scenes() + platform = PlatformUtils.get_platform(bot) + group_list = [s for s in scenes if s.is_group and s.id == group_id] + if not group_list: + logger.warning( + f"bot: {bot.self_id},group_id: {group_id},群组不存在," + "无法更新群成员信息..." + ) + return "更新群组失败,群组不存在..." + members = await interface.get_members(SceneType.GROUP, group_list[0].id) + db_user = await GroupInfoUser.filter(group_id=group_id).all() + db_user_uid = [u.user_id for u in db_user] + data_list = ([], [], []) + exist_member_list = [] + for member in members: + logger.debug(f"即将更新群组成员: {member}", "更新群组成员信息") + await cls.__handle_user(member, db_user, group_id, data_list, platform) + exist_member_list.append(member.id) + if data_list[0]: + try: + await GroupInfoUser.bulk_create(data_list[0], 30) + logger.debug( + f"创建用户数据 {len(data_list[0])} 条", + "更新群组成员信息", + target=group_id, + ) + except Exception as e: + logger.error( + f"批量创建用户数据失败: {e},开始进行逐个存储", + "更新群组成员信息", + ) + for u in data_list[0]: + try: + await u.save() + except Exception as e: + logger.error( + f"创建用户 {u.user_name}({u.user_id}) 数据失败: {e}", + "更新群组成员信息", + ) + if data_list[1]: + await GroupInfoUser.bulk_update(data_list[1], ["user_name"], 30) + logger.debug( + f"更新户数据 {len(data_list[1])} 条", + "更新群组成员信息", + target=group_id, + ) + if data_list[2]: + await GroupInfoUser.filter(id__in=data_list[2]).delete() + logger.debug(f"删除重复数据 Ids: {data_list[2]}", "更新群组成员信息") + + if delete_member_list := [ + uid for uid in db_user_uid if uid not in exist_member_list + ]: + await GroupInfoUser.filter( + user_id__in=delete_member_list, group_id=group_id + ).delete() + logger.info( + f"删除已退群用户 {len(delete_member_list)} 条", + "更新群组成员信息", + group_id=group_id, + platform="qq", + ) + return "群组成员信息更新完成!" diff --git a/zhenxun/builtin_plugins/admin/group_update.py b/zhenxun/builtin_plugins/admin/group_update.py new file mode 100644 index 00000000..ab2170e0 --- /dev/null +++ b/zhenxun/builtin_plugins/admin/group_update.py @@ -0,0 +1,45 @@ +from nonebot.adapters import Bot +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils +from zhenxun.utils.rules import admin_check, ensure_group + +__plugin_meta__ = PluginMetadata( + name="更新群组列表", + description="更新群组列表", + usage=""" + 更新群组的基本信息 + 指令: + 更新群组信息 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPER_AND_ADMIN, + admin_level=1, + ).to_dict(), +) + + +_matcher = on_alconna( + Alconna("更新群组信息"), + rule=admin_check(1) & ensure_group, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _(bot: Bot, session: EventSession, arparma: Arparma): + logger.info("更新群组信息", arparma.header_result, session=session) + try: + await PlatformUtils.update_group(bot) + await MessageUtils.build_message("已经成功更新了群组信息!").send(reply_to=True) + except Exception: + await MessageUtils.build_message("更新群组信息失败!").finish(reply_to=True) diff --git a/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py b/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py index 76b10259..e183de4a 100644 --- a/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py +++ b/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py @@ -9,7 +9,7 @@ from zhenxun.services.log import logger from zhenxun.utils.enum import BlockType, PluginType from zhenxun.utils.message import MessageUtils -from ._data_source import PluginManage, build_plugin, build_task +from ._data_source import PluginManage, build_plugin, build_task, delete_help_image from .command import _group_status_matcher, _status_matcher base_config = Config.get("plugin_switch") @@ -40,36 +40,35 @@ __plugin_meta__ = PluginMetadata( version="0.1", plugin_type=PluginType.SUPER_AND_ADMIN, superuser_help=""" - 超级管理员额外命令 - 格式: - 插件列表 - 开启/关闭[功能名称] ?[-t ["private", "p", "group", "g"](关闭类型)] ?[-g 群组Id] + 格式: + 插件列表 + 开启/关闭[功能名称] ?[-t ["private", "p", "group", "g"](关闭类型)] ?[-g 群组Id] - 开启/关闭插件df[功能名称]: 开启/关闭指定插件进群默认状态 - 开启/关闭所有插件df: 开启/关闭所有插件进群默认状态 - 开启/关闭所有插件: - 私聊中: 开启/关闭所有插件全局状态 - 群组中: 开启/关闭当前群组所有插件状态 + 开启/关闭插件df[功能名称]: 开启/关闭指定插件进群默认状态 + 开启/关闭所有插件df: 开启/关闭所有插件进群默认状态 + 开启/关闭所有插件: + 私聊中: 开启/关闭所有插件全局状态 + 群组中: 开启/关闭当前群组所有插件状态 - 开启/关闭群被动[name] ?[-g [group_id]] - 私聊中: 开启/关闭全局指定的被动状态 - 群组中: 开启/关闭当前群组指定的被动状态 - 示例: - 关闭群被动早晚安 - 关闭群被动早晚安 -g 12355555 + 开启/关闭群被动[name] ?[-g [group_id]] + 私聊中: 开启/关闭全局指定的被动状态 + 群组中: 开启/关闭当前群组指定的被动状态 + 示例: + 关闭群被动早晚安 + 关闭群被动早晚安 -g 12355555 - 开启/关闭所有群被动 -[g ?[group_id]] - 私聊中: 开启/关闭全局或指定群组被动状态 - 示例: - 开启所有群被动: 开启全局所有被动 - 开启所有群被动 -g 12345678: 开启群组12345678所有被动 + 开启/关闭所有群被动 ?[-g [group_id]] + 私聊中: 开启/关闭全局或指定群组被动状态 + 示例: + 开启所有群被动: 开启全局所有被动 + 开启所有群被动 -g 12345678: 开启群组12345678所有被动 - 私聊下: - 示例: - 开启签到 : 全局开启签到 - 关闭签到 : 全局关闭签到 - 关闭签到 p : 全局私聊关闭签到 - 关闭签到 -g 12345678 : 关闭群组12345678的签到功能(普通管理员无法开启) + 私聊下: + 示例: + 开启签到 : 全局开启签到 + 关闭签到 : 全局关闭签到 + 关闭签到 p : 全局私聊关闭签到 + 关闭签到 -g 12345678 : 关闭群组12345678的签到功能(普通管理员无法开启) """, admin_level=base_config.get("CHANGE_GROUP_SWITCH_LEVEL", 2), configs=[ @@ -81,7 +80,7 @@ __plugin_meta__ = PluginMetadata( type=int, ) ], - ).dict(), + ).to_dict(), ) @@ -94,7 +93,7 @@ async def _( if session.id1 in bot.config.superusers: image = await build_plugin() logger.info( - f"查看功能列表", + "查看功能列表", arparma.header_result, session=session, ) @@ -117,43 +116,39 @@ async def _( if not all.result and not plugin_name.available: await MessageUtils.build_message("请输入功能名称").finish(reply_to=True) name = plugin_name.result - gid = session.id3 or session.id2 - if gid: + if gid := session.id3 or session.id2: """修改当前群组的数据""" if task.result: if all.result: result = await PluginManage.unblock_group_all_task(gid) - logger.info(f"开启所有群组被动", arparma.header_result, session=session) + logger.info("开启所有群组被动", arparma.header_result, session=session) else: result = await PluginManage.unblock_group_task(name, gid) logger.info( f"开启群组被动 {name}", arparma.header_result, session=session ) + elif session.id1 in bot.config.superusers and default_status.result: + """单个插件的进群默认修改""" + result = await PluginManage.set_default_status(name, True) + logger.info( + f"超级用户开启 {name} 功能进群默认开关", + arparma.header_result, + session=session, + ) + elif all.result: + """所有插件""" + result = await PluginManage.set_all_plugin_status( + True, default_status.result, gid + ) + logger.info( + "开启群组中全部功能", + arparma.header_result, + session=session, + ) else: - if session.id1 in bot.config.superusers and default_status.result: - """单个插件的进群默认修改""" - result = await PluginManage.set_default_status(name, True) - logger.info( - f"超级用户开启 {name} 功能进群默认开关", - arparma.header_result, - session=session, - ) - else: - if all.result: - """所有插件""" - result = await PluginManage.set_all_plugin_status( - True, default_status.result, gid - ) - logger.info( - f"开启群组中全部功能", - arparma.header_result, - session=session, - ) - else: - result = await PluginManage.unblock_group_plugin(name, gid) - logger.info( - f"开启功能 {name}", arparma.header_result, session=session - ) + result = await PluginManage.unblock_group_plugin(name, gid) + logger.info(f"开启功能 {name}", arparma.header_result, session=session) + delete_help_image(gid) await MessageUtils.build_message(result).finish(reply_to=True) elif session.id1 in bot.config.superusers: """私聊""" @@ -170,7 +165,8 @@ async def _( True, default_status.result, group_id ) logger.info( - f"超级用户开启全部功能全局开关 {f'指定群组: {group_id}' if group_id else ''}", + "超级用户开启全部功能全局开关" + f" {f'指定群组: {group_id}' if group_id else ''}", arparma.header_result, session=session, ) @@ -204,16 +200,16 @@ async def _( arparma.header_result, session=session, ) - await MessageUtils.build_message(result).finish(reply_to=True) else: - result = await PluginManage.superuser_block(name, None, group_id) + result = await PluginManage.superuser_unblock(name, None, group_id) logger.info( f"超级用户开启功能 {name}", arparma.header_result, session=session, target=group_id, ) - await MessageUtils.build_message(result).finish(reply_to=True) + delete_help_image() + await MessageUtils.build_message(result).finish(reply_to=True) @_status_matcher.assign("close") @@ -231,43 +227,35 @@ async def _( if not all.result and not plugin_name.available: await MessageUtils.build_message("请输入功能名称").finish(reply_to=True) name = plugin_name.result - gid = session.id3 or session.id2 - if gid: + if gid := session.id3 or session.id2: """修改当前群组的数据""" if task.result: if all.result: result = await PluginManage.block_group_all_task(gid) - logger.info(f"开启所有群组被动", arparma.header_result, session=session) + logger.info("开启所有群组被动", arparma.header_result, session=session) else: result = await PluginManage.block_group_task(name, gid) logger.info( f"关闭群组被动 {name}", arparma.header_result, session=session ) + elif session.id1 in bot.config.superusers and default_status.result: + """单个插件的进群默认修改""" + result = await PluginManage.set_default_status(name, False) + logger.info( + f"超级用户开启 {name} 功能进群默认开关", + arparma.header_result, + session=session, + ) + elif all.result: + """所有插件""" + result = await PluginManage.set_all_plugin_status( + False, default_status.result, gid + ) + logger.info("关闭群组中全部功能", arparma.header_result, session=session) else: - if session.id1 in bot.config.superusers and default_status.result: - """单个插件的进群默认修改""" - result = await PluginManage.set_default_status(name, False) - logger.info( - f"超级用户开启 {name} 功能进群默认开关", - arparma.header_result, - session=session, - ) - else: - if all.result: - """所有插件""" - result = await PluginManage.set_all_plugin_status( - False, default_status.result, gid - ) - logger.info( - f"关闭群组中全部功能", - arparma.header_result, - session=session, - ) - else: - result = await PluginManage.block_group_plugin(name, gid) - logger.info( - f"关闭功能 {name}", arparma.header_result, session=session - ) + result = await PluginManage.block_group_plugin(name, gid) + logger.info(f"关闭功能 {name}", arparma.header_result, session=session) + delete_help_image(gid) await MessageUtils.build_message(result).finish(reply_to=True) elif session.id1 in bot.config.superusers: group_id = group.result if group.available else None @@ -283,7 +271,8 @@ async def _( False, default_status.result, group_id ) logger.info( - f"超级用户关闭全部功能全局开关 {f'指定群组: {group_id}' if group_id else ''}", + "超级用户关闭全部功能全局开关" + f" {f'指定群组: {group_id}' if group_id else ''}", arparma.header_result, session=session, ) @@ -317,13 +306,13 @@ async def _( arparma.header_result, session=session, ) - await MessageUtils.build_message(result).finish(reply_to=True) else: _type = BlockType.ALL - if block_type.available: - if block_type.result in ["p", "private"]: + if block_type.result in ["p", "private"]: + if block_type.available: _type = BlockType.PRIVATE - elif block_type.result in ["g", "group"]: + elif block_type.result in ["g", "group"]: + if block_type.available: _type = BlockType.GROUP result = await PluginManage.superuser_block(name, _type, group_id) logger.info( @@ -332,7 +321,8 @@ async def _( session=session, target=group_id, ) - await MessageUtils.build_message(result).finish(reply_to=True) + delete_help_image() + await MessageUtils.build_message(result).finish(reply_to=True) @_group_status_matcher.handle() @@ -362,11 +352,7 @@ async def _( ): image = await build_task(session.id3 or session.id2) if image: - logger.info( - f"查看群被动列表", - arparma.header_result, - session=session, - ) + logger.info("查看群被动列表", arparma.header_result, session=session) await MessageUtils.build_message(image).finish(reply_to=True) else: await MessageUtils.build_message("获取群被动任务失败...").finish(reply_to=True) diff --git a/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py b/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py index b10a929e..7af8dc29 100644 --- a/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py +++ b/zhenxun/builtin_plugins/admin/plugin_switch/_data_source.py @@ -1,3 +1,4 @@ +from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH from zhenxun.models.group_console import GroupConsole from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.task_info import TaskInfo @@ -5,6 +6,23 @@ from zhenxun.utils.enum import BlockType, PluginType from zhenxun.utils.exception import GroupInfoNotFound from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle +HELP_FILE = IMAGE_PATH / "SIMPLE_HELP.png" + +GROUP_HELP_PATH = DATA_PATH / "group_help" + + +def delete_help_image(gid: str | None = None): + """删除帮助图片""" + if gid: + file = GROUP_HELP_PATH / f"{gid}.png" + if file.exists(): + file.unlink() + else: + if HELP_FILE.exists(): + HELP_FILE.unlink() + for file in GROUP_HELP_PATH.iterdir(): + file.unlink() + def plugin_row_style(column: str, text: str) -> RowStyle: """被动技能文本风格 @@ -17,16 +35,12 @@ def plugin_row_style(column: str, text: str) -> RowStyle: RowStyle: RowStyle """ style = RowStyle() - if column == "全局状态": - if text == "开启": - style.font_color = "#67C23A" - else: - style.font_color = "#F56C6C" - if column == "加载状态": - if text == "SUCCESS": - style.font_color = "#67C23A" - else: - style.font_color = "#F56C6C" + if (column == "全局状态" and text == "开启") or ( + column != "全局状态" and column == "加载状态" and text == "SUCCESS" + ): + style.font_color = "#67C23A" + elif column in {"全局状态", "加载状态"}: + style.font_color = "#F56C6C" return style @@ -44,22 +58,21 @@ async def build_plugin() -> BuildImage: "金币花费", ] plugin_list = await PluginInfo.filter(plugin_type__not=PluginType.HIDDEN).all() - column_data = [] - for plugin in plugin_list: - column_data.append( - [ - plugin.id, - plugin.module, - plugin.name, - "开启" if plugin.status else "关闭", - plugin.block_type, - "SUCCESS" if plugin.load_status else "ERROR", - plugin.menu_type, - plugin.author, - plugin.version, - plugin.cost_gold, - ] - ) + column_data = [ + [ + plugin.id, + plugin.module, + plugin.name, + "开启" if plugin.status else "关闭", + plugin.block_type, + "SUCCESS" if plugin.load_status else "ERROR", + plugin.menu_type, + plugin.author, + plugin.version, + plugin.cost_gold, + ] + for plugin in plugin_list + ] return await ImageTemplate.table_page( "Plugin", "插件状态", @@ -80,11 +93,8 @@ def task_row_style(column: str, text: str) -> RowStyle: RowStyle: RowStyle """ style = RowStyle() - if column in ["群组状态", "全局状态"]: - if text == "开启": - style.font_color = "#67C23A" - else: - style.font_color = "#F56C6C" + if column in {"群组状态", "全局状态"}: + style.font_color = "#67C23A" if text == "开启" else "#F56C6C" return style @@ -119,7 +129,7 @@ async def build_task(group_id: str | None) -> BuildImage: task.id, task.module, task.name, - "开启" if task.module not in group.block_task else "关闭", + "开启" if f"<{task.module}," not in group.block_task else "关闭", "开启" if task.status else "关闭", task.run_time or "-", ] @@ -144,7 +154,6 @@ async def build_task(group_id: str | None) -> BuildImage: class PluginManage: - @classmethod async def set_default_status(cls, plugin_name: str, status: bool) -> str: """设置插件进群默认状态 @@ -159,12 +168,15 @@ class PluginManage: if plugin_name.isdigit(): plugin = await PluginInfo.get_or_none(id=int(plugin_name)) else: - plugin = await PluginInfo.get_or_none(name=plugin_name) + plugin = await PluginInfo.get_or_none( + name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT + ) if plugin: plugin.default_status = status await plugin.save(update_fields=["default_status"]) - return f'成功将 {plugin.name} 进群默认状态修改为: {"开启" if status else "关闭"}' - return f"没有找到这个功能喔..." + status_text = "开启" if status else "关闭" + return f"成功将 {plugin.name} 进群默认状态修改为: {status_text}" + return "没有找到这个功能喔..." @classmethod async def set_all_plugin_status( @@ -195,18 +207,16 @@ class PluginManage: if status: for module in module_list: group.block_plugin = group.block_plugin.replace( - f"{module},", "" + f"<{module},", "" ) else: - module_list = await PluginInfo.filter( - plugin_type=PluginType.NORMAL - ).values_list("module", flat=True) + module_list = [f"<{module}" for module in module_list] group.block_plugin = ",".join(module_list) + "," # type: ignore await group.save(update_fields=["block_plugin"]) return f'成功将此群组所有功能状态修改为: {"开启" if status else "关闭"}' return "获取群组失败..." await PluginInfo.filter(plugin_type=PluginType.NORMAL).update( - status=status, block_type=BlockType.ALL if not status else None + status=status, block_type=None if status else BlockType.ALL ) return f'成功将所有功能全局状态修改为: {"开启" if status else "关闭"}' @@ -397,7 +407,7 @@ class PluginManage: 参数: task_name: 被动技能名称 group_id: 群组Id - status: 状态 + status: 状态,为True时是关闭 is_all: 所有群被动 返回: @@ -410,6 +420,7 @@ class PluginManage: group, _ = await GroupConsole.get_or_create( group_id=group_id, channel_id__isnull=True ) + modules = [f"<{module}" for module in modules] if status: group.block_task = ",".join(modules) + "," # type: ignore else: @@ -417,19 +428,14 @@ class PluginManage: group.block_task = group.block_task.replace(f"{module},", "") await group.save(update_fields=["block_task"]) return f"已成功{status_str}全部被动技能!" - else: - if task := await TaskInfo.get_or_none(name=task_name): - group, _ = await GroupConsole.get_or_create( - group_id=group_id, channel_id__isnull=True - ) - if status: - group.block_task += f"{task.module}," - else: - if f"super:{task.module}," in group.block_task: - return f"{status_str} {task_name} 被动技能失败,当前群组该被动已被管理员禁用" - group.block_task = group.block_task.replace(f"{task.module},", "") - await group.save(update_fields=["block_task"]) - return f"已成功{status_str} {task_name} 被动技能!" + elif task := await TaskInfo.get_or_none(name=task_name): + if status: + await GroupConsole.set_block_task(group_id, task.module) + elif await GroupConsole.is_superuser_block_task(group_id, task.module): + return f"{status_str} {task_name} 被动技能失败,当前群组该被动已被管理员禁用" # noqa: E501 + else: + await GroupConsole.set_unblock_task(group_id, task.module) + return f"已成功{status_str} {task_name} 被动技能!" return "没有找到这个被动技能喔..." @classmethod @@ -450,24 +456,18 @@ class PluginManage: if plugin_name.isdigit(): plugin = await PluginInfo.get_or_none(id=int(plugin_name)) else: - plugin = await PluginInfo.get_or_none(name=plugin_name) - status_str = "开启" if status else "关闭" - if plugin: - group, _ = await GroupConsole.get_or_create( - group_id=group_id, channel_id__isnull=True + plugin = await PluginInfo.get_or_none( + name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT ) + if plugin: + status_str = "开启" if status else "关闭" if status: - if plugin.module in group.block_plugin: - group.block_plugin = group.block_plugin.replace( - f"{plugin.module},", "" - ) - await group.save(update_fields=["block_plugin"]) - return f"已成功{status_str} {plugin.name} 功能!" - else: - if plugin.module not in group.block_plugin: - group.block_plugin += f"{plugin.module}," - await group.save(update_fields=["block_plugin"]) + if await GroupConsole.is_normal_block_plugin(group_id, plugin.module): + await GroupConsole.set_unblock_plugin(group_id, plugin.module) return f"已成功{status_str} {plugin.name} 功能!" + elif not await GroupConsole.is_normal_block_plugin(group_id, plugin.module): + await GroupConsole.set_block_plugin(group_id, plugin.module) + return f"已成功{status_str} {plugin.name} 功能!" return f"该功能已经{status_str}了喔,不要重复{status_str}..." return "没有找到这个功能喔..." @@ -485,22 +485,16 @@ class PluginManage: 返回: str: 返回信息 """ - if task := await TaskInfo.get_or_none(name=task_name): + if not (task := await TaskInfo.get_or_none(name=task_name)): + return "没有找到这个功能喔..." + if group_id: + if status: + await GroupConsole.set_unblock_task(group_id, task.module, True) + else: + await GroupConsole.set_block_task(group_id, task.module, True) status_str = "开启" if status else "关闭" - if group_id: - group, _ = await GroupConsole.get_or_create( - group_id=group_id, channel_id__isnull=True - ) - if status: - group.block_task = group.block_task.replace( - f"super:{task.module},", "" - ) - else: - group.block_task += f"super:{task.module}," - await group.save(update_fields=["block_task"]) - return f"已成功将群组 {group_id} 被动技能 {task_name} {status_str}!" - return "没有找到这个群组喔..." - return "没有找到这个功能喔..." + return f"已成功将群组 {group_id} 被动技能 {task_name} {status_str}!" + return "没有找到这个群组喔..." @classmethod async def superuser_block( @@ -519,30 +513,67 @@ class PluginManage: if plugin_name.isdigit(): plugin = await PluginInfo.get_or_none(id=int(plugin_name)) else: - plugin = await PluginInfo.get_or_none(name=plugin_name) + plugin = await PluginInfo.get_or_none( + name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT + ) if plugin: if group_id: - if group := await GroupConsole.get_or_none( - group_id=group_id, channel_id__isnull=True + if not await GroupConsole.is_superuser_block_plugin( + group_id, plugin.module ): - if f"super:{plugin.module}," not in group.block_plugin: - group.block_plugin += f"super:{plugin.module}," - await group.save(update_fields=["block_plugin"]) - return ( - f"已成功关闭群组 {group.group_name} 的 {plugin_name} 功能!" - ) - return "此群组该功能已被超级用户关闭,不要重复关闭..." - return "群组信息未更新,请先更新群组信息..." + await GroupConsole.set_block_plugin(group_id, plugin.module, True) + return f"已成功关闭群组 {group_id} 的 {plugin_name} 功能!" + return "此群组该功能已被超级用户关闭,不要重复关闭..." plugin.block_type = block_type plugin.status = not bool(block_type) await plugin.save(update_fields=["status", "block_type"]) if not block_type: return f"已成功将 {plugin.name} 全局启用!" - else: - if block_type == BlockType.ALL: - return f"已成功将 {plugin.name} 全局关闭!" - if block_type == BlockType.GROUP: - return f"已成功将 {plugin.name} 全局群组关闭!" - if block_type == BlockType.PRIVATE: - return f"已成功将 {plugin.name} 全局私聊关闭!" + if block_type == BlockType.ALL: + return f"已成功将 {plugin.name} 全局关闭!" + if block_type == BlockType.GROUP: + return f"已成功将 {plugin.name} 全局群组关闭!" + if block_type == BlockType.PRIVATE: + return f"已成功将 {plugin.name} 全局私聊关闭!" + return "没有找到这个功能喔..." + + @classmethod + async def superuser_unblock( + cls, plugin_name: str, block_type: BlockType | None, group_id: str | None + ) -> str: + """超级用户开启插件 + + 参数: + plugin_name: 插件名称 + block_type: 禁用类型 + group_id: 群组id + + 返回: + str: 返回信息 + """ + if plugin_name.isdigit(): + plugin = await PluginInfo.get_or_none(id=int(plugin_name)) + else: + plugin = await PluginInfo.get_or_none( + name=plugin_name, load_status=True, plugin_type__not=PluginType.PARENT + ) + if plugin: + if group_id: + if await GroupConsole.is_superuser_block_plugin( + group_id, plugin.module + ): + await GroupConsole.set_unblock_plugin(group_id, plugin.module, True) + return f"已成功开启群组 {group_id} 的 {plugin_name} 功能!" + return "此群组该功能已被超级用户开启,不要重复开启..." + plugin.block_type = block_type + plugin.status = not bool(block_type) + await plugin.save(update_fields=["status", "block_type"]) + if not block_type: + return f"已成功将 {plugin.name} 全局启用!" + if block_type == BlockType.ALL: + return f"已成功将 {plugin.name} 全局开启!" + if block_type == BlockType.GROUP: + return f"已成功将 {plugin.name} 全局群组开启!" + if block_type == BlockType.PRIVATE: + return f"已成功将 {plugin.name} 全局私聊开启!" return "没有找到这个功能喔..." diff --git a/zhenxun/builtin_plugins/admin/plugin_switch/command.py b/zhenxun/builtin_plugins/admin/plugin_switch/command.py index 6d33b4df..8c3e8fab 100644 --- a/zhenxun/builtin_plugins/admin/plugin_switch/command.py +++ b/zhenxun/builtin_plugins/admin/plugin_switch/command.py @@ -133,16 +133,16 @@ _status_matcher.shortcut( ) _status_matcher.shortcut( - r"关闭(?P.+)", + r"关闭(插件|功能)df(?P.+)", command="switch", - arguments=["close", "{name}"], + arguments=["close", "{name}", "-df"], prefix=True, ) _status_matcher.shortcut( - r"关闭(插件|功能)df(?P.+)", + r"关闭(?P.+)", command="switch", - arguments=["close", "{name}", "-df"], + arguments=["close", "{name}"], prefix=True, ) diff --git a/zhenxun/builtin_plugins/admin/welcome_message.py b/zhenxun/builtin_plugins/admin/welcome_message.py deleted file mode 100644 index 6cb6c8dc..00000000 --- a/zhenxun/builtin_plugins/admin/welcome_message.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -import shutil -from typing import Annotated, Dict - -import ujson as json -from nonebot import on_command -from nonebot.params import Command -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Image -from nonebot_plugin_alconna import Text as alcText -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.rules import admin_check, ensure_group - -base_config = Config.get("admin_bot_manage") - -__plugin_meta__ = PluginMetadata( - name="自定义群欢迎消息", - description="自定义群欢迎消息", - usage=""" - 设置欢迎消息 欢迎新人! - 设置欢迎消息 欢迎你 -at - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("SET_GROUP_WELCOME_MESSAGE_LEVEL", 2), - configs=[ - RegisterConfig( - module="admin_bot_manage", - key="SET_GROUP_WELCOME_MESSAGE_LEVEL", - value=2, - help="设置群欢迎消息所需要的管理员权限等级", - default_value=2, - ) - ], - ).dict(), -) - -_matcher = on_command( - "设置欢迎消息", - rule=admin_check("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL") - & ensure_group, - priority=5, - block=True, -) - - -BASE_PATH = DATA_PATH / "welcome_message" -BASE_PATH.mkdir(parents=True, exist_ok=True) - -# 旧数据迁移 -old_file = DATA_PATH / "custom_welcome_msg" / "custom_welcome_msg.json" -if old_file.exists(): - try: - old_data: Dict[str, str] = json.load(old_file.open(encoding="utf8")) - for group_id, message in old_data.items(): - file = BASE_PATH / "qq" / f"{group_id}" / "text.json" - file.parent.mkdir(parents=True, exist_ok=True) - json.dump( - {"at": "[at]" in message, "message": message.replace("[at]", "")}, - file.open("w", encoding="utf8"), - ensure_ascii=False, - indent=4, - ) - logger.debug("群欢迎消息数据迁移", group_id=group_id) - shutil.rmtree(old_file.parent.absolute()) - except Exception as e: - pass - - -@_matcher.handle() -async def _( - session: EventSession, - message: UniMsg, - command: Annotated[tuple[str, ...], Command()], -): - path = BASE_PATH / f"{session.platform or session.bot_type}" / f"{session.id2}" - if session.id3: - path = ( - BASE_PATH - / f"{session.platform or session.bot_type}" - / f"{session.id3}" - / f"{session.id2}" - ) - file = path / "text.json" - idx = 0 - text = "" - for f in os.listdir(path): - (path / f).unlink() - message[0].text = message[0].text.replace(command[0], "").strip() - for msg in message: - if isinstance(msg, alcText): - text += msg.text - elif isinstance(msg, Image): - if msg.url: - text += f"[image:{idx}]" - await AsyncHttpx.download_file(msg.url, path / f"{idx}.png") - idx += 1 - else: - logger.debug("图片 URL 为空...", command[0]) - if not file.exists(): - file.parent.mkdir(exist_ok=True, parents=True) - is_at = "-at" in message - text = text.replace("-at", "") - json.dump( - {"at": is_at, "message": text}, - file.open("w"), - ensure_ascii=False, - indent=4, - ) - uni_msg = alcText("设置欢迎消息成功: \n") + message - await uni_msg.send() - logger.info(f"设置群欢迎消息成功: {text}", command[0], session=session) diff --git a/zhenxun/builtin_plugins/admin/welcome_message/__init__.py b/zhenxun/builtin_plugins/admin/welcome_message/__init__.py new file mode 100644 index 00000000..28ed1952 --- /dev/null +++ b/zhenxun/builtin_plugins/admin/welcome_message/__init__.py @@ -0,0 +1,132 @@ +from typing import Annotated + +from nonebot import on_command +from nonebot.params import Command +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import ( + Alconna, + AlconnaMatcher, + Args, + Arparma, + Field, + Match, + Text, + UniMsg, + on_alconna, +) +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.configs.config import Config +from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.rules import admin_check, ensure_group + +from .data_source import Manager + +base_config = Config.get("admin_bot_manage") + +__plugin_meta__ = PluginMetadata( + name="自定义群欢迎消息", + description="自定义群欢迎消息", + usage=""" + 设置群欢迎消息,当消息中包含 -at 时会at入群用户 + 可以设置多条欢迎消息,包含多条欢迎消息时将随机发送 + 指令: + 设置欢迎消息 + 查看欢迎消息 ?[id]: 存在id时查看指定欢迎消息内容 + 删除欢迎消息 [id] + 示例: + 设置欢迎消息 欢迎新人![图片] + 设置欢迎消息 欢迎你 -at + 查看欢迎消息 + 查看欢迎消息 2 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.ADMIN, + admin_level=base_config.get("SET_GROUP_WELCOME_MESSAGE_LEVEL", 2), + configs=[ + RegisterConfig( + module="admin_bot_manage", + key="SET_GROUP_WELCOME_MESSAGE_LEVEL", + value=2, + help="设置群欢迎消息所需要的管理员权限等级", + default_value=2, + ) + ], + ).to_dict(), +) + +_matcher = on_command( + "设置欢迎消息", + rule=admin_check("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL") + & ensure_group, + priority=5, + block=True, +) + +_show_matcher = on_alconna( + Alconna("查看欢迎消息", Args["idx?", int]), + rule=admin_check("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL") + & ensure_group, + priority=5, + block=True, +) + +_del_matcher: type[AlconnaMatcher] = on_alconna( + Alconna( + "删除欢迎消息", + Args[ + "idx", + int, + Field( + missing_tips=lambda: "请在命令后跟随指定id!", + unmatch_tips=lambda _: "删除指定id必须为数字!", + ), + ], + ), + skip_for_unmatch=False, + rule=admin_check("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL") + & ensure_group, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _( + session: Uninfo, + message: UniMsg, + command: Annotated[tuple[str, ...], Command()], +): + path = Manager.get_path(session) + if not path: + await MessageUtils.build_message("群组不存在...").finish() + message[0].text = message[0].text.replace(command[0], "").strip() + await Manager.save(path, message) + uni_msg = Text("设置欢迎消息成功: \n") + message + await uni_msg.send() + logger.info(f"设置群欢迎消息成功: {message}", command[0], session=session) + + +@_show_matcher.handle() +async def _(session: Uninfo, arparma: Arparma, idx: Match[int]): + result = await Manager.get_group_message( + session, idx.result if idx.available else None + ) + if not result: + await MessageUtils.build_message("当前还未设置群组欢迎消息哦...").finish() + await MessageUtils.build_message(result).send() + logger.info("查看群组欢迎信息", arparma.header_result, session=session) + + +@_del_matcher.handle() +async def _(session: Uninfo, arparma: Arparma, idx: int): + result = await Manager.delete_group_message(session, int(idx)) + if not result: + await MessageUtils.build_message("未查找到指定id的群组欢迎消息...").finish() + await MessageUtils.build_message(result).send() + logger.info(f"删除群组欢迎信息: {result}", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/admin/welcome_message/data_source.py b/zhenxun/builtin_plugins/admin/welcome_message/data_source.py new file mode 100644 index 00000000..811a8dda --- /dev/null +++ b/zhenxun/builtin_plugins/admin/welcome_message/data_source.py @@ -0,0 +1,266 @@ +import os +from pathlib import Path +import re +import shutil +import uuid + +import nonebot +from nonebot_plugin_alconna import UniMessage, UniMsg +from nonebot_plugin_uninfo import Uninfo +import ujson as json + +from zhenxun.configs.path_config import DATA_PATH +from zhenxun.services.log import logger +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils._image_template import ImageTemplate +from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.utils.platform import PlatformUtils + +BASE_PATH = DATA_PATH / "welcome_message" +BASE_PATH.mkdir(parents=True, exist_ok=True) + +driver = nonebot.get_driver() + + +old_file = DATA_PATH / "custom_welcome_msg" / "custom_welcome_msg.json" +if old_file.exists(): + try: + old_data: dict[str, str] = json.load(old_file.open(encoding="utf8")) + for group_id, message in old_data.items(): + file = BASE_PATH / "qq" / f"{group_id}" / "text.json" + file.parent.mkdir(parents=True, exist_ok=True) + json.dump( + { + uuid.uuid4(): { + "at": "[at]" in message, + "status": True, + "message": message.replace("[at]", ""), + } + }, + file.open("w", encoding="utf8"), + ensure_ascii=False, + indent=4, + ) + logger.debug("群欢迎消息数据迁移", group_id=group_id) + shutil.rmtree(old_file.parent.absolute()) + except Exception as e: + logger.error("群欢迎消息数据迁移失败...", e=e) + + +def migrate(path: Path): + """数据迁移 + + 参数: + path: 路径 + """ + text_file = path / "text.json" + if not text_file.exists(): + return + with text_file.open(encoding="utf8") as f: + json_data = json.load(f) + new_data = {} + if "at" in json_data: + split_msg = re.split(r"\[image:\d\]", str(json_data["message"])) + data = [] + for i in range(len(split_msg)): + msg = split_msg[i] + data.append( + { + "type": "text", + "text": msg, + } + ) + image_file = path / f"{i}.png" + if image_file.exists(): + data.append( + { + "type": "image", + "path": str(image_file), + } + ) + new_data[uuid.uuid4()] = { + "at": json_data.get("at", False), + "status": json_data.get("status", True), + "message": data, + } + with text_file.open("w", encoding="utf8") as f: + json.dump(new_data, f, ensure_ascii=False, indent=4) + + +@driver.on_startup +def _(): + """数据迁移 + + 参数: + path: 存储路径 + json_data: 存储数据 + """ + flag_file = BASE_PATH / "flag.txt" + if flag_file.exists(): + return + logger.info("开始迁移群欢迎消息数据...") + base_path = BASE_PATH + path_list = [] + for platform in os.listdir(BASE_PATH): + base_path = base_path / platform + for group_id in os.listdir(base_path): + group_path = base_path / group_id + is_channel = False + for file in os.listdir(group_path): + inner_file = group_path / file + if inner_file.is_dir(): + path_list.append(inner_file) + is_channel = True + if not is_channel: + path_list.append(group_path) + if path_list: + for path in path_list: + migrate(path) + if not flag_file.exists(): + flag_file.touch() + logger.success("迁移群欢迎消息数据完成!", "") + + +class Manager: + @classmethod + def __get_data(cls, session: Uninfo) -> dict | None: + """获取存储数据 + + 参数: + session: Uninfo + + 返回: + dict | None: 欢迎消息数据 + """ + if not session.group: + return None + path = cls.get_path(session) + if not path: + return None + file = path / "text.json" + if not file.exists(): + return None + with file.open(encoding="utf8") as f: + return json.load(f) + + @classmethod + def get_path(cls, session: Uninfo) -> Path | None: + """根据Session获取存储路径 + + 参数: + session: Uninfo: + + 返回: + Path: 存储路径 + """ + if not session.group: + return None + platform = PlatformUtils.get_platform(session) + path = BASE_PATH / f"{platform}" / f"{session.group.id}" + if session.group.parent: + path = ( + BASE_PATH + / f"{platform}" + / f"{session.group.parent.id}" + / f"{session.group.id}" + ) + path.mkdir(parents=True, exist_ok=True) + return path + + @classmethod + async def save(cls, path: Path, message: UniMsg): + """保存群欢迎消息 + + 参数: + path: 存储路径 + message: 消息内容 + """ + file = path / "text.json" + json_data = {} + if file.exists(): + with file.open(encoding="utf8") as f: + json_data = json.load(f) + data = [] + is_at = False + for msg in message.dump(True): + if msg["type"] == "image": + image_file = path / f"{uuid.uuid4()}.png" + await AsyncHttpx.download_file(msg["url"], image_file) + msg["path"] = str(image_file) + if not is_at and msg["type"] == "text" and "-at" in msg["text"]: + msg["text"] = msg["text"].replace("-at", "", 1).strip() + is_at = True + data.append(msg) + json_data[str(uuid.uuid4())] = {"at": is_at, "status": True, "message": data} + with file.open("w", encoding="utf8") as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) + + @classmethod + async def get_group_message( + cls, session: Uninfo, idx: int | None + ) -> BuildImage | UniMessage | None: + """获取群欢迎消息 + + 参数: + session: Uninfo + idx: 指定id + + 返回: + list: 消息内容 + """ + if not session.group: + return None + json_data = cls.__get_data(session) + if not json_data: + return None + if idx is not None: + key_list = list(json_data.keys()) + if idx < 0 or idx > len(key_list): + return None + return UniMessage().load(json_data[key_list[idx]]["message"]) + else: + msg_list = [] + for i, uid in enumerate(json_data): + msg_data = json_data[uid] + msg_list.append( + [ + i, + "开启" if msg_data["status"] else "关闭", + "是" if msg_data["at"] else "否", + str(UniMessage().load(msg_data["message"])), + ] + ) + if not msg_list: + return None + column_name = ["ID", "状态", "是否@", "消息"] + return await ImageTemplate.table_page( + "群欢迎消息", session.group.id, column_name, msg_list + ) + + @classmethod + async def delete_group_message(cls, session: Uninfo, idx: int) -> str | None: + """获取群欢迎消息 + + 参数: + session: EventSession: + id: 消息ID + + 返回: + list: 消息内容 + """ + json_data = cls.__get_data(session) + if not json_data: + return None + key_list = list(json_data.keys()) + if idx < 0 or idx >= len(key_list): + return None + old_msg = str(UniMessage().load(json_data[key_list[idx]]["message"])) + for msg in json_data[key_list[idx]]["message"]: + if msg["type"] == "image" and msg["path"]: + image_path = Path(msg["path"]) + if image_path.exists(): + image_path.unlink() + del json_data[key_list[idx]] + with file.open("w", encoding="utf8") as f: + json.dump(json_data, f, ensure_ascii=False, indent=4) + return f"删除群组欢迎消息成功!消息内容: {old_msg}" diff --git a/zhenxun/builtin_plugins/auto_update/__init__.py b/zhenxun/builtin_plugins/auto_update/__init__.py new file mode 100644 index 00000000..3d906e1e --- /dev/null +++ b/zhenxun/builtin_plugins/auto_update/__init__.py @@ -0,0 +1,97 @@ +from nonebot.adapters import Bot +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot.rule import to_me +from nonebot_plugin_alconna import ( + Alconna, + Args, + Match, + Option, + Query, + on_alconna, + store_true, +) +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.manager.resource_manager import ( + DownloadResourceException, + ResourceManager, +) +from zhenxun.utils.message import MessageUtils + +from ._data_source import UpdateManage + +__plugin_meta__ = PluginMetadata( + name="自动更新", + description="就算是真寻也会成长的", + usage=""" + usage: + 检查更新真寻最新版本,包括了自动更新 + 资源文件大小一般在130mb左右,除非必须更新一般仅更新代码文件 + 指令: + 检查更新 [main|release|resource] ?[-r] + main: main分支 + release: 最新release + resource: 资源文件 + -r: 下载资源文件,一般在更新main或release时使用 + 示例: + 检查更新 main + 检查更新 main -r + 检查更新 release -r + 检查更新 resource + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + ).to_dict(), +) + +_matcher = on_alconna( + Alconna( + "检查更新", + Args["ver_type?", ["main", "release", "resource"]], + Option("-r|--resource", action=store_true, help_text="下载资源文件"), + ), + priority=1, + block=True, + permission=SUPERUSER, + rule=to_me(), +) + + +@_matcher.handle() +async def _( + bot: Bot, + session: EventSession, + ver_type: Match[str], + resource: Query[bool] = Query("resource", False), +): + if not session.id1: + await MessageUtils.build_message("用户id为空...").finish() + result = "" + if ver_type.result in {"main", "release"}: + if not ver_type.available: + result = await UpdateManage.check_version() + logger.info("查看当前版本...", "检查更新", session=session) + await MessageUtils.build_message(result).finish() + try: + result = await UpdateManage.update(bot, session.id1, ver_type.result) + except Exception as e: + logger.error("版本更新失败...", "检查更新", session=session, e=e) + await MessageUtils.build_message(f"更新版本失败...e: {e}").finish() + if resource.result or ver_type.result == "resource": + try: + await ResourceManager.init_resources(True) + result += "\n资源文件更新成功!" + except DownloadResourceException: + result += "\n资源更新下载失败..." + except Exception as e: + logger.error("资源更新下载失败...", "检查更新", session=session, e=e) + result += "\n资源更新未知错误..." + if result: + await MessageUtils.build_message(result.strip()).finish() + await MessageUtils.build_message("更新版本失败...").finish() diff --git a/zhenxun/builtin_plugins/auto_update/_data_source.py b/zhenxun/builtin_plugins/auto_update/_data_source.py new file mode 100644 index 00000000..5c7a1df8 --- /dev/null +++ b/zhenxun/builtin_plugins/auto_update/_data_source.py @@ -0,0 +1,264 @@ +import os +import shutil +import subprocess +import tarfile +import zipfile + +from nonebot.adapters import Bot +from nonebot.utils import run_sync + +from zhenxun.services.log import logger +from zhenxun.utils.github_utils import GithubUtils +from zhenxun.utils.github_utils.models import RepoInfo +from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.utils.platform import PlatformUtils + +from .config import ( + BACKUP_PATH, + BASE_PATH, + BASE_PATH_STRING, + DEFAULT_GITHUB_URL, + DOWNLOAD_GZ_FILE, + DOWNLOAD_ZIP_FILE, + PYPROJECT_FILE, + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE, + PYPROJECT_LOCK_FILE_STRING, + RELEASE_URL, + REPLACE_FOLDERS, + REQ_TXT_FILE, + REQ_TXT_FILE_STRING, + TMP_PATH, + VERSION_FILE, +) + + +def install_requirement(): + requirement_path = (REQ_TXT_FILE).absolute() + + if not requirement_path.exists(): + logger.debug( + f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", "插件管理" + ) + return + try: + result = subprocess.run( + ["pip", "install", "-r", str(requirement_path)], + check=True, + capture_output=True, + text=True, + ) + logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", "插件管理") + except subprocess.CalledProcessError as e: + logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", "插件管理", e=e) + + +@run_sync +def _file_handle(latest_version: str | None): + """文件移动操作 + + 参数: + latest_version: 版本号 + """ + BACKUP_PATH.mkdir(exist_ok=True, parents=True) + logger.debug("开始解压文件压缩包...", "检查更新") + download_file = DOWNLOAD_GZ_FILE + if DOWNLOAD_GZ_FILE.exists(): + tf = tarfile.open(DOWNLOAD_GZ_FILE) + else: + download_file = DOWNLOAD_ZIP_FILE + tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE) + tf.extractall(TMP_PATH) + logger.debug("解压文件压缩包完成...", "检查更新") + download_file_path = TMP_PATH / next( + x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir() + ) + _pyproject = download_file_path / PYPROJECT_FILE_STRING + _lock_file = download_file_path / PYPROJECT_LOCK_FILE_STRING + _req_file = download_file_path / REQ_TXT_FILE_STRING + extract_path = download_file_path / BASE_PATH_STRING + target_path = BASE_PATH + if PYPROJECT_FILE.exists(): + logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新") + shutil.move(PYPROJECT_FILE, BACKUP_PATH / PYPROJECT_FILE_STRING) + if PYPROJECT_LOCK_FILE.exists(): + logger.debug(f"移除备份文件: {PYPROJECT_LOCK_FILE}", "检查更新") + shutil.move(PYPROJECT_LOCK_FILE, BACKUP_PATH / PYPROJECT_LOCK_FILE_STRING) + if REQ_TXT_FILE.exists(): + logger.debug(f"移除备份文件: {REQ_TXT_FILE}", "检查更新") + shutil.move(REQ_TXT_FILE, BACKUP_PATH / REQ_TXT_FILE_STRING) + if _pyproject.exists(): + logger.debug("移动文件: pyproject.toml", "检查更新") + shutil.move(_pyproject, PYPROJECT_FILE) + if _lock_file.exists(): + logger.debug("移动文件: poetry.lock", "检查更新") + shutil.move(_lock_file, PYPROJECT_LOCK_FILE) + if _req_file.exists(): + logger.debug("移动文件: requirements.txt", "检查更新") + shutil.move(_req_file, REQ_TXT_FILE) + for folder in REPLACE_FOLDERS: + """移动指定文件夹""" + _dir = BASE_PATH / folder + _backup_dir = BACKUP_PATH / folder + if _backup_dir.exists(): + logger.debug(f"删除备份文件夹 {_backup_dir}", "检查更新") + shutil.rmtree(_backup_dir) + if _dir.exists(): + logger.debug(f"移动旧文件夹 {_dir}", "检查更新") + shutil.move(_dir, _backup_dir) + else: + logger.warning(f"文件夹 {_dir} 不存在,跳过删除", "检查更新") + for folder in REPLACE_FOLDERS: + src_folder_path = extract_path / folder + dest_folder_path = target_path / folder + if src_folder_path.exists(): + logger.debug( + f"移动文件夹: {src_folder_path} -> {dest_folder_path}", "检查更新" + ) + shutil.move(src_folder_path, dest_folder_path) + else: + logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新") + if tf: + tf.close() + if download_file.exists(): + logger.debug(f"删除下载文件: {download_file}", "检查更新") + download_file.unlink() + if extract_path.exists(): + logger.debug(f"删除解压文件夹: {extract_path}", "检查更新") + shutil.rmtree(extract_path) + if TMP_PATH.exists(): + shutil.rmtree(TMP_PATH) + if latest_version: + with open(VERSION_FILE, "w", encoding="utf8") as f: + f.write(f"__version__: {latest_version}") + install_requirement() + + +class UpdateManage: + @classmethod + async def check_version(cls) -> str: + """检查更新版本 + + 返回: + str: 更新信息 + """ + cur_version = cls.__get_version() + data = await cls.__get_latest_data() + if not data: + return "检查更新获取版本失败..." + return ( + "检测到当前版本更新\n" + f"当前版本:{cur_version}\n" + f"最新版本:{data.get('name')}\n" + f"创建日期:{data.get('created_at')}\n" + f"更新内容:\n{data.get('body')}" + ) + + @classmethod + async def update(cls, bot: Bot, user_id: str, version_type: str) -> str: + """更新操作 + + 参数: + bot: Bot + user_id: 用户id + version_type: 更新版本类型 + + 返回: + str | None: 返回消息 + """ + logger.info("开始下载真寻最新版文件....", "检查更新") + cur_version = cls.__get_version() + url = None + new_version = None + repo_info = GithubUtils.parse_github_url(DEFAULT_GITHUB_URL) + if version_type in {"main"}: + repo_info.branch = version_type + new_version = await cls.__get_version_from_repo(repo_info) + if new_version: + new_version = new_version.split(":")[-1].strip() + url = await repo_info.get_archive_download_urls() + elif version_type == "release": + data = await cls.__get_latest_data() + if not data: + return "获取更新版本失败..." + new_version = data.get("name", "") + url = await repo_info.get_release_source_download_urls_tgz(new_version) + if not url: + return "获取版本下载链接失败..." + if TMP_PATH.exists(): + logger.debug(f"删除临时文件夹 {TMP_PATH}", "检查更新") + shutil.rmtree(TMP_PATH) + logger.debug( + f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}", + "检查更新", + ) + await PlatformUtils.send_superuser( + bot, + f"检测真寻已更新,版本更新:{cur_version} -> {new_version}\n开始更新...", + user_id, + ) + download_file = ( + DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE + ) + if await AsyncHttpx.download_file(url, download_file, stream=True): + logger.debug("下载真寻最新版文件完成...", "检查更新") + await _file_handle(new_version) + result = "版本更新完成" + return ( + f"{result}\n" + f"版本: {cur_version} -> {new_version}\n" + "请重新启动真寻以完成更新!" + ) + else: + logger.debug("下载真寻最新版文件失败...", "检查更新") + return "" + + @classmethod + def __get_version(cls) -> str: + """获取当前版本 + + 返回: + str: 当前版本号 + """ + _version = "v0.0.0" + if VERSION_FILE.exists(): + if text := VERSION_FILE.open(encoding="utf8").readline(): + _version = text.split(":")[-1].strip() + return _version + + @classmethod + async def __get_latest_data(cls) -> dict: + """获取最新版本信息 + + 返回: + dict: 最新版本数据 + """ + for _ in range(3): + try: + res = await AsyncHttpx.get(RELEASE_URL) + if res.status_code == 200: + return res.json() + except TimeoutError: + pass + except Exception as e: + logger.error("检查更新真寻获取版本失败", e=e) + return {} + + @classmethod + async def __get_version_from_repo(cls, repo_info: RepoInfo) -> str: + """从指定分支获取版本号 + + 参数: + branch: 分支名称 + + 返回: + str: 版本号 + """ + version_url = await repo_info.get_raw_download_urls(path="__version__") + try: + res = await AsyncHttpx.get(version_url) + if res.status_code == 200: + return res.text.strip() + except Exception as e: + logger.error(f"获取 {repo_info.branch} 分支版本失败", e=e) + return "未知版本" diff --git a/zhenxun/builtin_plugins/auto_update/config.py b/zhenxun/builtin_plugins/auto_update/config.py new file mode 100644 index 00000000..b4c61345 --- /dev/null +++ b/zhenxun/builtin_plugins/auto_update/config.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from zhenxun.configs.path_config import TEMP_PATH + +DEFAULT_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot/tree/main" +RELEASE_URL = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest" + +VERSION_FILE_STRING = "__version__" +VERSION_FILE = Path() / VERSION_FILE_STRING + +PYPROJECT_FILE_STRING = "pyproject.toml" +PYPROJECT_FILE = Path() / PYPROJECT_FILE_STRING +PYPROJECT_LOCK_FILE_STRING = "poetry.lock" +PYPROJECT_LOCK_FILE = Path() / PYPROJECT_LOCK_FILE_STRING +REQ_TXT_FILE_STRING = "requirements.txt" +REQ_TXT_FILE = Path() / REQ_TXT_FILE_STRING + +BASE_PATH_STRING = "zhenxun" +BASE_PATH = Path() / BASE_PATH_STRING + +TMP_PATH = TEMP_PATH / "auto_update" + +BACKUP_PATH = Path() / "backup" + +DOWNLOAD_GZ_FILE_STRING = "download_latest_file.tar.gz" +DOWNLOAD_ZIP_FILE_STRING = "download_latest_file.zip" +DOWNLOAD_GZ_FILE = TMP_PATH / DOWNLOAD_GZ_FILE_STRING +DOWNLOAD_ZIP_FILE = TMP_PATH / DOWNLOAD_ZIP_FILE_STRING + +REPLACE_FOLDERS = [ + "builtin_plugins", + "services", + "utils", + "models", + "configs", +] diff --git a/zhenxun/builtin_plugins/catchphrase.py b/zhenxun/builtin_plugins/catchphrase.py new file mode 100644 index 00000000..23736b5b --- /dev/null +++ b/zhenxun/builtin_plugins/catchphrase.py @@ -0,0 +1,28 @@ +from typing import Any + +from nonebot.adapters import Bot + +from zhenxun.configs.config import Config +from zhenxun.services.log import logger + +Config.add_plugin_config( + "catchphrase", + "CATCHPHRASE", + "", + help="小真寻的口癖,在文本末尾添加指定文字~", + default_value="", +) + + +@Bot.on_calling_api +async def handle_api_call(bot: Bot, api: str, data: dict[str, Any]): + if api == "send_msg": + catchphrase = Config.get_config("catchphrase", "CATCHPHRASE") + if catchphrase and (message := data.get("message")): + for i in range(len(message) - 1, -1, -1): + if message[i].type == "text": + message[i].data["text"] += catchphrase + logger.debug( + f"文本: {message[i].data['text']} 添加口癖: {catchphrase}" + ) + break diff --git a/zhenxun/builtin_plugins/chat_history/__init__.py b/zhenxun/builtin_plugins/chat_history/__init__.py index 838488cf..eb35e275 100644 --- a/zhenxun/builtin_plugins/chat_history/__init__.py +++ b/zhenxun/builtin_plugins/chat_history/__init__.py @@ -1,4 +1,5 @@ -import nonebot from pathlib import Path +import nonebot + nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/builtin_plugins/chat_history/chat_message.py b/zhenxun/builtin_plugins/chat_history/chat_message.py index 2cfdd607..d9e58c5a 100644 --- a/zhenxun/builtin_plugins/chat_history/chat_message.py +++ b/zhenxun/builtin_plugins/chat_history/chat_message.py @@ -28,7 +28,7 @@ __plugin_meta__ = PluginMetadata( type=bool, ) ], - ).dict(), + ).to_dict(), ) @@ -70,7 +70,7 @@ async def _(): await ChatHistory.bulk_create(message_list) logger.debug(f"批量添加聊天记录 {len(message_list)} 条", "定时任务") except Exception as e: - logger.error(f"定时批量添加聊天记录", "定时任务", e=e) + logger.error("定时批量添加聊天记录", "定时任务", e=e) # @test.handle() diff --git a/zhenxun/builtin_plugins/chat_history/chat_message_handle.py b/zhenxun/builtin_plugins/chat_history/chat_message_handle.py index 1287e1b0..10cfcf43 100644 --- a/zhenxun/builtin_plugins/chat_history/chat_message_handle.py +++ b/zhenxun/builtin_plugins/chat_history/chat_message_handle.py @@ -1,6 +1,5 @@ from datetime import datetime, timedelta -import pytz from nonebot.plugin import PluginMetadata from nonebot_plugin_alconna import ( Alconna, @@ -13,8 +12,9 @@ from nonebot_plugin_alconna import ( store_true, ) from nonebot_plugin_session import EventSession +import pytz -from zhenxun.configs.utils import PluginExtraData +from zhenxun.configs.utils import Command, PluginExtraData from zhenxun.models.chat_history import ChatHistory from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.services.log import logger @@ -45,7 +45,14 @@ __plugin_meta__ = PluginMetadata( version="0.1", plugin_type=PluginType.NORMAL, menu_type="数据统计", - ).dict(), + commands=[ + Command(command="消息统计"), + Command(command="日消息统计"), + Command(command="周消息排行"), + Command(command="月消息排行"), + Command(command="年消息排行"), + ], + ).to_dict(), ) @@ -113,7 +120,10 @@ async def _( date_scope = time_now.replace(microsecond=0) date_str = f"{str(date_scope).split('+')[0]} - 至今" else: - date_str = f"{date_scope[0].replace(microsecond=0)} - {date_scope[1].replace(microsecond=0)}" + date_str = ( + f"{date_scope[0].replace(microsecond=0)} - " + f"{date_scope[1].replace(microsecond=0)}" + ) A = await ImageTemplate.table_page( f"消息排行({count.result})", date_str, column_name, data_list ) diff --git a/zhenxun/builtin_plugins/check/__init__.py b/zhenxun/builtin_plugins/check/__init__.py new file mode 100644 index 00000000..35122e44 --- /dev/null +++ b/zhenxun/builtin_plugins/check/__init__.py @@ -0,0 +1,95 @@ +from nonebot import on_notice +from nonebot.adapters.onebot.v11 import PokeNotifyEvent +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot.rule import Rule, to_me +from nonebot_plugin_alconna import Alconna, on_alconna +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.rules import notice_rule + +from .data_source import get_status_info + +__plugin_meta__ = PluginMetadata( + name="服务器自我检查", + description="查看服务器当前状态", + usage=""" + 查看服务器当前状态 + 指令: + 自检 + 戳一戳BOT + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + configs=[ + RegisterConfig( + key="type", + value="mix", + help="自检触发方式 ['message', 'poke', 'mix']", + default_value="mix", + ) + ], + ).to_dict(), +) + + +def commandRule() -> Rule: + return Rule(lambda: Config.get_config("check", "type") in {"message", "mix"}) + + +def noticeRule() -> Rule: + return Rule(lambda: Config.get_config("check", "type") in {"poke", "mix"}) + + +_self_check_matcher = _self_check_matcher = on_alconna( + Alconna("自检"), + rule=to_me() & commandRule(), + permission=SUPERUSER, + block=True, + priority=1, +) + +_self_check_poke_matcher = on_notice( + priority=5, + permission=SUPERUSER, + block=False, + rule=notice_rule(PokeNotifyEvent) & to_me() & noticeRule(), +) + + +async def handle_self_check(): + try: + data = await get_status_info() + image = await template_to_pic( + template_path=str((TEMPLATE_PATH / "check").absolute()), + template_name="main.html", + templates={"data": data}, + pages={ + "viewport": {"width": 195, "height": 750}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + await MessageUtils.build_message(image).send() + logger.info("自检成功", "自检") + except Exception as e: + await MessageUtils.build_message(f"自检失败: {e}").send() + logger.error("自检失败", "自检", e=e) + + +@_self_check_matcher.handle() +async def handle_message_check(): + await handle_self_check() + + +@_self_check_poke_matcher.handle() +async def handle_poke_check(): + await handle_self_check() diff --git a/zhenxun/builtin_plugins/check/data_source.py b/zhenxun/builtin_plugins/check/data_source.py new file mode 100644 index 00000000..368500c7 --- /dev/null +++ b/zhenxun/builtin_plugins/check/data_source.py @@ -0,0 +1,209 @@ +from dataclasses import dataclass +import os +from pathlib import Path +import platform +import subprocess + +import cpuinfo +import nonebot +from nonebot.utils import run_sync +import psutil +from pydantic import BaseModel + +from zhenxun.configs.config import BotConfig +from zhenxun.services.log import logger +from zhenxun.utils.http_utils import AsyncHttpx + +BAIDU_URL = "https://www.baidu.com/" +GOOGLE_URL = "https://www.google.com/" + +VERSION_FILE = Path() / "__version__" +ARM_KEY = "aarch64" + + +@dataclass +class CPUInfo: + core: int | None + """CPU 物理核心数""" + usage: float + """CPU 占用百分比,取值范围(0,100]""" + freq: float + """CPU 的时钟速度(单位:GHz)""" + + @classmethod + def get_cpu_info(cls): + cpu_core = psutil.cpu_count(logical=False) + cpu_usage = psutil.cpu_percent(interval=0.1) + if _cpu_freq := psutil.cpu_freq(): + cpu_freq = round(_cpu_freq.current / 1000, 2) + else: + cpu_freq = 0 + return CPUInfo(core=cpu_core, usage=cpu_usage, freq=cpu_freq) + + +@dataclass +class RAMInfo: + """RAM 信息(单位:GB)""" + + total: float + """RAM 总量""" + usage: float + """当前 RAM 占用量/GB""" + + @classmethod + def get_ram_info(cls): + ram_total = round(psutil.virtual_memory().total / (1024**3), 2) + ram_usage = round(psutil.virtual_memory().used / (1024**3), 2) + + return RAMInfo(total=ram_total, usage=ram_usage) + + +@dataclass +class SwapMemory: + """Swap 信息(单位:GB)""" + + total: float + """Swap 总量""" + usage: float + """当前 Swap 占用量/GB""" + + @classmethod + def get_swap_info(cls): + swap_total = round(psutil.swap_memory().total / (1024**3), 2) + swap_usage = round(psutil.swap_memory().used / (1024**3), 2) + + return SwapMemory(total=swap_total, usage=swap_usage) + + +@dataclass +class DiskInfo: + """硬盘信息""" + + total: float + """硬盘总量""" + usage: float + """当前硬盘占用量/GB""" + + @classmethod + def get_disk_info(cls): + disk_total = round(psutil.disk_usage("/").total / (1024**3), 2) + disk_usage = round(psutil.disk_usage("/").used / (1024**3), 2) + + return DiskInfo(total=disk_total, usage=disk_usage) + + +class SystemInfo(BaseModel): + """系统信息""" + + cpu: CPUInfo + """CPU信息""" + ram: RAMInfo + """RAM信息""" + swap: SwapMemory + """SWAP信息""" + disk: DiskInfo + """DISK信息""" + + def get_system_info(self): + return { + "cpu_info": f"{self.cpu.usage}% - {self.cpu.freq}Ghz " + f"[{self.cpu.core} core]", + "cpu_process": self.cpu.usage, + "ram_info": f"{self.ram.usage} / {self.ram.total} GB", + "ram_process": ( + 0 if self.ram.total == 0 else (self.ram.usage / self.ram.total * 100) + ), + "swap_info": f"{self.swap.usage} / {self.swap.total} GB", + "swap_process": ( + 0 if self.swap.total == 0 else (self.swap.usage / self.swap.total * 100) + ), + "disk_info": f"{self.disk.usage} / {self.disk.total} GB", + "disk_process": ( + 0 if self.disk.total == 0 else (self.disk.usage / self.disk.total * 100) + ), + } + + +@run_sync +def __build_status() -> SystemInfo: + """获取 `CPU` `RAM` `SWAP` `DISK` 信息""" + cpu = CPUInfo.get_cpu_info() + ram = RAMInfo.get_ram_info() + swap = SwapMemory.get_swap_info() + disk = DiskInfo.get_disk_info() + + return SystemInfo(cpu=cpu, ram=ram, swap=swap, disk=disk) + + +async def __get_network_info(): + """网络请求""" + baidu, google = True, True + try: + await AsyncHttpx.get(BAIDU_URL, timeout=5) + except Exception as e: + logger.warning("自检:百度无法访问...", e=e) + baidu = False + try: + await AsyncHttpx.get(GOOGLE_URL, timeout=5) + except Exception as e: + logger.warning("自检:谷歌无法访问...", e=e) + google = False + return baidu, google + + +def __get_version() -> str | None: + """获取版本信息""" + if VERSION_FILE.exists(): + with open(VERSION_FILE, encoding="utf-8") as f: + if text := f.read(): + return text.split(":")[-1] + return None + + +def __get_arm_cpu(): + env = os.environ.copy() + env["LC_ALL"] = "en_US.UTF-8" + cpu_info = subprocess.check_output(["lscpu"], env=env).decode() + model_name = "" + cpu_freq = 0 + for line in cpu_info.splitlines(): + if "Model name" in line: + model_name = line.split(":")[1].strip() + if "CPU MHz" in line: + cpu_freq = float(line.split(":")[1].strip()) + return model_name, cpu_freq + + +def __get_arm_oracle_cpu_freq(): + cpu_freq = subprocess.check_output( + ["dmidecode", "-s", "processor-frequency"] + ).decode() + return round(float(cpu_freq.split()[0]) / 1000, 2) + + +async def get_status_info() -> dict: + """获取信息""" + data = await __build_status() + + system = platform.uname() + if system.machine == ARM_KEY and not ( + cpuinfo.get_cpu_info().get("brand_raw") and data.cpu.freq + ): + model_name, cpu_freq = __get_arm_cpu() + if not data.cpu.freq: + data.cpu.freq = cpu_freq or __get_arm_oracle_cpu_freq() + data = data.get_system_info() + data["brand_raw"] = model_name + else: + data = data.get_system_info() + data["brand_raw"] = cpuinfo.get_cpu_info().get("brand_raw", "Unknown") + + baidu, google = await __get_network_info() + data["baidu"] = "#8CC265" if baidu else "red" + data["google"] = "#8CC265" if google else "red" + + data["system"] = f"{system.system} {system.release}" + data["version"] = __get_version() + data["plugin_count"] = len(nonebot.get_loaded_plugins()) + data["nickname"] = BotConfig.self_nickname + return data diff --git a/zhenxun/builtin_plugins/help/__init__.py b/zhenxun/builtin_plugins/help/__init__.py index 69a981a3..726d4d1e 100644 --- a/zhenxun/builtin_plugins/help/__init__.py +++ b/zhenxun/builtin_plugins/help/__init__.py @@ -11,17 +11,19 @@ from nonebot_plugin_alconna import ( on_alconna, store_true, ) -from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo -from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.builtin_plugins.help._config import ( + GROUP_HELP_PATH, + SIMPLE_DETAIL_HELP_IMAGE, + SIMPLE_HELP_IMAGE, +) from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import BuildImage from zhenxun.utils.message import MessageUtils from ._data_source import create_help_img, get_plugin_help -from ._utils import GROUP_HELP_PATH __plugin_meta__ = PluginMetadata( name="帮助", @@ -31,27 +33,25 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.DEPENDANT, + is_show=False, configs=[ RegisterConfig( key="type", value="normal", - help="帮助图片样式 ['normal', 'HTML']", - default_value="normal", + help="帮助图片样式 ['normal', 'HTML', 'zhenxun']", + default_value="zhenxun", ) ], - ).dict(), + ).to_dict(), ) -SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png" -if SIMPLE_HELP_IMAGE.exists(): - SIMPLE_HELP_IMAGE.unlink() - _matcher = on_alconna( Alconna( "功能", Args["name?", str], Option("-s|--superuser", action=store_true, help_text="超级用户帮助"), + Option("-d|--detail", action=store_true, help_text="详细帮助"), ), aliases={"help", "帮助", "菜单"}, rule=to_me(), @@ -60,42 +60,43 @@ _matcher = on_alconna( ) +_matcher.shortcut( + r"详细帮助", + command="功能", + arguments=["--detail"], + prefix=True, +) + + @_matcher.handle() async def _( bot: Bot, name: Match[str], - session: EventSession, + session: Uninfo, is_superuser: Query[bool] = AlconnaQuery("superuser.value", False), + is_detail: Query[bool] = AlconnaQuery("detail.value", False), ): - _is_superuser = False - if is_superuser.available: - _is_superuser = is_superuser.result + _is_superuser = is_superuser.result if is_superuser.available else False if name.available: - if _is_superuser and session.id1 not in bot.config.superusers: + if _is_superuser and session.user.id not in bot.config.superusers: _is_superuser = False - if result := await get_plugin_help(name.result, _is_superuser): - if isinstance(result, BuildImage): - await MessageUtils.build_message(result).send(reply_to=True) - else: - await MessageUtils.build_message(result).send(reply_to=True) + if result := await get_plugin_help(session.user.id, name.result, _is_superuser): + await MessageUtils.build_message(result).send(reply_to=True) else: await MessageUtils.build_message("没有此功能的帮助信息...").send( reply_to=True ) - logger.info( - f"查看帮助详情: {name.result}", - "帮助", - session=session, - ) + logger.info(f"查看帮助详情: {name.result}", "帮助", session=session) + elif session.group and (gid := session.group.id): + _image_path = GROUP_HELP_PATH / f"{gid}_{is_detail.result}.png" + if not _image_path.exists(): + result = await create_help_img(session, gid, is_detail.result) + await MessageUtils.build_message(_image_path).finish() else: - if gid := session.id3 or session.id2: - _image_path = GROUP_HELP_PATH / f"{gid}.png" - if not _image_path.exists(): - await create_help_img(gid) - await MessageUtils.build_message(_image_path).finish() + if is_detail.result: + _image_path = SIMPLE_DETAIL_HELP_IMAGE else: - if not SIMPLE_HELP_IMAGE.exists(): - if SIMPLE_HELP_IMAGE.exists(): - SIMPLE_HELP_IMAGE.unlink() - await create_help_img(None) - await MessageUtils.build_message(SIMPLE_HELP_IMAGE).finish() + _image_path = SIMPLE_HELP_IMAGE + if not _image_path.exists(): + result = await create_help_img(session, None, is_detail.result) + await MessageUtils.build_message(_image_path).finish() diff --git a/zhenxun/builtin_plugins/help/_config.py b/zhenxun/builtin_plugins/help/_config.py index b38bf066..586026c5 100644 --- a/zhenxun/builtin_plugins/help/_config.py +++ b/zhenxun/builtin_plugins/help/_config.py @@ -1,13 +1,17 @@ -from pydantic import BaseModel +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH +GROUP_HELP_PATH = DATA_PATH / "group_help" +GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True) +for f in GROUP_HELP_PATH.iterdir(): + f.unlink() -class Item(BaseModel): - plugin_name: str - sta: int +SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png" +if SIMPLE_HELP_IMAGE.exists(): + SIMPLE_HELP_IMAGE.unlink() +SIMPLE_DETAIL_HELP_IMAGE = IMAGE_PATH / "SIMPLE_DETAIL_HELP.png" +if SIMPLE_DETAIL_HELP_IMAGE.exists(): + SIMPLE_DETAIL_HELP_IMAGE.unlink() -class PluginList(BaseModel): - plugin_type: str - icon: str - logo: str - items: list[Item] +base_config = Config.get("help") diff --git a/zhenxun/builtin_plugins/help/_data_source.py b/zhenxun/builtin_plugins/help/_data_source.py index 72467b2e..cfaa4503 100644 --- a/zhenxun/builtin_plugins/help/_data_source.py +++ b/zhenxun/builtin_plugins/help/_data_source.py @@ -1,37 +1,100 @@ +from pathlib import Path + import nonebot +from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.level_user import LevelUser from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.enum import PluginType from zhenxun.utils.image_utils import BuildImage, ImageTemplate -from ._utils import HelpImageBuild +from ._config import ( + GROUP_HELP_PATH, + SIMPLE_DETAIL_HELP_IMAGE, + SIMPLE_HELP_IMAGE, + base_config, +) +from .html_help import build_html_image +from .normal_help import build_normal_image +from .zhenxun_help import build_zhenxun_image random_bk_path = IMAGE_PATH / "background" / "help" / "simple_help" background = IMAGE_PATH / "background" / "0.png" -async def create_help_img(group_id: str | None): - """ - 说明: - 生成帮助图片 +driver = nonebot.get_driver() + + +async def create_help_img( + session: Uninfo, group_id: str | None, is_detail: bool +) -> Path: + """生成帮助图片 + 参数: - :param group_id: 群号 + session: Uninfo + group_id: 群号 """ - await HelpImageBuild().build_image(group_id) + help_type = base_config.get("type", "").strip().lower() + + match help_type: + case "html": + result = BuildImage.open(await build_html_image(group_id, is_detail)) + case "zhenxun": + result = BuildImage.open( + await build_zhenxun_image(session, group_id, is_detail) + ) + case _: + result = await build_normal_image(group_id, is_detail) + if group_id: + save_path = GROUP_HELP_PATH / f"{group_id}_{is_detail}.png" + elif is_detail: + save_path = SIMPLE_DETAIL_HELP_IMAGE + else: + save_path = SIMPLE_HELP_IMAGE + await result.save(save_path) + return save_path -async def get_plugin_help(name: str, is_superuser: bool) -> str | BuildImage: +async def get_user_allow_help(user_id: str) -> list[PluginType]: + """获取用户可访问插件类型列表 + + 参数: + user_id: 用户id + + 返回: + list[PluginType]: 插件类型列表 + """ + type_list = [PluginType.NORMAL, PluginType.DEPENDANT] + for level in await LevelUser.filter(user_id=user_id).values_list( + "user_level", flat=True + ): + if level > 0: # type: ignore + type_list.extend((PluginType.ADMIN, PluginType.SUPER_AND_ADMIN)) + break + if user_id in driver.config.superusers: + type_list.append(PluginType.SUPERUSER) + return type_list + + +async def get_plugin_help( + user_id: str, name: str, is_superuser: bool +) -> str | BuildImage: """获取功能的帮助信息 参数: + user_id: 用户id name: 插件名称或id is_superuser: 是否为超级用户 """ + type_list = await get_user_allow_help(user_id) if name.isdigit(): - plugin = await PluginInfo.get_or_none(id=int(name), load_status=True) + plugin = await PluginInfo.get_or_none(id=int(name), plugin_type__in=type_list) else: - plugin = await PluginInfo.get_or_none(name__iexact=name, load_status=True) + plugin = await PluginInfo.get_or_none( + name__iexact=name, load_status=True, plugin_type__in=type_list + ) if plugin: _plugin = nonebot.get_plugin_by_module_name(plugin.module_path) if _plugin and _plugin.metadata: diff --git a/zhenxun/builtin_plugins/help/_utils.py b/zhenxun/builtin_plugins/help/_utils.py index 3f4f891d..6c382c7d 100644 --- a/zhenxun/builtin_plugins/help/_utils.py +++ b/zhenxun/builtin_plugins/help/_utils.py @@ -1,250 +1,49 @@ -import os -import random -from typing import Dict +from collections.abc import Callable -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH, TEMPLATE_PATH from zhenxun.models.group_console import GroupConsole from zhenxun.models.plugin_info import PluginInfo -from zhenxun.utils.enum import BlockType, PluginType -from zhenxun.utils.image_utils import BuildImage, build_sort_image, group_image - -from ._config import Item - -GROUP_HELP_PATH = DATA_PATH / "group_help" -GROUP_HELP_PATH.mkdir(exist_ok=True, parents=True) -for f in os.listdir(GROUP_HELP_PATH): - group_help_image = GROUP_HELP_PATH / f - group_help_image.unlink() - -BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help" - -LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo" +from zhenxun.utils.enum import PluginType -class HelpImageBuild: - def __init__(self): - self._data: list[PluginInfo] = [] - self._sort_data: Dict[str, list[PluginInfo]] = {} - self._image_list = [] - self.icon2str = { - "normal": "fa fa-cog", - "原神相关": "fa fa-circle-o", - "常规插件": "fa fa-cubes", - "联系管理员": "fa fa-envelope-o", - "抽卡相关": "fa fa-credit-card-alt", - "来点好康的": "fa fa-picture-o", - "数据统计": "fa fa-bar-chart", - "一些工具": "fa fa-shopping-cart", - "商店": "fa fa-shopping-cart", - "其它": "fa fa-tags", - "群内小游戏": "fa fa-gamepad", - } +async def sort_type() -> dict[str, list[PluginInfo]]: + """ + 对插件按照菜单类型分类 + """ + data = await PluginInfo.filter( + menu_type__not="", + load_status=True, + plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], + is_show=True, + ) + sort_data = {} + for plugin in data: + menu_type = plugin.menu_type or "normal" + if menu_type == "normal": + menu_type = "功能" + if not sort_data.get(menu_type): + sort_data[menu_type] = [] + sort_data[menu_type].append(plugin) + return sort_data - async def sort_type(self): - """ - 对插件按照菜单类型分类 - """ - if not self._data: - self._data = await PluginInfo.filter( - menu_type__not="", - load_status=True, - plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], - ) - if not self._sort_data: - for plugin in self._data: - menu_type = plugin.menu_type or "normal" - if menu_type == "normal": - menu_type = "功能" - if not self._sort_data.get(menu_type): - self._sort_data[menu_type] = [] - self._sort_data[menu_type].append(plugin) - async def build_image(self, group_id: str | None): - if group_id: - help_image = GROUP_HELP_PATH / f"{group_id}.png" - else: - help_image = IMAGE_PATH / f"SIMPLE_HELP.png" - build_type = Config.get_config("help", "TYPE") - if build_type == "HTML": - byt = await self.build_html_image(group_id) - with open(help_image, "wb") as f: - f.write(byt) - else: - img = await self.build_pil_image(group_id) - await img.save(help_image) +async def classify_plugin( + group_id: str | None, is_detail: bool, handle: Callable +) -> dict[str, list]: + """对插件进行分类并判断状态 - async def build_html_image(self, group_id: str | None) -> bytes: - from nonebot_plugin_htmlrender import template_to_pic + 参数: + group_id: 群组id + is_detail: 是否详细帮助 - await self.sort_type() - classify = {} - for menu in self._sort_data: - for plugin in self._sort_data[menu]: - sta = 0 - if not plugin.status: - if group_id and plugin.block_type in [ - BlockType.ALL, - BlockType.GROUP, - ]: - sta = 2 - if not group_id and plugin.block_type in [ - BlockType.ALL, - BlockType.PRIVATE, - ]: - sta = 2 - if group_id and ( - group := await GroupConsole.get_or_none(group_id=group_id) - ): - if f"{plugin.module}:super," in group.block_plugin: - sta = 2 - if f"{plugin.module}," in group.block_plugin: - sta = 1 - if classify.get(menu): - classify[menu].append(Item(plugin_name=plugin.name, sta=sta)) - else: - classify[menu] = [Item(plugin_name=plugin.name, sta=sta)] - max_len = 0 - flag_index = -1 - max_data = None - plugin_list = [] - for index, plu in enumerate(classify.keys()): - if plu in self.icon2str.keys(): - icon = self.icon2str[plu] - else: - icon = "fa fa-pencil-square-o" - logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH)) - data = { - "name": plu if plu != "normal" else "功能", - "items": classify[plu], - "icon": icon, - "logo": str(logo.absolute()), - } - if len(classify[plu]) > max_len: - max_len = len(classify[plu]) - flag_index = index - max_data = data - plugin_list.append(data) - del plugin_list[flag_index] - plugin_list.insert(0, max_data) - pic = await template_to_pic( - template_path=str((TEMPLATE_PATH / "menu").absolute()), - template_name="zhenxun_menu.html", - templates={"plugin_list": plugin_list}, - pages={ - "viewport": {"width": 1903, "height": 975}, - "base_url": f"file://{TEMPLATE_PATH}", - }, - wait=2, - ) - return pic - - async def build_pil_image(self, group_id: str | None) -> BuildImage: - """构造帮助图片 - - 参数: - group_id: 群号 - """ - self._image_list = [] - await self.sort_type() - font_size = 24 - build_type = Config.get_config("help", "TYPE") - font = BuildImage.load_font("HYWenHei-85W.ttf", 20) - for idx, menu_type in enumerate(self._sort_data.keys()): - plugin_list = self._sort_data[menu_type] - wh_list = [ - BuildImage.get_text_size(f"{x.id}.{x.name}", font) for x in plugin_list - ] - wh_list.append(BuildImage.get_text_size(menu_type, font)) - # sum_height = sum([x[1] for x in wh_list]) - if build_type == "VV": - sum_height = 50 * len(plugin_list) + 10 - else: - sum_height = (font_size + 6) * len(plugin_list) + 10 - max_width = max([x[0] for x in wh_list]) + 30 - bk = BuildImage( - max_width + 40, - sum_height + 50, - font_size=30, - color="#a7d1fc", - font="CJGaoDeGuo.otf", - ) - title_size = bk.getsize(menu_type) - max_width = max_width if max_width > title_size[0] else title_size[0] - B = BuildImage( - max_width + 40, - sum_height, - font_size=font_size, - color="white" if not idx % 2 else "black", - ) - curr_h = 10 - group = await GroupConsole.get_or_none(group_id=group_id) - for i, plugin in enumerate(plugin_list): - text_color = (255, 255, 255) if idx % 2 else (0, 0, 0) - if group and f"{plugin.module}," in group.block_plugin: - text_color = (252, 75, 13) - pos = None - # 禁用状态划线 - if plugin.block_type in [BlockType.ALL, BlockType.GROUP] or ( - group and f"super:{plugin.module}," in group.block_plugin - ): - w = curr_h + int(B.getsize(plugin.name)[1] / 2) + 2 - pos = ( - 7, - w, - B.getsize(plugin.name)[0] + 35, - w, - ) - if build_type == "VV": - name_image = await self.build_name_image( # type: ignore - max_width, - plugin.name, - "black" if not idx % 2 else "white", - text_color, - pos, - ) - await B.paste(name_image, (0, curr_h), center_type="width") - curr_h += name_image.h + 5 - else: - await B.text((10, curr_h), f"{plugin.id}.{plugin.name}", text_color) - if pos: - await B.line(pos, (236, 66, 7), 3) - curr_h += font_size + 5 - await bk.text((0, 14), menu_type, center_type="width") - await bk.paste(B, (0, 50)) - await bk.transparent(2) - # await bk.acircle_corner(point_list=['lt', 'rt']) - self._image_list.append(bk) - image_group, h = group_image(self._image_list) - - async def _a(image: BuildImage): - await image.filter("GaussianBlur", 5) - - B = await build_sort_image( - image_group, - h, - background_path=BACKGROUND_PATH, - background_handle=_a, - ) - w = 10 - h = 10 - for msg in [ - "目前支持的功能列表:", - "可以通过 ‘帮助 [功能名称或功能Id]’ 来获取对应功能的使用方法", - ]: - text = await BuildImage.build_text_image(msg, "HYWenHei-85W.ttf", 24) - await B.paste(text, (w, h)) - h += 50 - if msg == "目前支持的功能列表:": - w += 50 - text = await BuildImage.build_text_image( - "注: 红字代表功能被群管理员禁用,红线代表功能正在维护", - "HYWenHei-85W.ttf", - 24, - (231, 74, 57), - ) - await B.paste( - text, - (300, 10), - ) - return B + 返回: + dict[str, list[Item]]: 分类插件数据 + """ + sort_data = await sort_type() + classify: dict[str, list] = {} + group = await GroupConsole.get_or_none(group_id=group_id) if group_id else None + for menu, value in sort_data.items(): + for plugin in value: + if not classify.get(menu): + classify[menu] = [] + classify[menu].append(handle(plugin, group, is_detail)) + return classify diff --git a/zhenxun/builtin_plugins/superuser/power/__ini__.py b/zhenxun/builtin_plugins/help/detail_help.py similarity index 100% rename from zhenxun/builtin_plugins/superuser/power/__ini__.py rename to zhenxun/builtin_plugins/help/detail_help.py diff --git a/zhenxun/builtin_plugins/help/html_help.py b/zhenxun/builtin_plugins/help/html_help.py new file mode 100644 index 00000000..1815b99a --- /dev/null +++ b/zhenxun/builtin_plugins/help/html_help.py @@ -0,0 +1,140 @@ +import os +import random + +from nonebot_plugin_htmlrender import template_to_pic +from pydantic import BaseModel + +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.enum import BlockType + +from ._utils import classify_plugin + +LOGO_PATH = TEMPLATE_PATH / "menu" / "res" / "logo" + + +class Item(BaseModel): + plugin_name: str + """插件名称""" + sta: int + """插件状态""" + + +class PluginList(BaseModel): + plugin_type: str + """菜单名称""" + icon: str + """图标""" + logo: str + """logo""" + items: list[Item] + """插件列表""" + + +ICON2STR = { + "normal": "fa fa-cog", + "原神相关": "fa fa-circle-o", + "常规插件": "fa fa-cubes", + "联系管理员": "fa fa-envelope-o", + "抽卡相关": "fa fa-credit-card-alt", + "来点好康的": "fa fa-picture-o", + "数据统计": "fa fa-bar-chart", + "一些工具": "fa fa-shopping-cart", + "商店": "fa fa-shopping-cart", + "其它": "fa fa-tags", + "群内小游戏": "fa fa-gamepad", +} + + +def __handle_item( + plugin: PluginInfo, group: GroupConsole | None, is_detail: bool +) -> Item: + """构造Item + + 参数: + plugin: PluginInfo + group: 群组 + is_detail: 是否详细 + + 返回: + Item: Item + """ + sta = 0 + if not plugin.status: + if group and plugin.block_type in [ + BlockType.ALL, + BlockType.GROUP, + ]: + sta = 2 + if not group and plugin.block_type in [ + BlockType.ALL, + BlockType.PRIVATE, + ]: + sta = 2 + if group: + if f"{plugin.module}:super," in group.block_plugin: + sta = 2 + if f"{plugin.module}," in group.block_plugin: + sta = 1 + return Item(plugin_name=plugin.name, sta=sta) + + +def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]: + """构建前端插件数据 + + 参数: + classify: 插件数据 + + 返回: + list[dict[str, str]]: 前端插件数据 + """ + lengths = [len(classify[c]) for c in classify] + index = lengths.index(max(lengths)) + menu_key = list(classify.keys())[index] + max_data = classify[menu_key] + del classify[menu_key] + plugin_list = [] + for menu_type in classify: + icon = "fa fa-pencil-square-o" + if menu_type in ICON2STR.keys(): + icon = ICON2STR[menu_type] + logo = LOGO_PATH / random.choice(os.listdir(LOGO_PATH)) + data = { + "name": menu_type if menu_type != "normal" else "功能", + "items": classify[menu_type], + "icon": icon, + "logo": str(logo.absolute()), + } + plugin_list.append(data) + plugin_list.insert( + 0, + { + "name": menu_key if menu_key != "normal" else "功能", + "items": max_data, + "icon": "fa fa-pencil-square-o", + "logo": str((LOGO_PATH / random.choice(os.listdir(LOGO_PATH))).absolute()), + }, + ) + return plugin_list + + +async def build_html_image(group_id: str | None, is_detail: bool) -> bytes: + """构造HTML帮助图片 + + 参数: + group_id: 群号 + is_detail: 是否详细帮助 + """ + classify = await classify_plugin(group_id, is_detail, __handle_item) + plugin_list = build_plugin_data(classify) + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "menu").absolute()), + template_name="zhenxun_menu.html", + templates={"plugin_list": plugin_list}, + pages={ + "viewport": {"width": 1903, "height": 975}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/help/normal_help.py b/zhenxun/builtin_plugins/help/normal_help.py new file mode 100644 index 00000000..0ef9aa89 --- /dev/null +++ b/zhenxun/builtin_plugins/help/normal_help.py @@ -0,0 +1,100 @@ +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.group_console import GroupConsole +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.enum import BlockType +from zhenxun.utils.image_utils import build_sort_image, group_image + +from ._utils import sort_type + +BACKGROUND_PATH = IMAGE_PATH / "background" / "help" / "simple_help" + + +async def build_normal_image(group_id: str | None, is_detail: bool) -> BuildImage: + """构造PIL帮助图片 + + 参数: + group_id: 群号 + is_detail: 详细帮助 + """ + image_list = [] + font_size = 24 + font = BuildImage.load_font("HYWenHei-85W.ttf", 20) + sort_data = await sort_type() + for idx, menu_type in enumerate(sort_data): + plugin_list = sort_data[menu_type] + """拿到最大宽度和结算高度""" + wh_list = [ + BuildImage.get_text_size(f"{x.id}.{x.name}", font) for x in plugin_list + ] + wh_list.append(BuildImage.get_text_size(menu_type, font)) + sum_height = (font_size + 6) * len(plugin_list) + 10 + max_width = max(x[0] for x in wh_list) + 30 + bk = BuildImage( + max_width + 40, + sum_height + 50, + font_size=30, + color="#a7d1fc", + font="CJGaoDeGuo.otf", + ) + title_size = bk.getsize(menu_type) + max_width = max_width if max_width > title_size[0] else title_size[0] + row = BuildImage( + max_width + 40, + sum_height, + font_size=font_size, + color="black" if idx % 2 else "white", + ) + curr_h = 10 + group = await GroupConsole.get_or_none(group_id=group_id) + for _, plugin in enumerate(plugin_list): + text_color = (255, 255, 255) if idx % 2 else (0, 0, 0) + if group and f"{plugin.module}," in group.block_plugin: + text_color = (252, 75, 13) + pos = None + # 禁用状态划线 + if plugin.block_type in [BlockType.ALL, BlockType.GROUP] or ( + group and f"super:{plugin.module}," in group.block_plugin + ): + w = curr_h + int(row.getsize(plugin.name)[1] / 2) + 2 + line_width = row.getsize(plugin.name)[0] + 35 + pos = (7, w, line_width, w) + await row.text((10, curr_h), f"{plugin.id}.{plugin.name}", text_color) + if pos: + await row.line(pos, (236, 66, 7), 3) + curr_h += font_size + 5 + await bk.text((0, 14), menu_type, center_type="width") + await bk.paste(row, (0, 50)) + await bk.transparent(2) + image_list.append(bk) + image_group, h = group_image(image_list) + + async def _a(image: BuildImage): + await image.filter("GaussianBlur", 5) + + result = await build_sort_image( + image_group, + h, + background_path=BACKGROUND_PATH, + background_handle=_a, + ) + width, height = 10, 10 + for s in [ + "目前支持的功能列表:", + "可以通过 ‘帮助 [功能名称或功能Id]’ 来获取对应功能的使用方法", + ]: + text = await BuildImage.build_text_image(s, "HYWenHei-85W.ttf", 24) + await result.paste(text, (width, height)) + height += 50 + if s == "目前支持的功能列表:": + width += 50 + text = await BuildImage.build_text_image( + "注: 红字代表功能被群管理员禁用,红线代表功能正在维护", + "HYWenHei-85W.ttf", + 24, + (231, 74, 57), + ) + await result.paste( + text, + (300, 10), + ) + return result diff --git a/zhenxun/builtin_plugins/help/zhenxun_help.py b/zhenxun/builtin_plugins/help/zhenxun_help.py new file mode 100644 index 00000000..f6d930e6 --- /dev/null +++ b/zhenxun/builtin_plugins/help/zhenxun_help.py @@ -0,0 +1,170 @@ +import nonebot +from nonebot_plugin_htmlrender import template_to_pic +from nonebot_plugin_uninfo import Uninfo +from pydantic import BaseModel + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.configs.utils import PluginExtraData +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.enum import BlockType +from zhenxun.utils.platform import PlatformUtils + +from ._utils import classify_plugin + + +class Item(BaseModel): + plugin_name: str + """插件名称""" + commands: list[str] + """插件命令""" + + +def __handle_item(plugin: PluginInfo, group: GroupConsole | None, is_detail: bool): + """构造Item + + 参数: + plugin: PluginInfo + group: 群组 + + 返回: + Item: Item + """ + if not plugin.status: + if plugin.block_type == BlockType.ALL: + plugin.name = f"{plugin.name}(不可用)" + elif group and plugin.block_type == BlockType.GROUP: + plugin.name = f"{plugin.name}(不可用)" + elif not group and plugin.block_type == BlockType.PRIVATE: + plugin.name = f"{plugin.name}(不可用)" + elif group and f"{plugin.module}," in group.block_plugin: + plugin.name = f"{plugin.name}(不可用)" + commands = [] + nb_plugin = nonebot.get_plugin_by_module_name(plugin.module_path) + if is_detail and nb_plugin and nb_plugin.metadata and nb_plugin.metadata.extra: + extra_data = PluginExtraData(**nb_plugin.metadata.extra) + commands = [cmd.command for cmd in extra_data.commands] + return Item(plugin_name=f"{plugin.id}-{plugin.name}", commands=commands) + + +def build_plugin_data(classify: dict[str, list[Item]]) -> list[dict[str, str]]: + """构建前端插件数据 + + 参数: + classify: 插件数据 + + 返回: + list[dict[str, str]]: 前端插件数据 + """ + classify = dict(sorted(classify.items(), key=lambda x: len(x[1]), reverse=True)) + menu_key = next(iter(classify.keys())) + max_data = classify[menu_key] + del classify[menu_key] + plugin_list = [ + { + "name": "主要功能" if menu in ["normal", "功能"] else menu, + "items": value, + } + for menu, value in classify.items() + ] + plugin_list = build_line_data(plugin_list) + plugin_list.insert( + 0, + build_plugin_line( + menu_key if menu_key not in ["normal", "功能"] else "主要功能", + max_data, + 30, + 100, + True, + ), + ) + return plugin_list + + +def build_plugin_line( + name: str, items: list, left: int, width: int | None = None, is_max: bool = False +) -> dict: + """构造插件行数据 + + 参数: + name: 菜单名称 + items: 插件名称列表 + left: 左边距 + width: 总插件长度. + is_max: 是否为最大长度的插件菜单 + + 返回: + dict: 插件数据 + """ + _plugins = [] + width = width or 50 + if len(items) // 2 > 6 or is_max: + width = 100 + plugin_list1 = [] + plugin_list2 = [] + for i in range(len(items)): + if i % 2: + plugin_list1.append(items[i]) + else: + plugin_list2.append(items[i]) + _plugins = [(30, 50, plugin_list1), (0, 50, plugin_list2)] + else: + _plugins = [(left, 100, items)] + return {"name": name, "items": _plugins, "width": width} + + +def build_line_data(plugin_list: list[dict]) -> list[dict]: + """构造插件数据 + + 参数: + plugin_list: 插件列表 + + 返回: + list[dict]: 插件数据 + """ + left = 30 + data = [] + for plugin in plugin_list: + data.append(build_plugin_line(plugin["name"], plugin["items"], left)) + if len(plugin["items"]) // 2 <= 6: + left = 15 if left == 30 else 30 + return data + + +async def build_zhenxun_image( + session: Uninfo, group_id: str | None, is_detail: bool +) -> bytes: + """构造真寻帮助图片 + + 参数: + bot_id: bot_id + group_id: 群号 + is_detail: 是否详细帮助 + """ + classify = await classify_plugin(group_id, is_detail, __handle_item) + plugin_list = build_plugin_data(classify) + platform = PlatformUtils.get_platform(session) + bot_id = BotConfig.get_qbot_uid(session.self_id) or session.self_id + bot_ava = PlatformUtils.get_user_avatar_url(bot_id, platform) + width = int(637 * 1.5) if is_detail else 637 + title_font = int(53 * 1.5) if is_detail else 53 + tip_font = int(19 * 1.5) if is_detail else 19 + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "ss_menu").absolute()), + template_name="main.html", + templates={ + "data": { + "plugin_list": plugin_list, + "ava": bot_ava, + "width": width, + "font_size": (title_font, tip_font), + "is_detail": is_detail, + } + }, + pages={ + "viewport": {"width": width, "height": 453}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/help_help.py b/zhenxun/builtin_plugins/help_help.py index d472440b..c315bcd8 100644 --- a/zhenxun/builtin_plugins/help_help.py +++ b/zhenxun/builtin_plugins/help_help.py @@ -18,14 +18,15 @@ from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils __plugin_meta__ = PluginMetadata( - name="功能名称当命令检测", + name="笨蛋检测", description="功能名称当命令检测", - usage=f"""被动""".strip(), + usage="""被动""".strip(), extra=PluginExtraData( author="HibiKier", version="0.1", plugin_type=PluginType.DEPENDANT, - ).dict(), + menu_type="其他", + ).to_dict(), ) _matcher = on_message(rule=to_me(), priority=996, block=False) @@ -47,7 +48,11 @@ async def _(matcher: Matcher, message: UniMsg, session: EventSession): return if text := message.extract_plain_text().strip(): if plugin := await PluginInfo.get_or_none( - name=text, load_status=True, plugin_type=PluginType.NORMAL + name=text, + load_status=True, + plugin_type=PluginType.NORMAL, + block_type__isnull=True, + status=True, ): image = None if _path.exists(): @@ -57,10 +62,12 @@ async def _(matcher: Matcher, message: UniMsg, session: EventSession): if image: message_list.append(image) message_list.append( - f"桀桀桀,预判到会有 '笨蛋' 把功能名称当命令用,特地前来嘲笑!但还是好心来帮帮你啦!\n请at我发送 '帮助{plugin.name}' 或者 '帮助{plugin.id}' 来获取该功能帮助!" + "桀桀桀,预判到会有 '笨蛋' 把功能名称当命令用,特地前来嘲笑!" + f"但还是好心来帮帮你啦!\n请at我发送 '帮助{plugin.name}' 或者" + f" '帮助{plugin.id}' 来获取该功能帮助!" ) logger.info( - f"检测到功能名称当命令使用,已发送帮助信息", "功能帮助", session=session + "检测到功能名称当命令使用,已发送帮助信息", "功能帮助", session=session ) await MessageUtils.build_message(message_list).send(reply_to=True) matcher.stop_propagation() diff --git a/zhenxun/builtin_plugins/hooks/_auth_checker.py b/zhenxun/builtin_plugins/hooks/_auth_checker.py index 4aaa3f19..3a990d89 100644 --- a/zhenxun/builtin_plugins/hooks/_auth_checker.py +++ b/zhenxun/builtin_plugins/hooks/_auth_checker.py @@ -1,3 +1,5 @@ +from typing import ClassVar + from nonebot.adapters import Bot, Event from nonebot.adapters.onebot.v11 import PokeNotifyEvent from nonebot.exception import IgnoredException @@ -8,10 +10,12 @@ from pydantic import BaseModel from tortoise.exceptions import IntegrityError from zhenxun.configs.config import Config +from zhenxun.models.bot_console import BotConsole from zhenxun.models.group_console import GroupConsole from zhenxun.models.level_user import LevelUser from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.plugin_limit import PluginLimit +from zhenxun.models.sign_user import SignUser from zhenxun.models.user_console import UserConsole from zhenxun.services.log import logger from zhenxun.utils.enum import ( @@ -29,7 +33,6 @@ base_config = Config.get("hook") class Limit(BaseModel): - limit: PluginLimit limiter: FreqLimiter | UserBlockLimiter | CountLimiter @@ -38,12 +41,11 @@ class Limit(BaseModel): class LimitManage: + add_module: ClassVar[list] = [] - add_module = [] - - cd_limit: dict[str, Limit] = {} - block_limit: dict[str, Limit] = {} - count_limit: dict[str, Limit] = {} + cd_limit: ClassVar[dict[str, Limit]] = {} + block_limit: ClassVar[dict[str, Limit]] = {} + count_limit: ClassVar[dict[str, Limit]] = {} @classmethod def add_limit(cls, limit: PluginLimit): @@ -85,6 +87,12 @@ class LimitManage: key_type = user_id if group_id and limit.watch_type == LimitWatchType.GROUP: key_type = channel_id or group_id + logger.debug( + f"解除对象: {key_type} 的block限制", + "AuthChecker", + session=user_id, + group_id=group_id, + ) limiter.set_false(key_type) @classmethod @@ -118,7 +126,7 @@ class LimitManage: @classmethod async def __check( cls, - limit_model: Limit, + limit_model: Limit | None, user_id: str, group_id: str | None, channel_id: str | None, @@ -136,33 +144,40 @@ class LimitManage: 异常: IgnoredException: IgnoredException """ - if limit_model: - limit = limit_model.limit - limiter = limit_model.limiter - is_limit = ( - LimitWatchType.ALL - or (group_id and limit.watch_type == LimitWatchType.GROUP) - or (not group_id and limit.watch_type == LimitWatchType.USER) + if not limit_model: + return + limit = limit_model.limit + limiter = limit_model.limiter + is_limit = ( + LimitWatchType.ALL + or (group_id and limit.watch_type == LimitWatchType.GROUP) + or (not group_id and limit.watch_type == LimitWatchType.USER) + ) + key_type = user_id + if group_id and limit.watch_type == LimitWatchType.GROUP: + key_type = channel_id or group_id + if is_limit and not limiter.check(key_type): + if limit.result: + await MessageUtils.build_message(limit.result).send() + logger.debug( + f"{limit.module}({limit.limit_type}) 正在限制中...", + "AuthChecker", + session=session, ) - key_type = user_id - if group_id and limit.watch_type == LimitWatchType.GROUP: - key_type = channel_id or group_id - if is_limit and not limiter.check(key_type): - if limit.result: - await MessageUtils.build_message(limit.result).send() - logger.debug( - f"{limit.module}({limit.limit_type}) 正在限制中...", - "HOOK", - session=session, - ) - raise IgnoredException(f"{limit.module} 正在限制中...") - else: - if isinstance(limiter, FreqLimiter): - limiter.start_cd(key_type) - if isinstance(limiter, UserBlockLimiter): - limiter.set_true(key_type) - if isinstance(limiter, CountLimiter): - limiter.increase(key_type) + raise IgnoredException(f"{limit.module} 正在限制中...") + else: + logger.debug( + f"开始进行限制 {limit.module}({limit.limit_type})...", + "AuthChecker", + session=user_id, + group_id=group_id, + ) + if isinstance(limiter, FreqLimiter): + limiter.start_cd(key_type) + if isinstance(limiter, UserBlockLimiter): + limiter.set_true(key_type) + if isinstance(limiter, CountLimiter): + limiter.increase(key_type) class IsSuperuserException(Exception): @@ -196,9 +211,9 @@ class AuthChecker: return False if plugin.plugin_type == PluginType.DEPENDANT: return False - if not self._flmt_s.check(sid): + if plugin.ignore_prompt: return False - return True + return self._flmt_s.check(sid) async def auth( self, @@ -232,12 +247,18 @@ class AuthChecker: user = await UserConsole.get_user(user_id, session.platform) except IntegrityError as e: logger.debug( - "重复创建用户,已跳过全选该次权限...", "HOOK", session=session, e=e + "重复创建用户,已跳过该次权限...", + "AuthChecker", + session=session, + e=e, ) return if plugin := await PluginInfo.get_or_none(module_path=module_path): if plugin.plugin_type == PluginType.HIDDEN: - logger.debug("插件为HIDDEN,已跳过...") + logger.debug( + f"插件: {plugin.name}:{plugin.module} " + "为HIDDEN,已跳过权限检查..." + ) return try: cost_gold = await self.auth_cost(user, plugin, session) @@ -247,13 +268,14 @@ class AuthChecker: if not plugin.limit_superuser: cost_gold = 0 raise IsSuperuserException() + await self.auth_bot(plugin, bot.self_id) await self.auth_group(plugin, session, message) await self.auth_admin(plugin, session) await self.auth_plugin(plugin, session, event) await self.auth_limit(plugin, session) except IsSuperuserException: logger.debug( - f"超级用户或被ban跳过权限检测...", "HOOK", session=session + "超级用户或被ban跳过权限检测...", "AuthChecker", session=session ) except IgnoredException: is_ignore = True @@ -277,10 +299,29 @@ class AuthChecker: if u := await UserConsole.get_user(user_id): u.gold = 0 await u.save(update_fields=["gold"]) - logger.debug(f"调用功能花费金币: {cost_gold}", "HOOK", session=session) + logger.debug( + f"调用功能花费金币: {cost_gold}", "AuthChecker", session=session + ) if is_ignore: raise IgnoredException("权限检测 ignore") + async def auth_bot(self, plugin: PluginInfo, bot_id: str): + """机器人权限 + + 参数: + plugin: PluginInfo + bot_id: bot_id + """ + if not await BotConsole.get_bot_status(bot_id): + logger.debug("Bot休眠中阻断权限检测...", "AuthChecker") + raise IgnoredException("BotConsole休眠权限检测 ignore") + if await BotConsole.is_block_plugin(bot_id, plugin.module): + logger.debug( + f"Bot插件 {plugin.name}({plugin.module}) 权限检查结果为关闭...", + "AuthChecker", + ) + raise IgnoredException("BotConsole插件权限检测 ignore") + async def auth_limit(self, plugin: PluginInfo, session: EventSession): """插件限制 @@ -294,9 +335,12 @@ class AuthChecker: if not group_id: group_id = channel_id channel_id = None - limit_list: list[PluginLimit] = await plugin.plugin_limit.all() # type: ignore - for limit in limit_list: - LimitManage.add_limit(limit) + if plugin.module not in LimitManage.add_module: + limit_list: list[PluginLimit] = await plugin.plugin_limit.filter( + status=True + ).all() # type: ignore + for limit in limit_list: + LimitManage.add_limit(limit) if user_id: await LimitManage.check( plugin.module, user_id, group_id, channel_id, session @@ -311,95 +355,111 @@ class AuthChecker: plugin: PluginInfo session: EventSession """ - user_id = session.id1 group_id = session.id3 channel_id = session.id2 - is_poke = isinstance(event, PokeNotifyEvent) if not group_id: group_id = channel_id channel_id = None - if user_id: + if user_id := session.id1: + if plugin.impression > 0: + sign_user = await SignUser.get_user(user_id) + if float(sign_user.impression) < plugin.impression: + if self.is_send_limit_message(plugin, user_id): + self._flmt_s.start_cd(user_id) + await MessageUtils.build_message( + f"好感度不足哦,当前功能需要好感度: {plugin.impression}," + "请继续签到提升好感度吧!" + ).send(reply_to=True) + logger.debug( + f"{plugin.name}({plugin.module}) 用户好感度不足...", + "AuthChecker", + session=session, + ) + raise IgnoredException("好感度不足...") if group_id: sid = group_id or user_id - if await GroupConsole.is_super_block_plugin( - group_id, plugin.module, channel_id + if await GroupConsole.is_superuser_block_plugin( + group_id, plugin.module ): """超级用户群组插件状态""" - if self.is_send_limit_message(plugin, sid) and not is_poke: + if self.is_send_limit_message(plugin, sid): self._flmt_s.start_cd(group_id or user_id) await MessageUtils.build_message( "超级管理员禁用了该群此功能..." ).send(reply_to=True) logger.debug( f"{plugin.name}({plugin.module}) 超级管理员禁用了该群此功能...", - "HOOK", + "AuthChecker", session=session, ) raise IgnoredException("超级管理员禁用了该群此功能...") - if await GroupConsole.is_block_plugin( - group_id, plugin.module, channel_id - ): + if await GroupConsole.is_normal_block_plugin(group_id, plugin.module): """群组插件状态""" - if self.is_send_limit_message(plugin, sid) and not is_poke: + if self.is_send_limit_message(plugin, sid): self._flmt_s.start_cd(group_id or user_id) await MessageUtils.build_message("该群未开启此功能...").send( reply_to=True ) logger.debug( f"{plugin.name}({plugin.module}) 未开启此功能...", - "HOOK", + "AuthChecker", session=session, ) raise IgnoredException("该群未开启此功能...") - if not plugin.status and plugin.block_type == BlockType.GROUP: + if plugin.block_type == BlockType.GROUP: """全局群组禁用""" try: - if self.is_send_limit_message(plugin, sid) and not is_poke: + if self.is_send_limit_message(plugin, sid): self._flmt_c.start_cd(group_id) await MessageUtils.build_message( "该功能在群组中已被禁用..." ).send(reply_to=True) except Exception as e: logger.error( - "auth_plugin 发送消息失败", "HOOK", session=session, e=e + "auth_plugin 发送消息失败", + "AuthChecker", + session=session, + e=e, ) logger.debug( f"{plugin.name}({plugin.module}) 该插件在群组中已被禁用...", - "HOOK", + "AuthChecker", session=session, ) raise IgnoredException("该插件在群组中已被禁用...") else: sid = user_id - if not plugin.status and plugin.block_type == BlockType.PRIVATE: + if plugin.block_type == BlockType.PRIVATE: """全局私聊禁用""" try: - if self.is_send_limit_message(plugin, sid) and not is_poke: + if self.is_send_limit_message(plugin, sid): self._flmt_c.start_cd(user_id) await MessageUtils.build_message( "该功能在私聊中已被禁用..." ).send() except Exception as e: logger.error( - "auth_admin 发送消息失败", "HOOK", session=session, e=e + "auth_admin 发送消息失败", + "AuthChecker", + session=session, + e=e, ) logger.debug( f"{plugin.name}({plugin.module}) 该插件在私聊中已被禁用...", - "HOOK", + "AuthChecker", session=session, ) raise IgnoredException("该插件在私聊中已被禁用...") if not plugin.status and plugin.block_type == BlockType.ALL: """全局状态""" - if group_id: - if await GroupConsole.is_super_group(group_id, channel_id): - raise IsSuperuserException() + if group_id and await GroupConsole.is_super_group(group_id): + raise IsSuperuserException() logger.debug( f"{plugin.name}({plugin.module}) 全局未开启此功能...", - "HOOK", + "AuthChecker", session=session, ) - if self.is_send_limit_message(plugin, sid) and not is_poke: + if self.is_send_limit_message(plugin, sid): self._flmt_s.start_cd(group_id or user_id) await MessageUtils.build_message("全局未开启此功能...").send() raise IgnoredException("全局未开启此功能...") @@ -412,9 +472,8 @@ class AuthChecker: session: EventSession """ user_id = session.id1 - group_id = session.id3 or session.id2 if user_id and plugin.admin_level: - if group_id: + if group_id := session.id3 or session.id2: if not await LevelUser.check_level( user_id, group_id, plugin.admin_level ): @@ -424,35 +483,38 @@ class AuthChecker: await MessageUtils.build_message( [ At(flag="user", target=user_id), - f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}", + f"你的权限不足喔," + f"该功能需要的权限等级: {plugin.admin_level}", ] ).send(reply_to=True) except Exception as e: logger.error( - "auth_admin 发送消息失败", "HOOK", session=session, e=e + "auth_admin 发送消息失败", + "AuthChecker", + session=session, + e=e, ) logger.debug( f"{plugin.name}({plugin.module}) 管理员权限不足...", - "HOOK", + "AuthChecker", session=session, ) raise IgnoredException("管理员权限不足...") - else: - if not await LevelUser.check_level(user_id, None, plugin.admin_level): - try: - await MessageUtils.build_message( - f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}" - ).send() - except Exception as e: - logger.error( - "auth_admin 发送消息失败", "HOOK", session=session, e=e - ) - logger.debug( - f"{plugin.name}({plugin.module}) 管理员权限不足...", - "HOOK", - session=session, + elif not await LevelUser.check_level(user_id, None, plugin.admin_level): + try: + await MessageUtils.build_message( + f"你的权限不足喔,该功能需要的权限等级: {plugin.admin_level}" + ).send() + except Exception as e: + logger.error( + "auth_admin 发送消息失败", "AuthChecker", session=session, e=e ) - raise IgnoredException("权限不足") + logger.debug( + f"{plugin.name}({plugin.module}) 管理员权限不足...", + "AuthChecker", + session=session, + ) + raise IgnoredException("权限不足") async def auth_group( self, plugin: PluginInfo, session: EventSession, message: UniMsg @@ -464,29 +526,40 @@ class AuthChecker: session: EventSession message: UniMsg """ - if group_id := session.id3 or session.id2: - text = message.extract_plain_text() - group = await GroupConsole.get_group(group_id) - if not group: - """群不存在""" - raise IgnoredException("群不存在") - if group.level < 0: - """群权限小于0""" - logger.debug( - f"群黑名单, 群权限-1...", - "HOOK", - session=session, - ) - raise IgnoredException("群黑名单") - if not group.status: - """群休眠""" - if text.strip() != "醒来": - logger.debug( - f"功能总开关关闭状态...", - "HOOK", - session=session, - ) - raise IgnoredException("功能总开关关闭状态") + if not (group_id := session.id3 or session.id2): + return + text = message.extract_plain_text() + group = await GroupConsole.get_group(group_id) + if not group: + """群不存在""" + logger.debug( + "群组信息不存在...", + "AuthChecker", + session=session, + ) + raise IgnoredException("群不存在") + if group.level < 0: + """群权限小于0""" + logger.debug( + "群黑名单, 群权限-1...", + "AuthChecker", + session=session, + ) + raise IgnoredException("群黑名单") + if not group.status: + """群休眠""" + if text.strip() != "醒来": + logger.debug("群休眠状态...", "AuthChecker", session=session) + raise IgnoredException("群休眠状态") + if plugin.level > group.level: + """插件等级大于群等级""" + logger.debug( + f"{plugin.name}({plugin.module}) 群等级限制.." + f"该功能需要的群等级: {plugin.level}..", + "AuthChecker", + session=session, + ) + raise IgnoredException(f"{plugin.name}({plugin.module}) 群等级限制...") async def auth_cost( self, user: UserConsole, plugin: PluginInfo, session: EventSession @@ -508,10 +581,13 @@ class AuthChecker: f"金币不足..该功能需要{plugin.cost_gold}金币.." ).send() except Exception as e: - logger.error("auth_cost 发送消息失败", "HOOK", session=session, e=e) + logger.error( + "auth_cost 发送消息失败", "AuthChecker", session=session, e=e + ) logger.debug( - f"{plugin.name}({plugin.module}) 金币限制..该功能需要{plugin.cost_gold}金币..", - "HOOK", + f"{plugin.name}({plugin.module}) 金币限制.." + f"该功能需要{plugin.cost_gold}金币..", + "AuthChecker", session=session, ) raise IgnoredException(f"{plugin.name}({plugin.module}) 金币限制...") diff --git a/zhenxun/builtin_plugins/hooks/auth_hook.py b/zhenxun/builtin_plugins/hooks/auth_hook.py index 4f030d8c..0ccca75c 100644 --- a/zhenxun/builtin_plugins/hooks/auth_hook.py +++ b/zhenxun/builtin_plugins/hooks/auth_hook.py @@ -1,6 +1,4 @@ -from typing import Optional - -from nonebot.adapters.onebot.v11 import Bot, Event, MessageEvent +from nonebot.adapters.onebot.v11 import Bot, Event from nonebot.matcher import Matcher from nonebot.message import run_postprocessor, run_preprocessor from nonebot_plugin_alconna import UniMsg @@ -27,7 +25,7 @@ async def _( @run_postprocessor async def _( matcher: Matcher, - exception: Optional[Exception], + exception: Exception | None, bot: Bot, event: Event, session: EventSession, diff --git a/zhenxun/builtin_plugins/hooks/ban_hook.py b/zhenxun/builtin_plugins/hooks/ban_hook.py index b4923eff..d9030527 100644 --- a/zhenxun/builtin_plugins/hooks/ban_hook.py +++ b/zhenxun/builtin_plugins/hooks/ban_hook.py @@ -8,6 +8,8 @@ from nonebot_plugin_session import EventSession from zhenxun.configs.config import Config from zhenxun.models.ban_console import BanConsole +from zhenxun.models.group_console import GroupConsole +from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils from zhenxun.utils.utils import FreqLimiter @@ -30,7 +32,7 @@ async def _( if plugin := matcher.plugin: if metadata := plugin.metadata: extra = metadata.extra - if extra.get("plugin_type") == PluginType.HIDDEN: + if extra.get("plugin_type") in [PluginType.HIDDEN]: return user_id = session.id1 group_id = session.id3 or session.id2 @@ -38,7 +40,12 @@ async def _( if user_id in bot.config.superusers: return if await BanConsole.is_ban(None, group_id): + logger.debug("群组处于黑名单中...", "ban_hook") raise IgnoredException("群组处于黑名单中...") + if g := await GroupConsole.get_group(group_id): + if g.level < 0: + logger.debug("群黑名单, 群权限-1...", "ban_hook") + raise IgnoredException("群黑名单, 群权限-1..") if user_id: ban_result = Config.get_config("hook", "BAN_RESULT") if user_id in bot.config.superusers: @@ -50,16 +57,16 @@ async def _( else: time = abs(int(time)) if time < 60: - time_str = str(time) + " 秒" + time_str = f"{time!s} 秒" else: minute = int(time / 60) if minute > 60: - hours = int(minute / 60) - minute = minute % 60 + hours = minute // 60 + minute %= 60 time_str = f"{hours} 小时 {minute}分钟" else: time_str = f"{minute} 分钟" - if ban_result and _flmt.check(user_id): + if time != -1 and ban_result and _flmt.check(user_id): _flmt.start_cd(user_id) await MessageUtils.build_message( [ @@ -67,4 +74,5 @@ async def _( f"{ban_result}\n在..在 {time_str} 后才会理你喔", ] ).send() + logger.debug("用户处于黑名单中...", "ban_hook") raise IgnoredException("用户处于黑名单中...") diff --git a/zhenxun/builtin_plugins/hooks/call_hook.py b/zhenxun/builtin_plugins/hooks/call_hook.py new file mode 100644 index 00000000..2ff4d39c --- /dev/null +++ b/zhenxun/builtin_plugins/hooks/call_hook.py @@ -0,0 +1,23 @@ +from typing import Any + +from nonebot.adapters import Bot + +from zhenxun.services.log import logger +from zhenxun.utils.manager.message_manager import MessageManager + + +@Bot.on_called_api +async def handle_api_result( + bot: Bot, exception: Exception | None, api: str, data: dict[str, Any], result: Any +): + if not exception and api == "send_msg": + try: + if (uid := data.get("user_id")) and (msg_id := result.get("message_id")): + MessageManager.add(str(uid), str(msg_id)) + logger.debug( + f"收集消息id,user_id: {uid}, msg_id: {msg_id}", "msg_hook" + ) + except Exception as e: + logger.warning( + f"收集消息id发生错误...data: {data}, result: {result}", "msg_hook", e=e + ) diff --git a/zhenxun/builtin_plugins/hooks/chkdsk_hook.py b/zhenxun/builtin_plugins/hooks/chkdsk_hook.py index 51246515..ec5ccfed 100644 --- a/zhenxun/builtin_plugins/hooks/chkdsk_hook.py +++ b/zhenxun/builtin_plugins/hooks/chkdsk_hook.py @@ -1,6 +1,7 @@ -import time from collections import defaultdict +import time +from nonebot.adapters import Event from nonebot.adapters.onebot.v11 import Bot from nonebot.exception import IgnoredException from nonebot.matcher import Matcher @@ -35,12 +36,12 @@ class BanCheckLimiter: self.default_check_time = default_check_time self.default_count = default_count - def add(self, key: str | int | float): + def add(self, key: str | float): if self.mint[key] == 1: self.mtime[key] = time.time() self.mint[key] += 1 - def check(self, key: str | int | float) -> bool: + def check(self, key: str | float) -> bool: if time.time() - self.mtime[key] > self.default_check_time: self.mtime[key] = time.time() self.mint[key] = 0 @@ -63,16 +64,25 @@ _blmt = BanCheckLimiter( # 恶意触发命令检测 @run_preprocessor -async def _(matcher: Matcher, bot: Bot, session: EventSession, state: T_State): +async def _( + matcher: Matcher, bot: Bot, session: EventSession, state: T_State, event: Event +): module = None if plugin := matcher.plugin: module = plugin.module_name if metadata := plugin.metadata: extra = metadata.extra - if extra.get("plugin_type") in [PluginType.HIDDEN, PluginType.DEPENDANT]: + if extra.get("plugin_type") in [ + PluginType.HIDDEN, + PluginType.DEPENDANT, + PluginType.ADMIN, + PluginType.SUPERUSER, + ]: return else: return + if matcher.type == "notice": + return user_id = session.id1 group_id = session.id3 or session.id2 malicious_ban_time = Config.get_config("hook", "MALICIOUS_BAN_TIME") @@ -92,7 +102,7 @@ async def _(matcher: Matcher, bot: Bot, session: EventSession, state: T_State): await MessageUtils.build_message( [ At(flag="user", target=user_id), - f"检测到恶意触发命令,您将被封禁 30 分钟", + "检测到恶意触发命令,您将被封禁 30 分钟", ] ).send() logger.debug( diff --git a/zhenxun/builtin_plugins/info/__init__.py b/zhenxun/builtin_plugins/info/__init__.py new file mode 100644 index 00000000..88a19f39 --- /dev/null +++ b/zhenxun/builtin_plugins/info/__init__.py @@ -0,0 +1,60 @@ +from nonebot.adapters import Bot +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Args, Arparma, At, Match, on_alconna +from nonebot_plugin_uninfo import Uninfo +from playwright.async_api import TimeoutError + +from zhenxun.configs.utils import Command, PluginExtraData +from zhenxun.models.group_member_info import GroupInfoUser +from zhenxun.services.log import logger +from zhenxun.utils.depends import UserName +from zhenxun.utils.message import MessageUtils + +from .my_info import get_user_info + +__plugin_meta__ = PluginMetadata( + name="查看信息", + description="查看个人信息", + usage=""" + 查看个人/群组信息 + 指令: + 我的信息 ?[at] + """.strip(), + extra=PluginExtraData( + author="HibiKier", version="0.1", commands=[Command(command="我的信息")] + ).to_dict(), +) + + +_matcher = on_alconna(Alconna("我的信息", Args["at_user?", At]), priority=5, block=True) + + +@_matcher.handle() +async def _( + bot: Bot, + session: Uninfo, + arparma: Arparma, + at_user: Match[At], + nickname: str = UserName(), +): + user_id = session.user.id + if at_user.available and session.group: + user_id = at_user.result.target + if user := await GroupInfoUser.get_or_none( + user_id=user_id, group_id=session.group.id + ): + nickname = user.user_name + else: + nickname = user_id + try: + result = await get_user_info( + session, user_id, session.group.id if session.group else None, nickname + ) + await MessageUtils.build_message(result).send(at_sender=True) + logger.info("获取用户信息", arparma.header_result, session=session) + except TimeoutError as e: + logger.error("获取用户信息超时", arparma.header_result, session=session, e=e) + await MessageUtils.build_message("获取用户信息超时...").finish(reply_to=True) + except Exception as e: + logger.error("获取用户信息失败", arparma.header_result, session=session, e=e) + await MessageUtils.build_message("获取用户信息失败...").finish(reply_to=True) diff --git a/zhenxun/builtin_plugins/info/my_info.py b/zhenxun/builtin_plugins/info/my_info.py new file mode 100644 index 00000000..0621c3d5 --- /dev/null +++ b/zhenxun/builtin_plugins/info/my_info.py @@ -0,0 +1,194 @@ +from datetime import datetime, timedelta +import random + +from nonebot_plugin_htmlrender import template_to_pic +from nonebot_plugin_uninfo import Uninfo +from tortoise.expressions import RawSQL +from tortoise.functions import Count + +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.chat_history import ChatHistory +from zhenxun.models.level_user import LevelUser +from zhenxun.models.sign_user import SignUser +from zhenxun.models.statistics import Statistics +from zhenxun.models.user_console import UserConsole +from zhenxun.utils.platform import PlatformUtils + +RACE = [ + "龙族", + "魅魔", + "森林精灵", + "血精灵", + "暗夜精灵", + "狗头人", + "狼人", + "猫人", + "猪头人", + "骷髅", + "僵尸", + "虫族", + "人类", + "天使", + "恶魔", + "甲壳虫", + "猎猫", + "人鱼", + "哥布林", + "地精", + "泰坦", + "矮人", + "山巨人", + "石巨人", +] + +SEX = ["男", "女", "雌", "雄"] + +OCC = [ + "猎人", + "战士", + "魔法师", + "狂战士", + "魔战士", + "盗贼", + "术士", + "牧师", + "骑士", + "刺客", + "游侠", + "召唤师", + "圣骑士", + "魔使", + "龙骑士", + "赏金猎手", + "吟游诗人", + "德鲁伊", + "祭司", + "符文师", + "狂暴术士", + "萨满", + "裁决者", + "角斗士", +] + +lik2level = { + 400: 8, + 270: 7, + 200: 6, + 140: 5, + 90: 4, + 50: 3, + 25: 2, + 10: 1, + 0: 0, +} + + +def get_level(impression: float) -> int: + """获取好感度等级""" + return next((level for imp, level in lik2level.items() if impression >= imp), 0) + + +async def get_chat_history( + user_id: str, group_id: str | None +) -> tuple[list[str], list[str]]: + """获取用户聊天记录 + + 参数: + user_id: 用户id + group_id: 群id + + 返回: + tuple[list[str], list[str]]: 日期列表, 次数列表 + + """ + now = datetime.now() + filter_date = now - timedelta(days=7, hours=now.hour, minutes=now.minute) + date_list = ( + await ChatHistory.filter( + user_id=user_id, group_id=group_id, create_time__gte=filter_date + ) + .annotate(date=RawSQL("DATE(create_time)"), count=Count("id")) + .group_by("date") + .values("date", "count") + ) + chart_date = [] + count_list = [] + date2cnt = {str(date["date"]): date["count"] for date in date_list} + date = now.date() + for _ in range(7): + if str(date) in date2cnt: + count_list.append(date2cnt[str(date)]) + else: + count_list.append(0) + chart_date.append(str(date)) + date -= timedelta(days=1) + for c in chart_date: + chart_date[chart_date.index(c)] = c[5:] + chart_date.reverse() + count_list.reverse() + return chart_date, count_list + + +async def get_user_info( + session: Uninfo, user_id: str, group_id: str | None, nickname: str +) -> bytes: + """获取用户个人信息 + + 参数: + session: Uninfo + bot: Bot + user_id: 用户id + group_id: 群id + nickname: 用户昵称 + + 返回: + bytes: 图片数据 + """ + platform = PlatformUtils.get_platform(session) or "qq" + ava_url = PlatformUtils.get_user_avatar_url(user_id, platform, session.self_id) + user = await UserConsole.get_user(user_id, platform) + level = await LevelUser.get_user_level(user_id, group_id) + sign_level = 0 + if sign_user := await SignUser.get_or_none(user_id=user_id): + sign_level = get_level(float(sign_user.impression)) + chat_count = await ChatHistory.filter(user_id=user_id, group_id=group_id).count() + stat_count = await Statistics.filter(user_id=user_id, group_id=group_id).count() + select_index = ["" for _ in range(9)] + select_index[sign_level] = "select" + uid = f"{user.uid}".rjust(8, "0") + uid = f"{uid[:4]} {uid[4:]}" + now = datetime.now() + weather = "moon" if now.hour < 6 or now.hour > 19 else "sun" + chart_date, count_list = await get_chat_history(user_id, group_id) + data = { + "date": now.date(), + "weather": weather, + "ava_url": ava_url, + "nickname": nickname, + "title": "勇 者", + "race": random.choice(RACE), + "sex": random.choice(SEX), + "occ": random.choice(OCC), + "uid": uid, + "description": "这是一个传奇的故事," + "人类的赞歌是勇气的赞歌,人类的伟大是勇气的伟译。", + "sign_level": sign_level, + "level": level, + "gold": user.gold, + "prop": len(user.props), + "call": stat_count, + "say": chat_count, + "select_index": select_index, + "chart_date": chart_date, + "count_list": count_list, + } + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "my_info").absolute()), + template_name="main.html", + templates={"data": data}, + pages={ + "viewport": {"width": 1754, "height": 1240}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/init/__init__.py b/zhenxun/builtin_plugins/init/__init__.py index b3bab4db..3d97a47c 100644 --- a/zhenxun/builtin_plugins/init/__init__.py +++ b/zhenxun/builtin_plugins/init/__init__.py @@ -20,24 +20,24 @@ async def _(bot: Bot): 参数: bot: Bot """ - if PlatformUtils.get_platform(bot) == "qq": - logger.debug(f"更新Bot: {bot.self_id} 的群认证...") - group_list, _ = await PlatformUtils.get_group_list(bot) - gid_list = [(g.group_id, g.group_name) for g in group_list] - db_group_list = await GroupConsole.all().values_list("group_id", flat=True) - create_list = [] - update_id = [] - for gid, name in gid_list: - if gid not in db_group_list: - create_list.append( - GroupConsole(group_id=gid, group_name=name, group_flag=1) - ) - else: - update_id.append(gid) - if create_list: - await GroupConsole.bulk_create(create_list, 10) + if PlatformUtils.get_platform(bot) != "qq": + return + logger.debug(f"更新Bot: {bot.self_id} 的群认证...") + group_list, _ = await PlatformUtils.get_group_list(bot) + db_group_list = await GroupConsole.all().values_list("group_id", flat=True) + create_list = [] + update_id = [] + for group in group_list: + if group.group_id not in db_group_list: + group.group_flag = 1 + create_list.append(group) else: - await GroupConsole.filter(group_id__in=update_id).update(group_flag=1) - logger.debug( - f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据,共修改 {len(update_id)} 条数据..." - ) + update_id.append(group.group_id) + if create_list: + await GroupConsole.bulk_create(create_list, 10) + else: + await GroupConsole.filter(group_id__in=update_id).update(group_flag=1) + logger.debug( + f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据," + f"共修改 {len(update_id)} 条数据..." + ) diff --git a/zhenxun/builtin_plugins/init/init_config.py b/zhenxun/builtin_plugins/init/init_config.py index 26534d4d..112d29de 100644 --- a/zhenxun/builtin_plugins/init/init_config.py +++ b/zhenxun/builtin_plugins/init/init_config.py @@ -25,7 +25,7 @@ if old_config_file.exists(): old_config_file.rename(SIMPLE_CONFIG_FILE) -def _handle_config(plugin: Plugin): +def _handle_config(plugin: Plugin, exists_module: list[str]): """处理配置项 参数: @@ -49,9 +49,10 @@ def _handle_config(plugin: Plugin): arg_parser=reg_config.arg_parser, _override=False, ) + exists_module.append(f"{module}:{reg_config.key}".lower()) -def _generate_simple_config(): +def _generate_simple_config(exists_module: list[str]): """ 生成简易配置 @@ -61,6 +62,7 @@ def _generate_simple_config(): # 读取用户配置 _data = {} _tmp_data = {} + exists_module += Config.add_module if SIMPLE_CONFIG_FILE.exists(): _data = _yaml.load(SIMPLE_CONFIG_FILE.open(encoding="utf8")) # 将简易配置文件的数据填充到配置文件 @@ -70,17 +72,19 @@ def _generate_simple_config(): try: if _data.get(module) and k in _data[module].keys(): Config.set_config(module, k, _data[module][k]) - _tmp_data[module][k] = Config.get_config(module, k) + if f"{module}:{k}".lower() in exists_module: + _tmp_data[module][k] = Config.get_config(module, k) except AttributeError as e: - raise AttributeError(f"{e}\n" + "可能为config.yaml配置文件填写不规范") + raise AttributeError(f"{e}\n可能为config.yaml配置文件填写不规范") from e + if not _tmp_data[module]: + _tmp_data.pop(module) Config.save() temp_file = DATA_PATH / "temp_config.yaml" # 重新生成简易配置文件 try: with open(temp_file, "w", encoding="utf8") as wf: - # yaml.dump(_tmp_data, wf, Dumper=yaml.RoundTripDumper, allow_unicode=True) _yaml.dump(_tmp_data, wf) - with open(temp_file, "r", encoding="utf8") as rf: + with open(temp_file, encoding="utf8") as rf: _data = _yaml.load(rf) # 添加注释 for module in _data.keys(): @@ -93,7 +97,7 @@ def _generate_simple_config(): with SIMPLE_CONFIG_FILE.open("w", encoding="utf8") as wf: _yaml.dump(_data, wf) except Exception as e: - logger.error(f"生成简易配置注释错误...", e=e) + logger.error("生成简易配置注释错误...", e=e) if temp_file.exists(): temp_file.unlink() @@ -104,9 +108,10 @@ def _(): 初始化插件数据配置 """ plugins2config_file = DATA_PATH / "configs" / "plugins2config.yaml" + exists_module = [] for plugin in get_loaded_plugins(): if plugin.metadata: - _handle_config(plugin) + _handle_config(plugin, exists_module) if not Config.is_empty(): Config.save() _data: CommentedMap = _yaml.load(plugins2config_file.open(encoding="utf8")) @@ -119,4 +124,4 @@ def _(): # 存完插件基本设置 with plugins2config_file.open("w", encoding="utf8") as wf: _yaml.dump(_data, wf) - _generate_simple_config() + _generate_simple_config(exists_module) diff --git a/zhenxun/builtin_plugins/init/init_plugin.py b/zhenxun/builtin_plugins/init/init_plugin.py index 8a65e27b..dbeddb54 100644 --- a/zhenxun/builtin_plugins/init/init_plugin.py +++ b/zhenxun/builtin_plugins/init/init_plugin.py @@ -1,9 +1,10 @@ +import aiofiles import nonebot -import ujson as json from nonebot import get_loaded_plugins from nonebot.drivers import Driver -from nonebot.plugin import Plugin +from nonebot.plugin import Plugin, PluginMetadata from ruamel.yaml import YAML +import ujson as json from zhenxun.configs.path_config import DATA_PATH from zhenxun.configs.utils import PluginExtraData, PluginSetting @@ -20,6 +21,8 @@ from zhenxun.utils.enum import ( PluginType, ) +from .manager import manager + _yaml = YAML(pure=True) _yaml.allow_unicode = True _yaml.indent = 2 @@ -31,7 +34,6 @@ async def _handle_setting( plugin: Plugin, plugin_list: list[PluginInfo], limit_list: list[PluginLimit], - task_list: list[TaskInfo], ): """处理插件设置 @@ -41,60 +43,56 @@ async def _handle_setting( limit_list: 插件限制列表 """ metadata = plugin.metadata - if metadata: - extra = metadata.extra - extra_data = PluginExtraData(**extra) - logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据") - setting = extra_data.setting or PluginSetting() - if metadata.type == "library": - extra_data.plugin_type = PluginType.HIDDEN - if ( - extra_data.plugin_type - == PluginType.HIDDEN - # and extra_data.plugin_type != "功能" - ): - extra_data.menu_type = "" - plugin_list.append( - PluginInfo( + if not metadata: + if not plugin.sub_plugins: + return + """父插件""" + metadata = PluginMetadata(name=plugin.name, description="", usage="") + extra = metadata.extra + extra_data = PluginExtraData(**extra) + logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据") + setting = extra_data.setting or PluginSetting() + if metadata.type == "library": + extra_data.plugin_type = PluginType.HIDDEN + if extra_data.plugin_type == PluginType.HIDDEN: + extra_data.menu_type = "" + if plugin.sub_plugins: + extra_data.plugin_type = PluginType.PARENT + plugin_list.append( + PluginInfo( + module=plugin.name, + module_path=plugin.module_name, + name=metadata.name, + author=extra_data.author, + version=extra_data.version, + level=setting.level, + default_status=setting.default_status, + limit_superuser=setting.limit_superuser, + menu_type=extra_data.menu_type, + cost_gold=setting.cost_gold, + plugin_type=extra_data.plugin_type, + admin_level=extra_data.admin_level, + is_show=extra_data.is_show, + ignore_prompt=extra_data.ignore_prompt, + parent=(plugin.parent_plugin.module_name if plugin.parent_plugin else None), + impression=setting.impression, + ) + ) + if extra_data.limits: + limit_list.extend( + PluginLimit( module=plugin.name, module_path=plugin.module_name, - name=metadata.name, - author=extra_data.author, - version=extra_data.version, - level=setting.level, - default_status=setting.default_status, - limit_superuser=setting.limit_superuser, - menu_type=extra_data.menu_type, - cost_gold=setting.cost_gold, - plugin_type=extra_data.plugin_type, - admin_level=extra_data.admin_level, + limit_type=limit._type, + watch_type=limit.watch_type, + status=limit.status, + check_type=limit.check_type, + result=limit.result, + cd=getattr(limit, "cd", None), + max_count=getattr(limit, "max_count", None), ) + for limit in extra_data.limits ) - if extra_data.limits: - for limit in extra_data.limits: - limit_list.append( - PluginLimit( - module=plugin.name, - module_path=plugin.module_name, - limit_type=limit._type, - watch_type=limit.watch_type, - status=limit.status, - check_type=limit.check_type, - result=limit.result, - cd=getattr(limit, "cd", None), - max_count=getattr(limit, "max_count", None), - ) - ) - if extra_data.tasks: - for task in extra_data.tasks: - task_list.append( - TaskInfo( - module=task.module, - name=task.name, - status=task.status, - run_time=task.run_time, - ) - ) @driver.on_startup @@ -104,15 +102,13 @@ async def _(): """ plugin_list: list[PluginInfo] = [] limit_list: list[PluginLimit] = [] - task_list: list[TaskInfo] = [] module2id = {} load_plugin = [] if module_list := await PluginInfo.all().values("id", "module_path"): module2id = {m["module_path"]: m["id"] for m in module_list} for plugin in get_loaded_plugins(): load_plugin.append(plugin.module_name) - if plugin.metadata: - await _handle_setting(plugin, plugin_list, limit_list, task_list) + await _handle_setting(plugin, plugin_list, limit_list) create_list = [] update_list = [] for plugin in plugin_list: @@ -127,63 +123,55 @@ async def _(): "version", "admin_level", "plugin_type", + "is_show", ] ) update_list.append(plugin) if create_list: await PluginInfo.bulk_create(create_list, 10) - if update_list: - # TODO: 批量更新无法更新plugin_type: tortoise.exceptions.OperationalError: column "superuser" does not exist - pass - # await PluginInfo.bulk_update( - # update_list, - # ["name", "author", "version", "admin_level", "plugin_type"], - # 10, - # ) - if limit_list: - limit_create = [] - plugins = [] - if module_path_list := [limit.module_path for limit in limit_list]: - plugins = await PluginInfo.filter(module_path__in=module_path_list).all() - if plugins: - for limit in limit_list: - if l := [p for p in plugins if p.module_path == limit.module_path]: - plugin = l[0] - limit_type_list = [ - _limit.limit_type for _limit in await plugin.plugin_limit.all() # type: ignore - ] - if limit.limit_type not in limit_type_list: - limit.plugin = plugin - limit_create.append(limit) - if limit_create: - await PluginLimit.bulk_create(limit_create, 10) - if task_list: - module_dict = { - t[1]: t[0] for t in await TaskInfo.all().values_list("id", "module") - } - create_list = [] - update_list = [] - for task in task_list: - if task.module not in module_dict: - create_list.append(task) - else: - task.id = module_dict[task.module] - update_list.append(task) - if create_list: - await TaskInfo.bulk_create(create_list, 10) - if update_list: - await TaskInfo.bulk_update( - update_list, - ["run_time", "status", "name"], - 10, - ) + # if update_list: + # # TODO: 批量更新无法更新plugin_type: tortoise.exceptions.OperationalError: + # column "superuser" does not exist + # pass + # await PluginInfo.bulk_update( + # update_list, + # ["name", "author", "version", "admin_level", "plugin_type"], + # 10, + # ) + # for limit in limit_list: + # limit_create = [] + # plugins = [] + # if module_path_list := [limit.module_path for limit in limit_list]: + # plugins = await PluginInfo.get_plugins(module_path__in=module_path_list) + # if plugins: + # for limit in limit_list: + # if lmt := [p for p in plugins if p.module_path == limit.module_path]: + # plugin = lmt[0] + # """不在数据库中""" + # limit_type_list = [ + # _limit.limit_type + # for _limit in await plugin.plugin_limit.all() # type: ignore + # ] + # if limit.limit_type not in limit_type_list: + # limit.plugin = plugin + # limit_create.append(limit) + # if limit_create: + # await PluginLimit.bulk_create(limit_create, 10) await data_migration() await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True) await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False) + manager.init() + if limit_list: + for limit in limit_list: + if not manager.exists(limit.module, limit.limit_type): + """不存在,添加""" + manager.add(limit.module, limit) + manager.save_file() + await manager.load_to_db() async def data_migration(): - await limit_migration() + # await limit_migration() await plugin_migration() await group_migration() @@ -195,14 +183,14 @@ async def limit_migration(): count_file = DATA_PATH / "configs" / "plugins2count.yaml" limit_data: dict[str, list[tuple[str, dict]]] = {} if cd_file.exists(): - with open(cd_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(cd_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): for k in data["PluginCdLimit"]: limit_data[k] = [("CD", data["PluginCdLimit"][k])] cd_file.unlink() if block_file.exists(): - with open(block_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(block_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): for k in data["PluginBlockLimit"]: if k in limit_data: limit_data[k].append(("BLOCK", data["PluginBlockLimit"][k])) @@ -210,8 +198,8 @@ async def limit_migration(): limit_data[k] = [("BLOCK", data["PluginBlockLimit"][k])] block_file.unlink() if count_file.exists(): - with open(count_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(count_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): for k in data["PluginCountLimit"]: if k in limit_data: limit_data[k].append(("COUNT", data["PluginCountLimit"][k])) @@ -287,7 +275,8 @@ async def limit_migration(): max_count=_limit.get("max_count"), ) ) - # TODO: 批量错误 tortoise.exceptions.OperationalError: syntax error at or near "ALL" + # TODO: 批量错误 tortoise.exceptions.OperationalError: + # syntax error at or near "ALL" # if update_list: # await PluginLimit.bulk_update( # update_list, @@ -311,8 +300,8 @@ async def plugin_migration(): setting_file = DATA_PATH / "configs" / "plugins2settings.yaml" plugin_file = DATA_PATH / "manager" / "plugins_manager.json" if setting_file.exists(): - with open(setting_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(setting_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): logger.info("开始迁移插件setting数据...") data = data["PluginSettings"] plugins = await PluginInfo.filter(module__in=data.keys()) @@ -342,8 +331,8 @@ async def plugin_migration(): setting_file.unlink() logger.info("迁移插件setting数据完成!") if plugin_file.exists(): - with open(plugin_file, encoding="utf8") as f: - if data := json.load(f): + async with aiofiles.open(plugin_file, encoding="utf8") as f: + if data := json.loads(await f.read()): logger.info("开始迁移插件数据...") plugins = await PluginInfo.filter(module__in=data.keys()) for plugin in plugins: @@ -359,7 +348,8 @@ async def plugin_migration(): block_type = BlockType.GROUP plugin.block_type = block_type await plugin.save(update_fields=["status", "block_type"]) - # TODO: tortoise.exceptions.OperationalError: syntax error at or near "ALL" + # TODO: tortoise.exceptions.OperationalError: syntax error at + # or near "ALL" # await PluginInfo.bulk_update(plugins, ["status", "block_type"], 10) plugin_file.unlink() logger.info("迁移插件数据完成!") @@ -371,22 +361,20 @@ async def group_migration(): """ group_file = DATA_PATH / "manager" / "group_manager.json" if group_file.exists(): - with open(group_file, encoding="utf8") as f: - if data := json.load(f): + async with aiofiles.open(group_file, encoding="utf8") as f: + if data := json.loads(await f.read()): logger.info("开始迁移群组数据...") update_list = [] create_list = [] white_group = data["white_group"] - close_task = data["close_task"] old_group_list: dict = data["group_manager"] - if close_task: + if close_task := data["close_task"]: """全局被动关闭""" await TaskInfo.filter(module__in=close_task).update(status=False) group_list = await GroupConsole.filter( group_id__in=old_group_list.keys() ) - for old_group_id in old_group_list: - old_group = old_group_list[old_group_id] + for old_group_id, old_group in old_group_list.items(): block_plugin = "" block_task = "" status = old_group.get("status", True) diff --git a/zhenxun/builtin_plugins/init/init_task.py b/zhenxun/builtin_plugins/init/init_task.py new file mode 100644 index 00000000..cead7d72 --- /dev/null +++ b/zhenxun/builtin_plugins/init/init_task.py @@ -0,0 +1,164 @@ +import nonebot +from nonebot import get_loaded_plugins +from nonebot.drivers import Driver +from nonebot.plugin import Plugin +from nonebot.utils import is_coroutine_callable +from nonebot_plugin_apscheduler import scheduler + +from zhenxun.configs.utils import PluginExtraData, Task +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger +from zhenxun.utils.common_utils import CommonUtils + +driver: Driver = nonebot.get_driver() + + +async def _handle_setting( + plugin: Plugin, + task_info_list: list[tuple[bool, TaskInfo]], + task_list: list[Task], +): + """处理插件设置 + + 参数: + plugin: Plugin + task_info_list: 被动技能db数据列表 + task_list: 被动技能列表 + """ + metadata = plugin.metadata + if not metadata: + return + extra = metadata.extra + extra_data = PluginExtraData(**extra) + if extra_data.tasks: + task_info_list.extend( + ( + task.create_status, + TaskInfo( + module=task.module, + name=task.name, + status=task.status, + default_status=task.default_status, + ), + ) + for task in extra_data.tasks + ) + task_list.extend(extra_data.tasks) + + +async def update_to_group(create_list: list[tuple[bool, TaskInfo]]): + """根据创建时状态对群组进行被动技能更新 + + 参数: + create_list: 被动技能创建列表 + """ + if blocks := [t[1].module for t in create_list if not t[0]]: + if group_list := await GroupConsole.all(): + for group in group_list: + block_tasks = list( + set(CommonUtils.convert_module_format(group.block_task) + blocks) + ) + group.block_task = CommonUtils.convert_module_format(block_tasks) + await GroupConsole.bulk_update(group_list, ["block_task"], 10) + + +async def to_db( + load_task: list[str], + create_list: list[tuple[bool, TaskInfo]], + update_list: list[TaskInfo], +): + """将被动技能保存至数据库 + + 参数: + load_task: 已加载的被动技能模块 + create_list: 被动技能创建列表 + update_list: 被动技能更新列表 + """ + if create_list: + _create_list = [t[1] for t in create_list] + await TaskInfo.bulk_create(_create_list, 10) + await update_to_group(create_list) + if update_list: + await TaskInfo.bulk_update( + update_list, + ["run_time", "name"], + 10, + ) + if load_task: + await TaskInfo.filter(module__in=load_task).update(load_status=True) + await TaskInfo.filter(module__not_in=load_task).update(load_status=False) + + +async def get_run_task(task: Task, *args, **kwargs): + is_run = False + if task.check: + if is_coroutine_callable(task.check): + if await task.check(*task.check_args): + is_run = True + elif task.check(*task.check_args): + is_run = True + else: + bot = task.check_args[0] + group_id = task.check_args[1] + if not await CommonUtils.task_is_block(bot, task.module, group_id): + is_run = True + if is_run and task.run_func: + if is_coroutine_callable(task.run_func): + await task.run_func(*args, **kwargs) + else: + task.run_func(*args, **kwargs) + + +async def create_schedule(task: Task): + scheduler_model = task.scheduler + if not scheduler_model or not task.run_func: + return + try: + scheduler.add_job( + get_run_task, + scheduler_model.trigger, + run_date=scheduler_model.run_date, + hour=scheduler_model.hour, + minute=scheduler_model.minute, + second=scheduler_model.second, + id=scheduler_model.id, + max_instances=scheduler_model.max_instances, + args=scheduler_model.args, + kwargs=scheduler_model.kwargs, + ) + logger.debug(f"成功动态创建定时任务: {task.name}({task.module})") + except Exception as e: + logger.error(f"动态创建定时任务 {task.name}({task.module}) 失败", e=e) + + +@driver.on_startup +async def _(): + """ + 初始化插件数据配置 + """ + task_list: list[Task] = [] + task_info_list: list[tuple[bool, TaskInfo]] = [] + for plugin in get_loaded_plugins(): + await _handle_setting(plugin, task_info_list, task_list) + if not task_info_list: + await TaskInfo.all().update(load_status=False) + return + module_dict = {t[1]: t[0] for t in await TaskInfo.all().values_list("id", "module")} + load_task = [] + create_list = [] + update_list = [] + for status, task in task_info_list: + if task.module not in module_dict: + create_list.append((status, task)) + else: + task.id = module_dict[task.module] + update_list.append(task) + load_task.append(task.module) + await to_db(load_task, create_list, update_list) + # db_task = await TaskInfo.filter(load_status=True, status=True).values_list( + # "module", flat=True + # ) + # task_list = [t for t in task_list if t.module in db_task] + # for task in task_list: + # create_schedule(task) diff --git a/zhenxun/builtin_plugins/init/manager.py b/zhenxun/builtin_plugins/init/manager.py new file mode 100644 index 00000000..d6ffa223 --- /dev/null +++ b/zhenxun/builtin_plugins/init/manager.py @@ -0,0 +1,421 @@ +from copy import deepcopy + +from ruamel.yaml import YAML + +from zhenxun.configs.path_config import DATA_PATH +from zhenxun.configs.utils import BaseBlock, PluginCdBlock, PluginCountBlock +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.plugin_limit import PluginLimit +from zhenxun.services.log import logger +from zhenxun.utils.enum import BlockType, LimitCheckType, PluginLimitType + +_yaml = YAML(pure=True) +_yaml.indent = 2 +_yaml.allow_unicode = True + + +CD_TEST = """需要cd的功能 +自定义的功能需要cd也可以在此配置 +key:模块名称 +cd:cd 时长(秒) +status:此限制的开关状态 +check_type:'PRIVATE'/'GROUP'/'ALL',限制私聊/群聊/全部 +watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id + 示例:'USER':用户N秒内触发1次,'GROUP':群N秒内触发1次 +result:回复的话,可以添加[at],[uname],[nickname]来对应艾特,用户群名称,昵称系统昵称 +result 为 "" 或 None 时则不回复 +result示例:"[uname]你冲的太快了,[nickname]先生,请稍后再冲[at]" +result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批" + 用户昵称↑ 昵称系统的昵称↑ 艾特用户↑""" + + +BLOCK_TEST = """用户调用阻塞 +即 当用户调用此功能还未结束时 +用发送消息阻止用户重复调用此命令直到该命令结束 +key:模块名称 +status:此限制的开关状态 +check_type:'PRIVATE'/'GROUP'/'ALL',限制私聊/群聊/全部 +watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id + 示例:'USER':阻塞用户,'group':阻塞群聊 +result:回复的话,可以添加[at],[uname],[nickname]来对应艾特,用户群名称,昵称系统昵称 +result 为 "" 或 None 时则不回复 +result示例:"[uname]你冲的太快了,[nickname]先生,请稍后再冲[at]" +result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批" + 用户昵称↑ 昵称系统的昵称↑ 艾特用户↑""" + +COUNT_TEST = """命令每日次数限制 +即 用户/群聊 每日可调用命令的次数 [数据内存存储,重启将会重置] +每日调用直到 00:00 刷新 +key:模块名称 +max_count: 每日调用上限 +status:此限制的开关状态 +watch_type:监听对象,以user_id或group_id作为键来限制,'USER':用户id,'GROUP':群id + 示例:'USER':用户上限,'group':群聊上限 +result:回复的话,可以添加[at],[uname],[nickname]来对应艾特,用户群名称,昵称系统昵称 +result 为 "" 或 None 时则不回复 +result示例:"[uname]你冲的太快了,[nickname]先生,请稍后再冲[at]" +result回复:"老色批你冲的太快了,欧尼酱先生,请稍后再冲@老色批" + 用户昵称↑ 昵称系统的昵称↑ 艾特用户↑""" + + +class Manager: + """ + 插件命令 cd 管理器 + """ + + def __init__(self): + self.cd_file = DATA_PATH / "configs" / "plugins2cd.yaml" + self.block_file = DATA_PATH / "configs" / "plugins2block.yaml" + self.count_file = DATA_PATH / "configs" / "plugins2count.yaml" + self.cd_data = {} + self.block_data = {} + self.count_data = {} + + def add( + self, + module: str, + data: BaseBlock | PluginCdBlock | PluginCountBlock | PluginLimit, + ): + """添加限制""" + if isinstance(data, PluginLimit): + check_type = BlockType.ALL + if LimitCheckType.GROUP == data.check_type: + check_type = BlockType.GROUP + elif LimitCheckType.PRIVATE == data.check_type: + check_type = BlockType.PRIVATE + if data.limit_type == PluginLimitType.CD: + data = PluginCdBlock( + status=data.status, + check_type=check_type, + watch_type=data.watch_type, + result=data.result, + cd=data.cd, + ) + elif data.limit_type == PluginLimitType.BLOCK: + data = BaseBlock( + status=data.status, + check_type=check_type, + watch_type=data.watch_type, + result=data.result, + ) + elif data.limit_type == PluginLimitType.COUNT: + data = PluginCountBlock( + status=data.status, + watch_type=data.watch_type, + result=data.result, + max_count=data.max_count, + ) + if isinstance(data, PluginCdBlock): + self.cd_data[module] = data + elif isinstance(data, PluginCountBlock): + self.count_data[module] = data + elif isinstance(data, BaseBlock): + self.block_data[module] = data + + def exists(self, module: str, type: PluginLimitType): + """是否存在""" + if type == PluginLimitType.CD: + return module in self.cd_data + elif type == PluginLimitType.BLOCK: + return module in self.block_data + elif type == PluginLimitType.COUNT: + return module in self.count_data + + def init(self): + if not self.cd_file.exists(): + self.save_cd_file() + if not self.block_file.exists(): + self.save_block_file() + if not self.count_file.exists(): + self.save_count_file() + self.__load_file() + + def __load_file(self): + self.__load_block_file() + self.__load_cd_file() + self.__load_count_file() + + def save_file(self): + """保存文件""" + self.save_cd_file() + self.save_block_file() + self.save_count_file() + + def save_cd_file(self): + """保存文件""" + self._extracted_from_save_file_3("PluginCdLimit", CD_TEST, self.cd_data) + + def save_block_file(self): + """保存文件""" + self._extracted_from_save_file_3( + "PluginBlockLimit", BLOCK_TEST, self.block_data + ) + + def save_count_file(self): + """保存文件""" + self._extracted_from_save_file_3( + "PluginCountLimit", COUNT_TEST, self.count_data + ) + + def _extracted_from_save_file_3(self, type_: str, after: str, data: dict): + """保存文件 + + 参数: + type_: 类型参数 + after: 备注 + """ + temp_data = deepcopy(data) + if not temp_data: + temp_data = { + "test": { + "status": False, + "check_type": "ALL", + "limit_type": "USER", + "result": "你冲的太快了,请稍后再冲", + } + } + if type_ == "PluginCdLimit": + temp_data["test"]["cd"] = 5 + elif type_ == "PluginCountLimit": + temp_data["test"]["max_count"] = 5 + del temp_data["test"]["check_type"] + else: + for v in temp_data: + temp_data[v] = temp_data[v].to_dict() + if check_type := temp_data[v].get("check_type"): + temp_data[v]["check_type"] = str(check_type) + if watch_type := temp_data[v].get("watch_type"): + temp_data[v]["watch_type"] = str(watch_type) + if type_ == "PluginCountLimit": + del temp_data[v]["check_type"] + file = self.block_file + if type_ == "PluginCdLimit": + file = self.cd_file + elif type_ == "PluginCountLimit": + file = self.count_file + with open(file, "w", encoding="utf8") as f: + _yaml.dump({type_: temp_data}, f) + with open(file, encoding="utf8") as rf: + _data = _yaml.load(rf) + _data.yaml_set_comment_before_after_key(after=after, key=type_) + with open(file, "w", encoding="utf8") as wf: + _yaml.dump(_data, wf) + + def __load_cd_file(self): + self.cd_data: dict[str, PluginCdBlock] = {} + if self.cd_file.exists(): + with open(self.cd_file, encoding="utf8") as f: + temp = _yaml.load(f) + if "PluginCdLimit" in temp.keys(): + for k, v in temp["PluginCdLimit"].items(): + if "." in k: + k = k.split(".")[-1] + self.cd_data[k] = PluginCdBlock.parse_obj(v) + + def __load_block_file(self): + self.block_data: dict[str, BaseBlock] = {} + if self.block_file.exists(): + with open(self.block_file, encoding="utf8") as f: + temp = _yaml.load(f) + if "PluginBlockLimit" in temp.keys(): + for k, v in temp["PluginBlockLimit"].items(): + if "." in k: + k = k.split(".")[-1] + self.block_data[k] = BaseBlock.parse_obj(v) + + def __load_count_file(self): + self.count_data: dict[str, PluginCountBlock] = {} + if self.count_file.exists(): + with open(self.count_file, encoding="utf8") as f: + temp = _yaml.load(f) + if "PluginCountLimit" in temp.keys(): + for k, v in temp["PluginCountLimit"].items(): + if "." in k: + k = k.split(".")[-1] + self.count_data[k] = PluginCountBlock.parse_obj(v) + + def __replace_data( + self, + db_data: PluginLimit | None, + limit: PluginCdBlock | BaseBlock | PluginCountBlock, + ) -> PluginLimit: + """替换数据""" + if not db_data: + db_data = PluginLimit() + db_data.status = limit.status + check_type = LimitCheckType.ALL + if BlockType.GROUP == limit.check_type: + check_type = LimitCheckType.GROUP + elif BlockType.PRIVATE == limit.check_type: + check_type = LimitCheckType.PRIVATE + db_data.check_type = check_type + db_data.watch_type = limit.watch_type + db_data.result = limit.result or "" + return db_data + + def __set_data( + self, + k: str, + db_data: PluginLimit | None, + limit: PluginCdBlock | BaseBlock | PluginCountBlock, + limit_type: PluginLimitType, + module2plugin: dict[str, PluginInfo], + ) -> tuple[PluginLimit, bool]: + """设置数据 + + 参数: + k: 模块名 + db_data: 数据库数据 + limit: 文件数据 + limit_type: 限制类型 + module2plugin: 模块:插件信息 + + 返回: + tuple[PluginLimit, bool]: PluginLimit,是否创建 + """ + if not db_data: + return ( + PluginLimit( + module=k, + module_path=module2plugin[k].module_path, + limit_type=limit_type, + plugin=module2plugin[k], + cd=getattr(limit, "cd", None), + max_count=getattr(limit, "max_count", None), + status=limit.status, + check_type=limit.check_type, + watch_type=limit.watch_type, + result=limit.result, + ), + True, + ) + db_data = self.__replace_data(db_data, limit) + if limit_type == PluginLimitType.CD: + db_data.cd = limit.cd # type: ignore + if limit_type == PluginLimitType.COUNT: + db_data.max_count = limit.max_count # type: ignore + return db_data, False + + def __get_file_data(self, limit_type: PluginLimitType) -> dict: + """获取文件数据 + + 参数: + limit_type: 限制类型 + + 返回: + dict: 文件数据 + """ + if limit_type == PluginLimitType.CD: + return self.cd_data + elif limit_type == PluginLimitType.COUNT: + return self.count_data + else: + return self.block_data + + def __set_db_limits( + self, + db_limits: list[PluginLimit], + module2plugin: dict[str, PluginInfo], + limit_type: PluginLimitType, + ) -> tuple[list[PluginLimit], list[PluginLimit], list[int]]: + """更新cd限制数据 + + 参数: + db_limits: 数据库limits + module2plugin: 模块:插件信息 + + 返回: + tuple[list[PluginLimit], list[PluginLimit]]: 创建列表,更新列表 + """ + update_list = [] + create_list = [] + delete_list = [] + db_type_limits = [ + limit for limit in db_limits if limit.limit_type == limit_type + ] + if data := self.__get_file_data(limit_type): + db_type_limit_modules = [ + (limit.module, limit.id) for limit in db_type_limits + ] + delete_list.extend( + id for module, id in db_type_limit_modules if module not in data.keys() + ) + for k, v in data.items(): + if not module2plugin.get(k): + if k != "test": + logger.warning( + f"插件模块 {k} 未加载,已过滤当前 {v._type} 限制..." + ) + continue + db_data = [limit for limit in db_type_limits if limit.module == k] + db_data, is_create = self.__set_data( + k, db_data[0] if db_data else None, v, limit_type, module2plugin + ) + if is_create: + create_list.append(db_data) + else: + update_list.append(db_data) + else: + delete_list = [limit.id for limit in db_type_limits] + return create_list, update_list, delete_list + + async def __set_all_limit( + self, + ) -> tuple[list[PluginLimit], list[PluginLimit], list[int]]: + """获取所有插件限制数据 + + 返回: + tuple[list[PluginLimit], list[PluginLimit]]: 创建列表,更新列表 + """ + db_limits = await PluginLimit.all() + modules = set( + list(self.cd_data.keys()) + + list(self.block_data.keys()) + + list(self.count_data.keys()) + ) + plugins = await PluginInfo.get_plugins(module__in=modules) + module2plugin = {p.module: p for p in plugins} + create_list, update_list, delete_list = self.__set_db_limits( + db_limits, module2plugin, PluginLimitType.CD + ) + create_list1, update_list1, delete_list1 = self.__set_db_limits( + db_limits, module2plugin, PluginLimitType.COUNT + ) + create_list2, update_list2, delete_list2 = self.__set_db_limits( + db_limits, module2plugin, PluginLimitType.BLOCK + ) + all_create = create_list + create_list1 + create_list2 + all_update = update_list + update_list1 + update_list2 + all_delete = delete_list + delete_list1 + delete_list2 + return all_create, all_update, all_delete + + async def load_to_db(self): + """读取配置文件""" + + create_list, update_list, delete_list = await self.__set_all_limit() + if create_list: + await PluginLimit.bulk_create(create_list) + if update_list: + for limit in update_list: + await limit.save( + update_fields=[ + "status", + "check_type", + "watch_type", + "result", + "cd", + "max_count", + ] + ) + # TODO: tortoise.exceptions.OperationalError:syntax error at or near "GROUP" + # await PluginLimit.bulk_update( + # update_list, + # ["status", "check_type", "watch_type", "result", "cd", "max_count"], + # ) + if delete_list: + await PluginLimit.filter(id__in=delete_list).delete() + cnt = await PluginLimit.filter(status=True).count() + logger.info(f"已经加载 {cnt} 个插件限制.") + + +manager = Manager() diff --git a/zhenxun/builtin_plugins/nickname.py b/zhenxun/builtin_plugins/nickname.py index f5cce1fb..7dd9a697 100644 --- a/zhenxun/builtin_plugins/nickname.py +++ b/zhenxun/builtin_plugins/nickname.py @@ -1,5 +1,5 @@ import random -from typing import Any, List +from typing import Any from nonebot import on_regex from nonebot.adapters import Bot @@ -7,11 +7,10 @@ from nonebot.params import Depends, RegexGroup from nonebot.plugin import PluginMetadata from nonebot.rule import to_me from nonebot_plugin_alconna import Alconna, Option, on_alconna, store_true -from nonebot_plugin_session import EventSession -from nonebot_plugin_userinfo import EventUserInfo, UserInfo +from nonebot_plugin_uninfo import Uninfo -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.configs.config import BotConfig, Config +from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig from zhenxun.models.ban_console import BanConsole from zhenxun.models.friend_user import FriendUser from zhenxun.models.group_member_info import GroupInfoUser @@ -19,32 +18,40 @@ from zhenxun.services.log import logger from zhenxun.utils.depends import UserName from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils __plugin_meta__ = PluginMetadata( name="昵称系统", description="区区昵称,才不想叫呢!", usage=f""" - 个人昵称,将替换{NICKNAME}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称 + 个人昵称,将替换{BotConfig.self_nickname}称呼你的名称,群聊 与 私聊 昵称相互独立, + 全局昵称设置将更改您目前所有群聊中及私聊的昵称 指令: 以后叫我 [昵称]: 设置当前群聊/私聊的昵称 全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称 - {NICKNAME}我是谁 + {BotConfig.self_nickname}我是谁 """.strip(), extra=PluginExtraData( author="HibiKier", version="0.1", plugin_type=PluginType.NORMAL, menu_type="其他", + commands=[ + Command(command="以后叫我 [昵称]"), + Command(command="全局昵称设置 [昵称]"), + Command(command=f"{BotConfig.self_nickname}我是谁"), + ], configs=[ RegisterConfig( key="BLACK_WORD", value=["爸", "爹", "爷", "父"], - help="昵称所屏蔽的关键词,已设置的昵称会被替换为 *,未设置的昵称会在设置时提示", + help="昵称所屏蔽的关键词,已设置的昵称会被替换为 *," + "未设置的昵称会在设置时提示", default_value=None, - type=List[str], + type=list[str], ) ], - ).dict(), + ).to_dict(), ) _nickname_matcher = on_regex( @@ -86,25 +93,25 @@ _matcher.shortcut( CALL_NAME = [ "好啦好啦,我知道啦,{},以后就这么叫你吧", - f"嗯嗯,{NICKNAME}" + "记住你的昵称了哦,{}", + f"嗯嗯,{BotConfig.self_nickname}" + "记住你的昵称了哦,{}", "好突然,突然要叫你昵称什么的...{}..", - f"{NICKNAME}" + "会好好记住{}的,放心吧", + f"{BotConfig.self_nickname}" + "会好好记住{}的,放心吧", "好..好.,那窝以后就叫你{}了.", ] REMIND = [ "我肯定记得你啊,你是{}啊", "我不会忘记你的,你也不要忘记我!{}", - f"哼哼,{NICKNAME}" + "记忆力可是很好的,{}", + f"哼哼,{BotConfig.self_nickname}" + "记忆力可是很好的,{}", "嗯?你是失忆了嘛...{}..", - f"不要小看{NICKNAME}" + "的记忆力啊!笨蛋{}!QAQ", + f"不要小看{BotConfig.self_nickname}" + "的记忆力啊!笨蛋{}!QAQ", "哎?{}..怎么了吗..突然这样问..", ] CANCEL = [ - f"呜..{NICKNAME}" + "睡一觉就会忘记的..和梦一样..{}", + f"呜..{BotConfig.self_nickname}" + "睡一觉就会忘记的..和梦一样..{}", "窝知道了..{}..", - f"是{NICKNAME}" + "哪里做的不好嘛..好吧..晚安{}", + f"是{BotConfig.self_nickname}" + "哪里做的不好嘛..好吧..晚安{}", "呃,{},下次我绝对绝对绝对不会再忘记你!", "可..可恶!{}!太可恶了!呜", ] @@ -117,7 +124,7 @@ def CheckNickname(): async def dependency( bot: Bot, - session: EventSession, + session: Uninfo, reg_group: tuple[Any, ...] = RegexGroup(), ): black_word = Config.get_config("nickname", "BLACK_WORD") @@ -127,7 +134,7 @@ def CheckNickname(): await MessageUtils.build_message("叫你空白?叫你虚空?叫你无名??").finish( at_sender=True ) - if session.id1 in bot.config.superusers: + if session.user.id in bot.config.superusers: logger.debug( f"超级用户设置昵称, 跳过合法检测: {name}", "昵称设置", session=session ) @@ -152,123 +159,115 @@ def CheckNickname(): logger.debug( "昵称设置禁止字符: [{word}]", "昵称设置", session=session ) - await MessageUtils.build_message(f"字符 [{x}] 为禁止字符!").finish( - at_sender=True - ) + await MessageUtils.build_message( + f"字符 [{word}] 为禁止字符!" + ).finish(at_sender=True) return Depends(dependency) @_nickname_matcher.handle(parameterless=[CheckNickname()]) async def _( - session: EventSession, - user_info: UserInfo = EventUserInfo(), + session: Uninfo, + uname: str = UserName(), reg_group: tuple[Any, ...] = RegexGroup(), ): - if session.id1: - (name,) = reg_group - if len(name) < 5: - if random.random() < 0.3: - name = "~".join(name) - if gid := session.id3 or session.id2: - await GroupInfoUser.set_user_nickname( - session.id1, - gid, - name, - user_info.user_displayname - or user_info.user_remark - or user_info.user_name, - session.platform, - ) - logger.info(f"设置群昵称成功: {name}", "昵称设置", session=session) - await MessageUtils.build_message( - random.choice(CALL_NAME).format(name) - ).finish(reply_to=True) - else: - await FriendUser.set_user_nickname( - session.id1, - name, - user_info.user_displayname - or user_info.user_remark - or user_info.user_name, - session.platform, - ) - logger.info(f"设置私聊昵称成功: {name}", "昵称设置", session=session) - await MessageUtils.build_message( - random.choice(CALL_NAME).format(name) - ).finish(reply_to=True) - await MessageUtils.build_message("用户id为空...").send() + (name,) = reg_group + if len(name) < 5 and random.random() < 0.3: + name = "~".join(name) + group_id = None + if session.group: + group_id = session.group.parent.id if session.group.parent else session.group.id + if group_id: + await GroupInfoUser.set_user_nickname( + session.user.id, + group_id, + name, + uname, + PlatformUtils.get_platform(session), + ) + logger.info(f"设置群昵称成功: {name}", "昵称设置", session=session) + else: + await FriendUser.set_user_nickname( + session.user.id, + name, + uname, + PlatformUtils.get_platform(session), + ) + logger.info(f"设置私聊昵称成功: {name}", "昵称设置", session=session) + await MessageUtils.build_message(random.choice(CALL_NAME).format(name)).finish( + reply_to=True + ) @_global_nickname_matcher.handle(parameterless=[CheckNickname()]) async def _( - session: EventSession, + session: Uninfo, nickname: str = UserName(), reg_group: tuple[Any, ...] = RegexGroup(), ): - if session.id1: - (name,) = reg_group - await FriendUser.set_user_nickname( - session.id1, - name, - nickname, - session.platform, - ) - await GroupInfoUser.filter(user_id=session.id1).update(nickname=name) - logger.info(f"设置全局昵称成功: {name}", "设置全局昵称", session=session) - await MessageUtils.build_message(random.choice(CALL_NAME).format(name)).finish( - reply_to=True - ) - await MessageUtils.build_message("用户id为空...").send() + (name,) = reg_group + await FriendUser.set_user_nickname( + session.user.id, + name, + nickname, + PlatformUtils.get_platform(session), + ) + await GroupInfoUser.filter(user_id=session.user.id).update(nickname=name) + logger.info(f"设置全局昵称成功: {name}", "设置全局昵称", session=session) + await MessageUtils.build_message(random.choice(CALL_NAME).format(name)).finish( + reply_to=True + ) @_matcher.assign("name") -async def _(session: EventSession, user_info: UserInfo = EventUserInfo()): - if session.id1: - if gid := session.id3 or session.id2: - nickname = await GroupInfoUser.get_user_nickname(session.id1, gid) - card = user_info.user_displayname or user_info.user_name - else: - nickname = await FriendUser.get_user_nickname(session.id1) - card = user_info.user_name - if nickname: - await MessageUtils.build_message( - random.choice(REMIND).format(nickname) - ).finish(reply_to=True) - else: - await MessageUtils.build_message( - random.choice( - [ - "没..没有昵称嘛,{}", - "啊,你是{}啊,我想叫你的昵称!", - "是{}啊,有什么事吗?", - "你是{}?", - ] - ).format(card) - ).finish(reply_to=True) - await MessageUtils.build_message("用户id为空...").send() +async def _(session: Uninfo, uname: str = UserName()): + group_id = None + if session.group: + group_id = session.group.parent.id if session.group.parent else session.group.id + if group_id: + nickname = await GroupInfoUser.get_user_nickname(session.user.id, group_id) + card = uname + else: + nickname = await FriendUser.get_user_nickname(session.user.id) + card = uname + if nickname: + await MessageUtils.build_message(random.choice(REMIND).format(nickname)).finish( + reply_to=True + ) + else: + await MessageUtils.build_message( + random.choice( + [ + "没..没有昵称嘛,{}", + "啊,你是{}啊,我想叫你的昵称!", + "是{}啊,有什么事吗?", + "你是{}?", + ] + ).format(card) + ).finish(reply_to=True) @_matcher.assign("cancel") -async def _(bot: Bot, session: EventSession, user_info: UserInfo = EventUserInfo()): - if session.id1: - gid = session.id3 or session.id2 - if gid: - nickname = await GroupInfoUser.get_user_nickname(session.id1, gid) +async def _(bot: Bot, session: Uninfo): + group_id = None + if session.group: + group_id = session.group.parent.id if session.group.parent else session.group.id + if group_id: + nickname = await GroupInfoUser.get_user_nickname(session.user.id, group_id) + else: + nickname = await FriendUser.get_user_nickname(session.user.id) + if nickname: + await MessageUtils.build_message(random.choice(CANCEL).format(nickname)).send( + reply_to=True + ) + if group_id: + await GroupInfoUser.set_user_nickname(session.user.id, group_id, "") else: - nickname = await FriendUser.get_user_nickname(session.id1) - if nickname: - await MessageUtils.build_message( - random.choice(CANCEL).format(nickname) - ).send(reply_to=True) - if gid: - await GroupInfoUser.set_user_nickname(session.id1, gid, "") - else: - await FriendUser.set_user_nickname(session.id1, "") - await BanConsole.ban(session.id1, gid, 9, 60, bot.self_id) - return - else: - await MessageUtils.build_message("你在做梦吗?你没有昵称啊").finish( - reply_to=True - ) - await MessageUtils.build_message("用户id为空...").send() + await FriendUser.set_user_nickname(session.user.id, "") + await BanConsole.ban(session.user.id, group_id, 9, 60, bot.self_id) + return + else: + await MessageUtils.build_message("你在做梦吗?你没有昵称啊").finish( + reply_to=True + ) diff --git a/zhenxun/builtin_plugins/platform/__init__.py b/zhenxun/builtin_plugins/platform/__init__.py index 448afcf7..8ded37ef 100644 --- a/zhenxun/builtin_plugins/platform/__init__.py +++ b/zhenxun/builtin_plugins/platform/__init__.py @@ -1,11 +1,25 @@ -import os from pathlib import Path import nonebot +from zhenxun.services.log import logger + path = Path(__file__).parent -for f in os.listdir(path): - _p = path / f - if _p.is_dir(): - nonebot.load_plugins(str(_p.resolve())) + +try: + from nonebot.adapters.onebot.v11 import Bot + + nonebot.load_plugins(str((path / "qq").resolve())) +except ImportError: + logger.warning("未安装 onebot-adapter,无法加载QQ平台专用插件...") + + +try: + from nonebot.adapters.qq import ( # noqa: F401 # pyright: ignore [reportMissingImports] + Bot, + ) + + nonebot.load_plugins(str((path / "qq_api").resolve())) +except ImportError: + logger.warning("未安装 qq-adapter,无法加载QQ官平台专用插件...") diff --git a/zhenxun/builtin_plugins/platform/qq/exception.py b/zhenxun/builtin_plugins/platform/qq/exception.py new file mode 100644 index 00000000..87e14676 --- /dev/null +++ b/zhenxun/builtin_plugins/platform/qq/exception.py @@ -0,0 +1,11 @@ +class ForceAddGroupError(Exception): + """ + 强制拉群 + """ + + def __init__(self, info: str): + super().__init__(self) + self._info = info + + def get_info(self) -> str: + return self._info diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle.py b/zhenxun/builtin_plugins/platform/qq/group_handle.py deleted file mode 100644 index 262a67f1..00000000 --- a/zhenxun/builtin_plugins/platform/qq/group_handle.py +++ /dev/null @@ -1,317 +0,0 @@ -import os -import random -import re -from datetime import datetime - -import nonebot -import ujson as json -from nonebot import on_notice, on_request -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import ( - GroupDecreaseNoticeEvent, - GroupIncreaseNoticeEvent, -) -from nonebot.adapters.onebot.v12 import ( - GroupMemberDecreaseEvent, - GroupMemberIncreaseEvent, -) -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import At - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task -from zhenxun.models.fg_request import FgRequest -from zhenxun.models.group_console import GroupConsole -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.models.level_user import LevelUser -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType, RequestHandleType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import FreqLimiter - -__plugin_meta__ = PluginMetadata( - name="QQ群事件处理", - description="群事件处理", - usage="", - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.HIDDEN, - configs=[ - RegisterConfig( - module="invite_manager", - key="message", - value=f"请不要未经同意就拉{NICKNAME}入群!告辞!", - help="强制拉群后进群回复的内容", - ), - RegisterConfig( - module="invite_manager", - key="flag", - value=True, - help="强制拉群后进群退出并回复内容", - default_value=True, - type=bool, - ), - RegisterConfig( - module="invite_manager", - key="welcome_msg_cd", - value=5, - help="群欢迎消息cd", - default_value=5, - type=int, - ), - RegisterConfig( - module="_task", - key="DEFAULT_GROUP_WELCOME", - value=True, - help="被动 进群欢迎 进群默认开关状态", - default_value=True, - type=bool, - ), - RegisterConfig( - module="_task", - key="DEFAULT_REFUND_GROUP_REMIND", - value=True, - help="被动 退群提醒 进群默认开关状态", - default_value=True, - type=bool, - ), - ], - tasks=[ - Task(module="group_welcome", name="进群欢迎"), - Task(module="refund_group_remind", name="退群提醒"), - ], - ).dict(), -) - - -superuser = nonebot.get_driver().config.platform_superusers["qq"][0] - -base_config = Config.get("invite_manager") - - -limit_cd = base_config.get("welcome_msg_cd") - -_flmt = FreqLimiter(limit_cd) - - -group_increase_handle = on_notice(priority=1, block=False) -"""群员增加处理""" -group_decrease_handle = on_notice(priority=1, block=False) -"""群员减少处理""" -add_group = on_request(priority=1, block=False) -"""加群同意请求""" - - -@group_increase_handle.handle() -async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent): - user_id = str(event.user_id) - group_id = str(event.group_id) - if user_id == bot.self_id: - """新成员为bot本身""" - group = await GroupConsole.get_or_none( - group_id=group_id, channel_id__isnull=True - ) - if not group or group.group_flag == 0: - """群聊不存在或被强制拉群""" - if base_config.get("flag"): - """退出群组""" - try: - if result_msg := base_config.get("message"): - await bot.send_group_msg( - group_id=event.group_id, message=result_msg - ) - await bot.set_group_leave(group_id=event.group_id) - await bot.send_private_msg( - user_id=int(superuser), - message=f"触发强制入群保护,已成功退出群聊 {group_id}...", - ) - logger.info( - f"强制拉群或未有群信息,退出群聊成功", - "入群检测", - group_id=event.group_id, - ) - if req := await FgRequest.get_or_none( - group_id=group_id, handle_type__isnull=True - ): - req.handle_type = RequestHandleType.IGNORE - await req.save(update_fields=["handle_type"]) - except Exception as e: - logger.error( - f"强制拉群或未有群信息,退出群聊失败", - "入群检测", - group_id=event.group_id, - e=e, - ) - await bot.send_private_msg( - user_id=int(superuser), - message=f"触发强制入群保护,退出群聊 {event.group_id} 失败...", - ) - await GroupConsole.filter(group_id=group_id).delete() - else: - """允许群组并设置群认证,默认群功能开关""" - if group: - await GroupConsole.filter( - group_id=group_id, channel_id__isnull=True - ).update(group_flag=1) - else: - block_plugin = "" - if plugin_list := await PluginInfo.filter( - default_status=False - ).all(): - for plugin in plugin_list: - block_plugin += f"{plugin.module}," - group_info = await bot.get_group_info(group_id=event.group_id) - await GroupConsole.create( - group_id=group_info["group_id"], - group_name=group_info["group_name"], - max_member_count=group_info["max_member_count"], - member_count=group_info["member_count"], - group_flag=1, - block_plugin=block_plugin, - platform="qq", - ) - """刷新群管理员权限""" - admin_default_auth = Config.get_config( - "admin_bot_manage", "ADMIN_DEFAULT_AUTH" - ) - # 即刻刷新权限 - for user_info in await bot.get_group_member_list( - group_id=event.group_id - ): - """即刻刷新权限""" - if ( - user_info["role"] - in [ - "owner", - "admin", - ] - and not await LevelUser.is_group_flag( - user_info["user_id"], group_id - ) - and admin_default_auth is not None - ): - await LevelUser.set_level( - user_info["user_id"], - user_info["group_id"], - admin_default_auth, - ) - logger.debug( - f"添加默认群管理员权限: {admin_default_auth}", - "入群检测", - session=user_info["user_id"], - group_id=user_info["group_id"], - ) - if str(user_info["user_id"]) in bot.config.superusers: - await LevelUser.set_level( - user_info["user_id"], user_info["group_id"], 9 - ) - logger.debug( - f"添加超级用户权限: 9", - "入群检测", - session=user_info["user_id"], - group_id=user_info["group_id"], - ) - else: - join_time = datetime.now() - user_info = await bot.get_group_member_info( - group_id=event.group_id, user_id=event.user_id - ) - await GroupInfoUser.update_or_create( - user_id=str(user_info["user_id"]), - group_id=str(user_info["group_id"]), - defaults={"user_name": user_info["nickname"], "user_join_time": join_time}, - ) - logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功") - - if _flmt.check(group_id): - """群欢迎消息""" - _flmt.start_cd(group_id) - path = DATA_PATH / "welcome_message" / "qq" / f"{group_id}" - data = json.load((path / "text.json").open(encoding="utf-8")) - message = data["message"] - msg_split = re.split(r"\[image:\d+\]", message) - msg_list = [] - if data["at"]: - msg_list.append(At(flag="user", target=user_id)) - for i, text in enumerate(msg_split): - msg_list.append(text) - img_file = path / f"{i}.png" - if img_file.exists(): - msg_list.append(img_file) - if not TaskInfo.is_block("group_welcome", group_id): - logger.info(f"发送群欢迎消息...", "入群检测", group_id=group_id) - if msg_list: - await MessageUtils.build_message(msg_list).send() - else: - image = ( - IMAGE_PATH - / "qxz" - / random.choice(os.listdir(IMAGE_PATH / "qxz")) - ) - await MessageUtils.build_message( - [ - "新人快跑啊!!本群现状↓(快使用自定义!)", - image, - ] - ).send() - - -@group_decrease_handle.handle() -async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent): - if event.sub_type == "kick_me": - """踢出Bot""" - group_id = event.group_id - operator_id = event.operator_id - if user := await GroupInfoUser.get_or_none( - user_id=str(event.operator_id), group_id=str(event.group_id) - ): - operator_name = user.user_name - else: - operator_name = "None" - group = await GroupConsole.filter(group_id=str(group_id)).first() - group_name = group.group_name if group else "" - coffee = int(superuser) - await bot.send_private_msg( - user_id=coffee, - message=f"****呜..一份踢出报告****\n" - f"我被 {operator_name}({operator_id})\n" - f"踢出了 {group_name}({group_id})\n" - f"日期:{str(datetime.now()).split('.')[0]}", - ) - if group: - await group.delete() - return - if str(event.user_id) == bot.self_id: - """踢出Bot""" - await GroupConsole.filter(group_id=str(event.group_id)).delete() - return - if user := await GroupInfoUser.get_or_none( - user_id=str(event.user_id), group_id=str(event.group_id) - ): - user_name = user.user_name - else: - user_name = f"{event.user_id}" - await GroupInfoUser.filter( - user_id=str(event.user_id), group_id=str(event.group_id) - ).delete() - logger.info( - f"名称: {user_name} 退出群聊", - "group_decrease_handle", - session=event.user_id, - group_id=event.group_id, - ) - result = "" - if event.sub_type == "leave": - result = f"{user_name}离开了我们..." - if event.sub_type == "kick": - operator = await bot.get_group_member_info( - user_id=event.operator_id, group_id=event.group_id - ) - operator_name = operator["card"] if operator["card"] else operator["nickname"] - result = f"{user_name} 被 {operator_name} 送走了." - if not TaskInfo.is_block("refund_group_remind", str(event.group_id)): - await group_decrease_handle.send(f"{result}") diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py b/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py new file mode 100644 index 00000000..d621f087 --- /dev/null +++ b/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py @@ -0,0 +1,152 @@ +from nonebot import on_notice, on_request +from nonebot.adapters import Bot +from nonebot.adapters.onebot.v11 import ( + GroupDecreaseNoticeEvent, + GroupIncreaseNoticeEvent, +) +from nonebot.adapters.onebot.v12 import ( + GroupMemberDecreaseEvent, + GroupMemberIncreaseEvent, +) +from nonebot.plugin import PluginMetadata +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.builtin_plugins.platform.qq.exception import ForceAddGroupError +from zhenxun.configs.config import BotConfig, Config +from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task +from zhenxun.models.group_console import GroupConsole +from zhenxun.utils.common_utils import CommonUtils +from zhenxun.utils.enum import PluginType +from zhenxun.utils.platform import PlatformUtils +from zhenxun.utils.rules import notice_rule + +from .data_source import GroupManager + +__plugin_meta__ = PluginMetadata( + name="QQ群事件处理", + description="群事件处理", + usage="", + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.HIDDEN, + configs=[ + RegisterConfig( + module="invite_manager", + key="message", + value=f"请不要未经同意就拉{BotConfig.self_nickname}入群!告辞!", + help="强制拉群后进群回复的内容", + ), + RegisterConfig( + module="invite_manager", + key="flag", + value=True, + help="强制拉群后进群退出并回复内容", + default_value=True, + type=bool, + ), + RegisterConfig( + module="invite_manager", + key="welcome_msg_cd", + value=5, + help="群欢迎消息cd", + default_value=5, + type=int, + ), + RegisterConfig( + module="_task", + key="DEFAULT_GROUP_WELCOME", + value=True, + help="被动 进群欢迎 进群默认开关状态", + default_value=True, + type=bool, + ), + RegisterConfig( + module="_task", + key="DEFAULT_REFUND_GROUP_REMIND", + value=True, + help="被动 退群提醒 进群默认开关状态", + default_value=True, + type=bool, + ), + ], + tasks=[ + Task( + module="group_welcome", + name="进群欢迎", + create_status=False, + default_status=False, + ), + Task( + module="refund_group_remind", + name="退群提醒", + create_status=False, + default_status=False, + ), + ], + ).to_dict(), +) + + +base_config = Config.get("invite_manager") + + +limit_cd = base_config.get("welcome_msg_cd") + + +group_increase_handle = on_notice( + priority=1, + block=False, + rule=notice_rule([GroupIncreaseNoticeEvent, GroupMemberIncreaseEvent]), +) +"""群员增加处理""" +group_decrease_handle = on_notice( + priority=1, + block=False, + rule=notice_rule([GroupMemberDecreaseEvent, GroupDecreaseNoticeEvent]), +) +"""群员减少处理""" +add_group = on_request(priority=1, block=False) +"""加群同意请求""" + + +@group_increase_handle.handle() +async def _( + bot: Bot, + session: Uninfo, + event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent, +): + if session.user.id == bot.self_id: + """新成员为bot本身""" + group, _ = await GroupConsole.get_or_create( + group_id=str(event.group_id), channel_id__isnull=True + ) + try: + await GroupManager.add_bot( + bot, str(event.operator_id), str(event.group_id), group + ) + except ForceAddGroupError as e: + await PlatformUtils.send_superuser(bot, e.get_info()) + else: + await GroupManager.add_user(session, bot) + + +@group_decrease_handle.handle() +async def _( + bot: Bot, + session: Uninfo, + event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent, +): + user_id = str(event.user_id) + group_id = str(event.group_id) + if event.sub_type == "kick_me": + """踢出Bot""" + await GroupManager.kick_bot(bot, user_id, group_id) + elif event.sub_type in ["leave", "kick"]: + result = await GroupManager.run_user( + bot, user_id, group_id, str(event.operator_id), event.sub_type + ) + if result and not await CommonUtils.task_is_block( + session, "refund_group_remind" + ): + await group_decrease_handle.send(result) diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py b/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py new file mode 100644 index 00000000..1190fb5e --- /dev/null +++ b/zhenxun/builtin_plugins/platform/qq/group_handle/data_source.py @@ -0,0 +1,336 @@ +from datetime import datetime +import os +from pathlib import Path +import random + +from nonebot.adapters import Bot +from nonebot.exception import ActionFailed +from nonebot_plugin_alconna import At, UniMessage +from nonebot_plugin_uninfo import Uninfo +import ujson as json + +from zhenxun.builtin_plugins.platform.qq.exception import ForceAddGroupError +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH +from zhenxun.models.fg_request import FgRequest +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.group_member_info import GroupInfoUser +from zhenxun.models.level_user import LevelUser +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.services.log import logger +from zhenxun.utils.common_utils import CommonUtils +from zhenxun.utils.enum import RequestHandleType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils +from zhenxun.utils.utils import FreqLimiter + +base_config = Config.get("invite_manager") + +limit_cd = base_config.get("welcome_msg_cd") + +WELCOME_PATH = DATA_PATH / "welcome_message" + +DEFAULT_IMAGE_PATH = IMAGE_PATH / "qxz" + + +class GroupManager: + _flmt = FreqLimiter(limit_cd) + + @classmethod + async def __handle_add_group( + cls, bot: Bot, group_id: str, group: GroupConsole | None + ): + """允许群组并设置群认证,默认群功能开关 + + 参数: + bot: Bot + group_id: 群组id + group: GroupConsole + """ + if group: + group.group_flag = 1 + await group.save(update_fields=["group_flag"]) + else: + block_plugin = "" + if plugin_list := await PluginInfo.filter(default_status=False).all(): + for plugin in plugin_list: + block_plugin += f"<{plugin.module}," + group_info = await bot.get_group_info(group_id=group_id, no_cache=True) + await GroupConsole.create( + group_id=group_info["group_id"], + group_name=group_info["group_name"], + max_member_count=group_info["max_member_count"], + member_count=group_info["member_count"], + group_flag=1, + block_plugin=block_plugin, + platform="qq", + ) + + @classmethod + async def __refresh_level(cls, bot: Bot, group_id: str): + """刷新权限 + + 参数: + bot: Bot + group_id: 群组id + """ + admin_default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH") + member_list = await bot.get_group_member_list(group_id=group_id) + member_id_list = [str(user_info["user_id"]) for user_info in member_list] + flag2u = await LevelUser.filter( + user_id__in=member_id_list, group_id=group_id, group_flag=1 + ).values_list("user_id", flat=True) + # 即刻刷新权限 + for user_info in member_list: + user_id = str(user_info["user_id"]) + role = user_info["role"] + if user_id in bot.config.superusers: + await LevelUser.set_level(user_id, group_id, 9) + logger.debug( + "添加超级用户权限: 9", + "入群检测", + session=user_id, + group_id=user_info["group_id"], + ) + elif ( + admin_default_auth is not None + and role in ["owner", "admin"] + and user_id not in flag2u + ): + await LevelUser.set_level( + user_id, + group_id, + admin_default_auth if role == "admin" else admin_default_auth + 1, + ) + logger.debug( + f"添加默认群管理员权限: {admin_default_auth}", + "入群检测", + session=user_id, + group_id=user_info["group_id"], + ) + + @classmethod + async def add_bot( + cls, bot: Bot, operator_id: str, group_id: str, group: GroupConsole + ): + """拉入bot + + 参数: + bot: Bot + operator_id: 操作者id + group_id: 群组id + group: GroupConsole + """ + if ( + base_config.get("flag") + and operator_id not in bot.config.superusers + and group.group_flag != 1 + ): + """退出群组""" + try: + if result_msg := base_config.get("message"): + await bot.send_group_msg(group_id=int(group_id), message=result_msg) + await bot.set_group_leave(group_id=int(group_id)) + logger.info( + "强制拉群或未有群信息,退出群聊成功", "入群检测", group_id=group_id + ) + await FgRequest.filter( + group_id=group_id, handle_type__isnull=True + ).update(handle_type=RequestHandleType.IGNORE) + except Exception as e: + logger.error( + "强制拉群或未有群信息,退出群聊失败", + "入群检测", + group_id=group_id, + e=e, + ) + raise ForceAddGroupError("强制拉群或未有群信息,退出群聊失败...") from e + await GroupConsole.filter(group_id=group_id).delete() + raise ForceAddGroupError(f"触发强制入群保护,已成功退出群聊 {group_id}...") + else: + await cls.__handle_add_group(bot, group_id, group) + """刷新群管理员权限""" + await cls.__refresh_level(bot, group_id) + + @classmethod + def get_path(cls, session: Uninfo) -> Path | None: + """根据Session获取存储路径 + + 参数: + session: Uninfo: + + 返回: + Path: 存储路径 + """ + if not session.group: + return None + platform = PlatformUtils.get_platform(session) + path = WELCOME_PATH / f"{platform}" / f"{session.group.id}" + if session.group.parent: + path = ( + WELCOME_PATH + / f"{platform}" + / f"{session.group.parent.id}" + / f"{session.group.id}" + ) + path.mkdir(parents=True, exist_ok=True) + return path + + @classmethod + def __get_welcome_data(cls, session: Uninfo) -> dict | None: + """获取存储数据 + + 参数: + session: Uninfo + + 返回: + dict | None: 欢迎消息数据 + """ + if not session.group: + return None + path = cls.get_path(session) + if not path: + return None + file = path / "text.json" + if not file.exists(): + return None + with file.open(encoding="utf8") as f: + return json.load(f) + + @classmethod + async def __send_welcome_message(cls, session: Uninfo, user_id: str): + """发送群欢迎消息 + + 参数: + user_id: 用户id + group_id: 群组id + """ + if not session.group: + return + cls._flmt.start_cd(session.group.id) + if json_data := cls.__get_welcome_data(session): + key = random.choice([k for k in json_data.keys() if json_data[k]["status"]]) + welcome_data = json_data[key] + msg_list = UniMessage().load(welcome_data["message"]) + if welcome_data["at"]: + msg_list.insert(0, At("user", user_id)) + logger.info("发送群欢迎消息...", "入群检测", session=session) + if msg_list: + await MessageUtils.build_message(msg_list).finish() # type: ignore + image = DEFAULT_IMAGE_PATH / random.choice(os.listdir(DEFAULT_IMAGE_PATH)) + await MessageUtils.build_message( + [ + "新人快跑啊!!本群现状↓(快使用自定义群欢迎消息!)", + image, + ] + ).send() + + @classmethod + async def add_user(cls, session: Uninfo, bot: Bot): + """拉入用户 + + 参数: + session: Uninfo + bot: Bot + """ + user_id = session.user.id + group_id = "" + if session.group: + if session.group.parent: + group_id = session.group.parent.id + else: + group_id = session.group.id + join_time = datetime.now() + try: + user_info = await bot.get_group_member_info( + group_id=int(group_id), user_id=int(user_id), no_cache=True + ) + except ActionFailed as e: + logger.warning("获取用户信息识别...", e=e) + user_info = {"user_id": user_id, "group_id": group_id, "nickname": ""} + await GroupInfoUser.update_or_create( + user_id=str(user_info["user_id"]), + group_id=str(user_info["group_id"]), + defaults={ + "user_name": user_info["nickname"], + "user_join_time": join_time, + }, + ) + logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功") + if not await CommonUtils.task_is_block( + session, "group_welcome" + ) and cls._flmt.check(group_id): + await cls.__send_welcome_message(session, user_id) + + @classmethod + async def kick_bot(cls, bot: Bot, group_id: str, operator_id: str): + """踢出bot + + 参数: + bot: Bot + group_id: 群组id + operator_id: 操作员id + """ + if user := await GroupInfoUser.get_or_none( + user_id=operator_id, group_id=group_id + ): + operator_name = user.user_name + else: + operator_name = "None" + group = await GroupConsole.get_group(group_id) + group_name = group.group_name if group else "" + if group: + await group.delete() + await PlatformUtils.send_superuser( + bot, + f"****呜..一份踢出报告****\n" + f"我被 {operator_name}({operator_id})\n" + f"踢出了 {group_name}({group_id})\n" + f"日期:{str(datetime.now()).split('.')[0]}", + ) + + @classmethod + async def run_user( + cls, + bot: Bot, + user_id: str, + group_id: str, + operator_id: str, + sub_type: str, + ) -> str | None: + """踢出用户或用户离开 + + 参数: + bot: Bot + user_id: 用户id + group_id: 群组id + operator_id: 操作员id + sub_type: 类型 + + 返回: + str | None: 返回消息 + """ + if user := await GroupInfoUser.get_or_none(user_id=user_id, group_id=group_id): + user_name = user.user_name + else: + user_name = f"{user_id}" + if user: + await user.delete() + logger.info( + f"名称: {user_name} 退出群聊", + "group_decrease_handle", + session=user_id, + group_id=group_id, + ) + if sub_type == "kick": + if operator_id != "0": + operator = await bot.get_group_member_info( + user_id=int(operator_id), group_id=int(group_id) + ) + operator_name = operator["card"] or operator["nickname"] + else: + operator_name = "" + return f"{user_name} 被 {operator_name} 送走了." + elif sub_type == "leave": + return f"{user_name}离开了我们..." + return None diff --git a/zhenxun/builtin_plugins/platform/qq_api/ug_watch.py b/zhenxun/builtin_plugins/platform/qq_api/ug_watch.py new file mode 100644 index 00000000..4e7a708c --- /dev/null +++ b/zhenxun/builtin_plugins/platform/qq_api/ug_watch.py @@ -0,0 +1,31 @@ +from nonebot.message import run_preprocessor +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.models.friend_user import FriendUser +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.group_member_info import GroupInfoUser +from zhenxun.services.log import logger +from zhenxun.utils.platform import PlatformUtils + + +@run_preprocessor +async def do_something(session: Uninfo): + platform = PlatformUtils.get_platform(session) + if session.group: + if not await GroupConsole.exists(group_id=session.group.id): + await GroupConsole.create(group_id=session.group.id) + logger.info("添加当前群组ID信息" "", session=session) + + if not await GroupInfoUser.exists( + user_id=session.user.id, group_id=session.group.id + ): + await GroupInfoUser.create( + user_id=session.user.id, group_id=session.group.id, platform=platform + ) + logger.info("添加当前用户群组ID信息", "", session=session) + elif not await FriendUser.exists(user_id=session.user.id, platform=platform): + try: + await FriendUser.create(user_id=session.user.id, platform=platform) + logger.info("添加当前好友用户信息", "", session=session) + except Exception as e: + logger.error("添加当前好友用户信息失败", session=session, e=e) diff --git a/zhenxun/builtin_plugins/plugin_store/__init__.py b/zhenxun/builtin_plugins/plugin_store/__init__.py new file mode 100644 index 00000000..7e9f52a0 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/__init__.py @@ -0,0 +1,167 @@ +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.utils import is_number + +from .data_source import ShopManage + +__plugin_meta__ = PluginMetadata( + name="插件商店", + description="插件商店", + usage=""" + 插件商店 : 查看当前的插件商店 + 添加插件 id or module : 添加插件 + 移除插件 id or module : 移除插件 + 搜索插件 name or author : 搜索插件 + 更新插件 id or module : 更新插件 + 更新全部插件 : 更新全部插件 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + ).to_dict(), +) + +_matcher = on_alconna( + Alconna( + "插件商店", + Subcommand("add", Args["plugin_id", str]), + Subcommand("remove", Args["plugin_id", str]), + Subcommand("search", Args["plugin_name_or_author", str]), + Subcommand("update", Args["plugin_id", str]), + Subcommand("update_all"), + ), + permission=SUPERUSER, + priority=1, + block=True, +) + +_matcher.shortcut( + r"(添加|安装)插件", + command="插件商店", + arguments=["add", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"(移除|卸载)插件", + command="插件商店", + arguments=["remove", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"搜索插件", + command="插件商店", + arguments=["search", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"更新插件", + command="插件商店", + arguments=["update", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"更新全部插件", + command="插件商店", + arguments=["update_all"], + prefix=True, +) + + +@_matcher.assign("$main") +async def _(session: EventSession): + try: + result = await ShopManage.get_plugins_info() + logger.info("查看插件列表", "插件商店", session=session) + await MessageUtils.build_message(result).send() + except Exception as e: + logger.error(f"查看插件列表失败 e: {e}", "插件商店", session=session, e=e) + await MessageUtils.build_message("获取插件列表失败...").send() + + +@_matcher.assign("add") +async def _(session: EventSession, plugin_id: str): + try: + if is_number(plugin_id): + await MessageUtils.build_message(f"正在添加插件 Id: {plugin_id}").send() + else: + await MessageUtils.build_message(f"正在添加插件 Module: {plugin_id}").send() + result = await ShopManage.add_plugin(plugin_id) + except Exception as e: + logger.error(f"添加插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"添加插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"添加插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + + +@_matcher.assign("remove") +async def _(session: EventSession, plugin_id: str): + try: + result = await ShopManage.remove_plugin(plugin_id) + except Exception as e: + logger.error(f"移除插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"移除插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"移除插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + + +@_matcher.assign("search") +async def _(session: EventSession, plugin_name_or_author: str): + try: + result = await ShopManage.search_plugin(plugin_name_or_author) + except Exception as e: + logger.error( + f"搜索插件 name: {plugin_name_or_author}失败", + "插件商店", + session=session, + e=e, + ) + await MessageUtils.build_message( + f"搜索插件 name: {plugin_name_or_author} 失败 e: {e}" + ).finish() + logger.info(f"搜索插件 name: {plugin_name_or_author}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + + +@_matcher.assign("update") +async def _(session: EventSession, plugin_id: str): + try: + if is_number(plugin_id): + await MessageUtils.build_message(f"正在更新插件 Id: {plugin_id}").send() + else: + await MessageUtils.build_message(f"正在更新插件 Module: {plugin_id}").send() + result = await ShopManage.update_plugin(plugin_id) + except Exception as e: + logger.error(f"更新插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"更新插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"更新插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + + +@_matcher.assign("update_all") +async def _(session: EventSession): + try: + await MessageUtils.build_message("正在更新全部插件").send() + result = await ShopManage.update_all_plugin() + except Exception as e: + logger.error("更新全部插件失败", "插件商店", session=session, e=e) + await MessageUtils.build_message(f"更新全部插件失败 e: {e}").finish() + logger.info("更新全部插件", "插件商店", session=session) + await MessageUtils.build_message(result).send() diff --git a/zhenxun/builtin_plugins/plugin_store/config.py b/zhenxun/builtin_plugins/plugin_store/config.py new file mode 100644 index 00000000..dacaffec --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/config.py @@ -0,0 +1,11 @@ +from pathlib import Path + +BASE_PATH = Path() / "zhenxun" +BASE_PATH.mkdir(parents=True, exist_ok=True) + + +DEFAULT_GITHUB_URL = "https://github.com/zhenxun-org/zhenxun_bot_plugins/tree/main" +"""伴生插件github仓库地址""" + +EXTRA_GITHUB_URL = "https://github.com/zhenxun-org/zhenxun_bot_plugins_index/tree/index" +"""插件库索引github仓库地址""" diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py new file mode 100644 index 00000000..39c7d263 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/data_source.py @@ -0,0 +1,463 @@ +from pathlib import Path +import shutil +import subprocess + +from aiocache import cached +import ujson as json + +from zhenxun.builtin_plugins.auto_update.config import REQ_TXT_FILE_STRING +from zhenxun.builtin_plugins.plugin_store.models import StorePluginInfo +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.services.log import logger +from zhenxun.services.plugin_init import PluginInitManager +from zhenxun.utils.github_utils import GithubUtils +from zhenxun.utils.github_utils.models import RepoAPI +from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle +from zhenxun.utils.utils import is_number + +from .config import BASE_PATH, DEFAULT_GITHUB_URL, EXTRA_GITHUB_URL + + +def row_style(column: str, text: str) -> RowStyle: + """被动技能文本风格 + + 参数: + column: 表头 + text: 文本内容 + + 返回: + RowStyle: RowStyle + """ + style = RowStyle() + if column == "-" and text == "已安装": + style.font_color = "#67C23A" + return style + + +def install_requirement(plugin_path: Path): + requirement_files = ["requirement.txt", "requirements.txt"] + requirement_paths = [plugin_path / file for file in requirement_files] + + existing_requirements = next( + (path for path in requirement_paths if path.exists()), None + ) + + if not existing_requirements: + logger.debug( + f"No requirement.txt found for plugin: {plugin_path.name}", "插件管理" + ) + return + + try: + result = subprocess.run( + ["poetry", "run", "pip", "install", "-r", str(existing_requirements)], + check=True, + capture_output=True, + text=True, + ) + logger.debug( + "Successfully installed dependencies for" + f" plugin: {plugin_path.name}. Output:\n{result.stdout}", + "插件管理", + ) + except subprocess.CalledProcessError: + logger.error( + f"Failed to install dependencies for plugin: {plugin_path.name}. " + " Error:\n{e.stderr}" + ) + + +class ShopManage: + @classmethod + @cached(60) + async def get_data(cls) -> dict[str, StorePluginInfo]: + """获取插件信息数据 + + 异常: + ValueError: 访问请求失败 + + 返回: + dict: 插件信息数据 + """ + default_github_url = await GithubUtils.parse_github_url( + DEFAULT_GITHUB_URL + ).get_raw_download_urls("plugins.json") + extra_github_url = await GithubUtils.parse_github_url( + EXTRA_GITHUB_URL + ).get_raw_download_urls("plugins.json") + res = await AsyncHttpx.get(default_github_url) + res2 = await AsyncHttpx.get(extra_github_url) + + # 检查请求结果 + if res.status_code != 200 or res2.status_code != 200: + raise ValueError(f"下载错误, code: {res.status_code}, {res2.status_code}") + + # 解析并合并返回的 JSON 数据 + data1 = json.loads(res.text) + data2 = json.loads(res2.text) + return { + name: StorePluginInfo(**detail) + for name, detail in {**data1, **data2}.items() + } + + @classmethod + def version_check(cls, plugin_info: StorePluginInfo, suc_plugin: dict[str, str]): + """版本检查 + + 参数: + plugin_info: StorePluginInfo + suc_plugin: dict[str, str] + + 返回: + str: 版本号 + """ + module = plugin_info.module + if suc_plugin.get(module) and not cls.check_version_is_new( + plugin_info, suc_plugin + ): + return f"{suc_plugin[module]} (有更新->{plugin_info.version})" + return plugin_info.version + + @classmethod + def check_version_is_new( + cls, plugin_info: StorePluginInfo, suc_plugin: dict[str, str] + ): + """检查版本是否有更新 + + 参数: + plugin_info: StorePluginInfo + suc_plugin: dict[str, str] + + 返回: + bool: 是否有更新 + """ + module = plugin_info.module + return suc_plugin.get(module) and plugin_info.version == suc_plugin[module] + + @classmethod + async def get_loaded_plugins(cls, *args) -> list[tuple[str, str]]: + """获取已加载的插件 + + 返回: + list[str]: 已加载的插件 + """ + return await PluginInfo.filter(load_status=True).values_list(*args) + + @classmethod + async def get_plugins_info(cls) -> BuildImage | str: + """插件列表 + + 返回: + BuildImage | str: 返回消息 + """ + data: dict[str, StorePluginInfo] = await cls.get_data() + column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] + plugin_list = await cls.get_loaded_plugins("module", "version") + suc_plugin = {p[0]: (p[1] or "0.1") for p in plugin_list} + data_list = [ + [ + "已安装" if plugin_info[1].module in suc_plugin else "", + id, + plugin_info[0], + plugin_info[1].description, + plugin_info[1].author, + cls.version_check(plugin_info[1], suc_plugin), + plugin_info[1].plugin_type_name, + ] + for id, plugin_info in enumerate(data.items()) + ] + return await ImageTemplate.table_page( + "插件列表", + "通过添加/移除插件 ID 来管理插件", + column_name, + data_list, + text_style=row_style, + ) + + @classmethod + async def add_plugin(cls, plugin_id: str) -> str: + """添加插件 + + 参数: + plugin_id: 插件id或模块名 + + 返回: + str: 返回消息 + """ + data: dict[str, StorePluginInfo] = await cls.get_data() + try: + plugin_key = await cls._resolve_plugin_key(plugin_id) + except ValueError as e: + return str(e) + plugin_list = await cls.get_loaded_plugins("module") + plugin_info = data[plugin_key] + if plugin_info.module in [p[0] for p in plugin_list]: + return f"插件 {plugin_key} 已安装,无需重复安装" + is_external = True + if plugin_info.github_url is None: + plugin_info.github_url = DEFAULT_GITHUB_URL + is_external = False + version_split = plugin_info.version.split("-") + if len(version_split) > 1: + github_url_split = plugin_info.github_url.split("/tree/") + plugin_info.github_url = f"{github_url_split[0]}/tree/{version_split[1]}" + logger.info(f"正在安装插件 {plugin_key}...") + await cls.install_plugin_with_repo( + plugin_info.github_url, + plugin_info.module_path, + plugin_info.is_dir, + is_external, + ) + return f"插件 {plugin_key} 安装成功! 重启后生效" + + @classmethod + async def install_plugin_with_repo( + cls, github_url: str, module_path: str, is_dir: bool, is_external: bool = False + ): + files: list[str] + repo_api: RepoAPI + repo_info = GithubUtils.parse_github_url(github_url) + logger.debug(f"成功获取仓库信息: {repo_info}", "插件管理") + for repo_api in GithubUtils.iter_api_strategies(): + try: + await repo_api.parse_repo_info(repo_info) + break + except Exception as e: + logger.warning( + f"获取插件文件失败: {e} | API类型: {repo_api.strategy}", "插件管理" + ) + continue + else: + raise ValueError("所有API获取插件文件失败,请检查网络连接") + if module_path == ".": + module_path = "" + replace_module_path = module_path.replace(".", "/") + files = repo_api.get_files( + module_path=replace_module_path + ("" if is_dir else ".py"), + is_dir=is_dir, + ) + download_urls = [await repo_info.get_raw_download_urls(file) for file in files] + base_path = BASE_PATH / "plugins" if is_external else BASE_PATH + base_path = base_path if module_path else base_path / repo_info.repo + download_paths: list[Path | str] = [base_path / file for file in files] + logger.debug(f"插件下载路径: {download_paths}", "插件管理") + result = await AsyncHttpx.gather_download_file(download_urls, download_paths) + for _id, success in enumerate(result): + if not success: + break + else: + # 安装依赖 + plugin_path = base_path / "/".join(module_path.split(".")) + try: + req_files = repo_api.get_files( + f"{replace_module_path}/{REQ_TXT_FILE_STRING}", False + ) + req_files.extend( + repo_api.get_files(f"{replace_module_path}/requirement.txt", False) + ) + logger.debug(f"获取插件依赖文件列表: {req_files}", "插件管理") + req_download_urls = [ + await repo_info.get_raw_download_urls(file) for file in req_files + ] + req_paths: list[Path | str] = [plugin_path / file for file in req_files] + logger.debug(f"插件依赖文件下载路径: {req_paths}", "插件管理") + if req_files: + result = await AsyncHttpx.gather_download_file( + req_download_urls, req_paths + ) + for success in result: + if not success: + raise Exception("插件依赖文件下载失败") + logger.debug(f"插件依赖文件列表: {req_paths}", "插件管理") + install_requirement(plugin_path) + except ValueError as e: + logger.warning("未获取到依赖文件路径...", e=e) + return True + raise Exception("插件下载失败...") + + @classmethod + async def remove_plugin(cls, plugin_id: str) -> str: + """移除插件 + + 参数: + plugin_id: 插件id或模块名 + + 返回: + str: 返回消息 + """ + data: dict[str, StorePluginInfo] = await cls.get_data() + try: + plugin_key = await cls._resolve_plugin_key(plugin_id) + except ValueError as e: + return str(e) + plugin_info = data[plugin_key] + path = BASE_PATH + if plugin_info.github_url: + path = BASE_PATH / "plugins" + for p in plugin_info.module_path.split("."): + path = path / p + if not plugin_info.is_dir: + path = Path(f"{path}.py") + if not path.exists(): + return f"插件 {plugin_key} 不存在..." + logger.debug(f"尝试移除插件 {plugin_key} 文件: {path}", "插件管理") + if plugin_info.is_dir: + shutil.rmtree(path) + else: + path.unlink() + await PluginInitManager.remove(f"zhenxun.{plugin_info.module_path}") + return f"插件 {plugin_key} 移除成功! 重启后生效" + + @classmethod + async def search_plugin(cls, plugin_name_or_author: str) -> BuildImage | str: + """搜索插件 + + 参数: + plugin_name_or_author: 插件名称或作者 + + 返回: + BuildImage | str: 返回消息 + """ + data: dict[str, StorePluginInfo] = await cls.get_data() + plugin_list = await cls.get_loaded_plugins("module", "version") + suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list} + filtered_data = [ + (id, plugin_info) + for id, plugin_info in enumerate(data.items()) + if plugin_name_or_author.lower() in plugin_info[0].lower() + or plugin_name_or_author.lower() in plugin_info[1].author.lower() + ] + + data_list = [ + [ + "已安装" if plugin_info[1].module in suc_plugin else "", + id, + plugin_info[0], + plugin_info[1].description, + plugin_info[1].author, + cls.version_check(plugin_info[1], suc_plugin), + plugin_info[1].plugin_type_name, + ] + for id, plugin_info in filtered_data + ] + if not data_list: + return "未找到相关插件..." + column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] + return await ImageTemplate.table_page( + "插件列表", + "通过添加/移除插件 ID 来管理插件", + column_name, + data_list, + text_style=row_style, + ) + + @classmethod + async def update_plugin(cls, plugin_id: str) -> str: + """更新插件 + + 参数: + plugin_id: 插件id + + 返回: + str: 返回消息 + """ + data: dict[str, StorePluginInfo] = await cls.get_data() + try: + plugin_key = await cls._resolve_plugin_key(plugin_id) + except ValueError as e: + return str(e) + logger.info(f"尝试更新插件 {plugin_key}", "插件管理") + plugin_info = data[plugin_key] + plugin_list = await cls.get_loaded_plugins("module", "version") + suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list} + if plugin_info.module not in [p[0] for p in plugin_list]: + return f"插件 {plugin_key} 未安装,无法更新" + logger.debug(f"当前插件列表: {suc_plugin}", "插件管理") + if cls.check_version_is_new(plugin_info, suc_plugin): + return f"插件 {plugin_key} 已是最新版本" + is_external = True + if plugin_info.github_url is None: + plugin_info.github_url = DEFAULT_GITHUB_URL + is_external = False + await cls.install_plugin_with_repo( + plugin_info.github_url, + plugin_info.module_path, + plugin_info.is_dir, + is_external, + ) + return f"插件 {plugin_key} 更新成功! 重启后生效" + + @classmethod + async def update_all_plugin(cls) -> str: + """更新插件 + + 参数: + plugin_id: 插件id + + 返回: + str: 返回消息 + """ + data: dict[str, StorePluginInfo] = await cls.get_data() + plugin_list = list(data.keys()) + update_failed_list = [] + update_success_list = [] + result = "--已更新{}个插件 {}个失败 {}个成功--" + logger.info(f"尝试更新全部插件 {plugin_list}", "插件管理") + for plugin_key in plugin_list: + try: + plugin_info = data[plugin_key] + plugin_list = await cls.get_loaded_plugins("module", "version") + suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list} + if plugin_info.module not in [p[0] for p in plugin_list]: + logger.debug(f"插件 {plugin_key} 未安装,跳过", "插件管理") + continue + if cls.check_version_is_new(plugin_info, suc_plugin): + logger.debug(f"插件 {plugin_key} 已是最新版本,跳过", "插件管理") + continue + logger.info(f"正在更新插件 {plugin_key}", "插件管理") + is_external = True + if plugin_info.github_url is None: + plugin_info.github_url = DEFAULT_GITHUB_URL + is_external = False + await cls.install_plugin_with_repo( + plugin_info.github_url, + plugin_info.module_path, + plugin_info.is_dir, + is_external, + ) + update_success_list.append(plugin_key) + except Exception as e: + logger.error(f"更新插件 {plugin_key} 失败: {e}", "插件管理") + update_failed_list.append(plugin_key) + if not update_success_list and not update_failed_list: + return "全部插件已是最新版本" + if update_success_list: + result += "\n* 以下插件更新成功:\n\t- {}".format( + "\n\t- ".join(update_success_list) + ) + if update_failed_list: + result += "\n* 以下插件更新失败:\n\t- {}".format( + "\n\t- ".join(update_failed_list) + ) + return ( + result.format( + len(update_success_list) + len(update_failed_list), + len(update_failed_list), + len(update_success_list), + ) + + "\n重启后生效" + ) + + @classmethod + async def _resolve_plugin_key(cls, plugin_id: str) -> str: + data: dict[str, StorePluginInfo] = await cls.get_data() + if is_number(plugin_id): + idx = int(plugin_id) + if idx < 0 or idx >= len(data): + raise ValueError("插件ID不存在...") + return list(data.keys())[idx] + elif isinstance(plugin_id, str): + if plugin_id not in [v.module for k, v in data.items()]: + raise ValueError("插件Module不存在...") + return {v.module: k for k, v in data.items()}[plugin_id] diff --git a/zhenxun/builtin_plugins/plugin_store/models.py b/zhenxun/builtin_plugins/plugin_store/models.py new file mode 100644 index 00000000..df65dd56 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/models.py @@ -0,0 +1,43 @@ +from nonebot.compat import model_dump +from pydantic import BaseModel + +from zhenxun.utils.enum import PluginType + +type2name: dict[str, str] = { + "NORMAL": "普通插件", + "ADMIN": "管理员插件", + "SUPERUSER": "超级用户插件", + "ADMIN_SUPERUSER": "管理员/超级用户插件", + "DEPENDANT": "依赖插件", + "HIDDEN": "其他插件", +} + + +class StorePluginInfo(BaseModel): + """插件信息""" + + module: str + """模块名""" + module_path: str + """模块路径""" + description: str + """简介""" + usage: str + """用法""" + author: str + """作者""" + version: str + """版本""" + plugin_type: PluginType + """插件类型""" + is_dir: bool + """是否为文件夹插件""" + github_url: str | None = None + """github链接""" + + @property + def plugin_type_name(self): + return type2name[self.plugin_type.value] + + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) diff --git a/zhenxun/builtin_plugins/record_request.py b/zhenxun/builtin_plugins/record_request.py index e894d61e..d4b0c694 100644 --- a/zhenxun/builtin_plugins/record_request.py +++ b/zhenxun/builtin_plugins/record_request.py @@ -1,24 +1,27 @@ -import time +import asyncio from datetime import datetime +import random +import time -import nonebot from nonebot import on_message, on_request -from nonebot.adapters.onebot.v11 import ActionFailed +from nonebot.adapters.onebot.v11 import ( + ActionFailed, + FriendRequestEvent, + GroupRequestEvent, +) from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v11 import FriendRequestEvent, GroupRequestEvent from nonebot.adapters.onebot.v12 import Bot as v12Bot from nonebot.plugin import PluginMetadata from nonebot_plugin_apscheduler import scheduler from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.models.fg_request import FgRequest from zhenxun.models.friend_user import FriendUser from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType -from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils base_config = Config.get("invite_manager") @@ -39,20 +42,26 @@ __plugin_meta__ = PluginMetadata( help="是否自动同意好友添加", type=bool, default_value=False, - ) + ), + RegisterConfig( + module="invite_manager", + key="AUTO_ADD_GROUP", + value=False, + help="是否自动同意邀请入群", + type=bool, + default_value=False, + ), ], - ).dict(), + ).to_dict(), ) class Timer: - data: dict[str, float] = {} + data: dict[str, float] = {} # noqa: RUF012 @classmethod def check(cls, uid: int | str): - if uid not in cls.data: - return True - return time.time() - cls.data[uid] > 5 * 60 + return True if uid not in cls.data else time.time() - cls.data[uid] > 5 * 60 @classmethod def clear(cls): @@ -69,29 +78,20 @@ _t = on_message(priority=999, block=False, rule=lambda: False) @friend_req.handle() async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSession): - superuser = nonebot.get_driver().config.platform_superusers["qq"][0] if event.user_id and Timer.check(event.user_id): - logger.debug(f"收录好友请求...", "好友请求", target=event.user_id) + logger.debug("收录好友请求...", "好友请求", target=event.user_id) user = await bot.get_stranger_info(user_id=event.user_id) nickname = user["nickname"] # sex = user["sex"] # age = str(user["age"]) comment = event.comment - if superuser: - superuser = int(superuser) - await MessageUtils.build_message( - f"*****一份好友申请*****\n" - f"昵称:{nickname}({event.user_id})\n" - f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n" - f"日期:{str(datetime.now()).split('.')[0]}\n" - f"备注:{event.comment}" - ).send(target=PlatformUtils.get_target(bot, superuser)) if base_config.get("AUTO_ADD_FRIEND"): logger.debug( - f"已开启好友请求自动同意,成功通过该请求", + "已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id, ) + await asyncio.sleep(random.randint(1, 10)) await bot.set_friend_add_request(flag=event.flag, approve=True) await FriendUser.create( user_id=str(user["user_id"]), user_name=user["nickname"] @@ -103,7 +103,7 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi user_id=str(event.user_id), handle_type__isnull=True, ).update(handle_type=RequestHandleType.EXPIRE) - await FgRequest.create( + f = await FgRequest.create( request_type=RequestType.FRIEND, platform=session.platform, bot_id=bot.self_id, @@ -112,93 +112,137 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi nickname=nickname, comment=comment, ) + await PlatformUtils.send_superuser( + bot, + f"*****一份好友申请*****\n" + f"ID: {f.id}\n" + f"昵称:{nickname}({event.user_id})\n" + f"自动同意:{'√' if base_config.get('AUTO_ADD_FRIEND') else '×'}\n" + f"日期:{str(datetime.now()).split('.')[0]}\n" + f"备注:{event.comment}", + ) else: - logger.debug(f"好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id) + logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id) @group_req.handle() async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession): - superuser = nonebot.get_driver().config.platform_superusers["qq"][0] - # 邀请 - if event.sub_type == "invite": - if str(event.user_id) in bot.config.superusers: - try: - logger.debug( - f"超级用户自动同意加入群聊", - "群聊请求", - session=event.user_id, - target=event.group_id, - ) - if isinstance(bot, v11Bot): - group_info = await bot.get_group_info(group_id=event.group_id) - max_member_count = group_info["max_member_count"] - member_count = group_info["member_count"] - else: - group_info = await bot.get_group_info(group_id=str(event.group_id)) - max_member_count = 0 - member_count = 0 - await GroupConsole.update_or_create( - group_id=str(event.group_id), - defaults={ - "group_name": group_info["group_name"], - "max_member_count": max_member_count, - "member_count": member_count, - "group_flag": 1, - }, - ) - await bot.set_group_add_request( - flag=event.flag, sub_type="invite", approve=True - ) - except ActionFailed as e: - logger.error( - "超级用户自动同意加入群聊发生错误", - "群聊请求", - session=event.user_id, - target=event.group_id, - e=e, - ) - else: - if Timer.check(f"{event.user_id}:{event.group_id}"): - logger.debug( - f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", - "群聊请求", - target=event.group_id, - ) - nickname = await FriendUser.get_user_name(str(event.user_id)) - await Text( - f"*****一份入群申请*****\n" - f"申请人:{nickname}({event.user_id})\n" - f"群聊:{event.group_id}\n" - f"邀请日期:{datetime.now().replace(microsecond=0)}" - ).send_to(target=TargetQQPrivate(user_id=superuser), bot=bot) - await bot.send_private_msg( - user_id=event.user_id, - message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n" - "请确保已经群主或群管理沟通过!\n" - "等待管理员处理吧!", - ) - # 旧请求全部设置为过期 - await FgRequest.filter( - request_type=RequestType.GROUP, - user_id=str(event.user_id), - group_id=str(event.group_id), - handle_type__isnull=True, - ).update(handle_type=RequestHandleType.EXPIRE) - await FgRequest.create( - request_type=RequestType.GROUP, - platform=session.platform, - bot_id=bot.self_id, - flag=event.flag, - user_id=str(event.user_id), - nickname=nickname, - group_id=str(event.group_id), - ) + if event.sub_type != "invite": + return + if str(event.user_id) in bot.config.superusers or base_config.get("AUTO_ADD_GROUP"): + try: + logger.debug( + "超级用户自动同意加入群聊或开启自动同意入群", + "群聊请求", + session=event.user_id, + target=event.group_id, + ) + group, _ = await GroupConsole.update_or_create( + group_id=str(event.group_id), + defaults={ + "group_name": "", + "max_member_count": 0, + "member_count": 0, + "group_flag": 1, + }, + ) + await bot.set_group_add_request( + flag=event.flag, sub_type="invite", approve=True + ) + if isinstance(bot, v11Bot): + group_info = await bot.get_group_info(group_id=event.group_id) + max_member_count = group_info["max_member_count"] + member_count = group_info["member_count"] else: - logger.debug( - f"群聊请求五分钟内重复, 已忽略", - "群聊请求", - target=f"{event.user_id}:{event.group_id}", - ) + group_info = await bot.get_group_info(group_id=str(event.group_id)) + max_member_count = 0 + member_count = 0 + group.max_member_count = max_member_count + group.member_count = member_count + group.group_name = group_info["group_name"] + await group.save( + update_fields=["group_name", "max_member_count", "member_count"] + ) + except ActionFailed as e: + logger.error( + "超超级用户自动同意加入群聊或开启自动同意入群,加入群组发生错误", + "群聊请求", + session=event.user_id, + target=event.group_id, + e=e, + ) + if str(event.user_id) not in bot.config.superusers and base_config.get( + "AUTO_ADD_GROUP" + ): + # 非超级用户邀请自动加入群组 + nickname = await FriendUser.get_user_name(str(event.user_id)) + f = await FgRequest.create( + request_type=RequestType.GROUP, + platform=session.platform, + bot_id=bot.self_id, + flag=event.flag, + user_id=str(event.user_id), + nickname=nickname, + group_id=str(event.group_id), + handle_type=RequestHandleType.APPROVE, + ) + await PlatformUtils.send_superuser( + bot, + f"*****一份入群申请*****\n" + f"ID:{f.id}\n" + f"申请人:{nickname}({event.user_id})\n群聊:" + f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}\n" + "注: 该请求已自动同意", + ) + await asyncio.sleep(random.randint(1, 5)) + await bot.send_private_msg( + user_id=event.user_id, + message=f"管理员已开启自动同意群组邀请,请不要让{BotConfig.self_nickname}受委屈哦(狠狠监控)" + "\n在群组中 群组管理员与群主 允许使用管理员帮助" + "(包括ban与功能开关等)\n请在群组中发送 '管理员帮助'", + ) + elif Timer.check(f"{event.user_id}:{event.group_id}"): + logger.debug( + f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", + "群聊请求", + target=event.group_id, + ) + nickname = await FriendUser.get_user_name(str(event.user_id)) + await bot.send_private_msg( + user_id=event.user_id, + message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n" + "请确保已经群主或群管理沟通过!\n" + "等待管理员处理吧!", + ) + # 旧请求全部设置为过期 + await FgRequest.filter( + request_type=RequestType.GROUP, + user_id=str(event.user_id), + group_id=str(event.group_id), + handle_type__isnull=True, + ).update(handle_type=RequestHandleType.EXPIRE) + f = await FgRequest.create( + request_type=RequestType.GROUP, + platform=session.platform, + bot_id=bot.self_id, + flag=event.flag, + user_id=str(event.user_id), + nickname=nickname, + group_id=str(event.group_id), + ) + await PlatformUtils.send_superuser( + bot, + f"*****一份入群申请*****\n" + f"ID:{f.id}\n" + f"申请人:{nickname}({event.user_id})\n群聊:" + f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}", + ) + else: + logger.debug( + "群聊请求五分钟内重复, 已忽略", + "群聊请求", + target=f"{event.user_id}:{event.group_id}", + ) @scheduler.scheduled_job( @@ -207,3 +251,7 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio ) async def _(): Timer.clear() + + +async def _(): + Timer.clear() diff --git a/zhenxun/builtin_plugins/restart/__init__.py b/zhenxun/builtin_plugins/restart/__init__.py new file mode 100644 index 00000000..8856db13 --- /dev/null +++ b/zhenxun/builtin_plugins/restart/__init__.py @@ -0,0 +1,96 @@ +import os +from pathlib import Path +import platform + +import aiofiles +import nonebot +from nonebot import on_command +from nonebot.adapters import Bot +from nonebot.params import ArgStr +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot.rule import to_me +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils + +__plugin_meta__ = PluginMetadata( + name="重启", + description="执行脚本重启真寻", + usage=""" + 重启 + """.strip(), + extra=PluginExtraData( + author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER + ).to_dict(), +) + + +_matcher = on_command( + "重启", + permission=SUPERUSER, + rule=to_me(), + priority=1, + block=True, +) + +driver = nonebot.get_driver() + + +RESTART_MARK = Path() / "is_restart" + +RESTART_FILE = Path() / "restart.sh" + + +@_matcher.got( + "flag", + prompt=f"确定是否重启{BotConfig.self_nickname}?\n确定请回复[是|好|确定]\n(重启失败咱们将失去联系,请谨慎!)", +) +async def _(bot: Bot, session: Uninfo, flag: str = ArgStr("flag")): + if flag.lower() in {"true", "是", "好", "确定", "确定是"}: + await MessageUtils.build_message( + f"开始重启{BotConfig.self_nickname}..请稍等..." + ).send() + async with aiofiles.open(RESTART_MARK, "w", encoding="utf8") as f: + await f.write(f"{bot.self_id} {session.user.id}") + logger.info("开始重启真寻...", "重启", session=session) + if str(platform.system()).lower() == "windows": + import sys + + python = sys.executable + os.execl(python, python, *sys.argv) + else: + os.system("./restart.sh") # noqa: ASYNC221 + else: + await MessageUtils.build_message("已取消操作...").send() + + +@driver.on_bot_connect +async def _(bot: Bot): + if str(platform.system()).lower() != "windows" and not RESTART_FILE.exists(): + async with aiofiles.open(RESTART_FILE, "w", encoding="utf8") as f: + await f.write( + "pid=$(netstat -tunlp | grep " + + str(bot.config.port) + + " | awk '{print $7}')\n" + "pid=${pid%/*}\n" + "kill -9 $pid\n" + "sleep 3\n" + "python3 bot.py" + ) + os.system("chmod +x ./restart.sh") # noqa: ASYNC221 + logger.info("已自动生成 restart.sh(重启) 文件,请检查脚本是否与本地指令符合...") + if RESTART_MARK.exists(): + async with aiofiles.open(RESTART_MARK, encoding="utf8") as f: + bot_id, user_id = (await f.read()).split() + if bot := nonebot.get_bot(bot_id): + if target := PlatformUtils.get_target(user_id=user_id): + await MessageUtils.build_message( + f"{BotConfig.self_nickname}已成功重启!" + ).send(target, bot=bot) + RESTART_MARK.unlink() diff --git a/zhenxun/builtin_plugins/scheduler/auto_backup.py b/zhenxun/builtin_plugins/scheduler/auto_backup.py index 92f1119b..af5ef382 100644 --- a/zhenxun/builtin_plugins/scheduler/auto_backup.py +++ b/zhenxun/builtin_plugins/scheduler/auto_backup.py @@ -1,5 +1,5 @@ -import shutil from pathlib import Path +import shutil from nonebot_plugin_apscheduler import scheduler @@ -18,14 +18,7 @@ Config.add_plugin_config( Config.add_plugin_config( "_backup", "BACKUP_DIR_OR_FILE", - [ - "data/black_word", - "data/configs", - "data/statistics", - "data/word_bank", - "data/manager", - "configs", - ], + ["data"], help="备份的文件夹或文件", default_value=[], type=list[str], @@ -39,24 +32,25 @@ Config.add_plugin_config( minute=25, ) async def _(): - if Config.get_config("_backup", "BACKUP_FLAG"): - _backup_path = Path() / "backup" - _backup_path.mkdir(exist_ok=True, parents=True) - if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"): - for path_file in backup_dir_or_file: - try: - path = Path(path_file) - _p = _backup_path / path_file - if path.exists(): - if path.is_dir(): - if _p.exists(): - shutil.rmtree(_p, ignore_errors=True) - shutil.copytree(path_file, _p) - else: - if _p.exists(): - _p.unlink() - shutil.copy(path_file, _p) - logger.debug(f"已完成自动备份:{path_file}", "自动备份") - except Exception as e: - logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e) - logger.info("自动备份成功...", "自动备份") + if not Config.get_config("_backup", "BACKUP_FLAG"): + return + _backup_path = Path() / "backup" + _backup_path.mkdir(exist_ok=True, parents=True) + if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"): + for path_file in backup_dir_or_file: + try: + path = Path(path_file) + _p = _backup_path / path_file + if path.exists(): + if path.is_dir(): + if _p.exists(): + shutil.rmtree(_p, ignore_errors=True) + shutil.copytree(path_file, _p) + else: + if _p.exists(): + _p.unlink() + shutil.copy(path_file, _p) + logger.debug(f"已完成自动备份:{path_file}", "自动备份") + except Exception as e: + logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e) + logger.info("自动备份成功...", "自动备份") diff --git a/zhenxun/builtin_plugins/scheduler/chat_check.py b/zhenxun/builtin_plugins/scheduler/chat_check.py index 599dff35..d7559665 100644 --- a/zhenxun/builtin_plugins/scheduler/chat_check.py +++ b/zhenxun/builtin_plugins/scheduler/chat_check.py @@ -1,15 +1,25 @@ from datetime import datetime, timedelta import nonebot -import pytz from nonebot_plugin_apscheduler import scheduler +import pytz +from zhenxun.configs.config import Config from zhenxun.models.chat_history import ChatHistory from zhenxun.models.group_console import GroupConsole from zhenxun.models.task_info import TaskInfo from zhenxun.services.log import logger from zhenxun.utils.platform import PlatformUtils +Config.add_plugin_config( + "chat_check", + "STATUS", + True, + help="是否开启群组两日内未发送任何消息,关闭该群全部被动", + default_value=True, + type=bool, +) + @scheduler.scheduled_job( "cron", @@ -17,28 +27,32 @@ from zhenxun.utils.platform import PlatformUtils minute=40, ) async def _(): + if not Config.get_config("chat_history", "FLAG"): + logger.debug("未开启历史发言记录,过滤群组发言检测...") + return + if not Config.get_config("chat_check", "STATUS"): + logger.debug("未开启群组聊天时间检查,过滤群组发言检测...") + return """检测群组发言时间并禁用全部被动""" update_list = [] - for bot in nonebot.get_bots().values(): - group_list, _ = await PlatformUtils.get_group_list(bot) - group_list = [g for g in group_list if g.channel_id == None] - for group in group_list: - try: - last_message = ( - await ChatHistory.filter(group_id=group.group_id) - .annotate() - .order_by("-create_time") - .first() - ) - if last_message: - now = datetime.now(pytz.timezone("Asia/Shanghai")) - if modules := await TaskInfo.annotate().values_list( - "module", flat=True - ): + if modules := await TaskInfo.annotate().values_list("module", flat=True): + for bot in nonebot.get_bots().values(): + group_list, _ = await PlatformUtils.get_group_list(bot, True) + for group in group_list: + try: + last_message = ( + await ChatHistory.filter(group_id=group.group_id) + .annotate() + .order_by("-create_time") + .first() + ) + if last_message: + now = datetime.now(pytz.timezone("Asia/Shanghai")) if now - timedelta(days=2) > last_message.create_time: _group, _ = await GroupConsole.get_or_create( group_id=group.group_id, channel_id__isnull=True ) + modules = [f"<{module}" for module in modules] _group.block_task = ",".join(modules) + "," # type: ignore update_list.append(_group) logger.info( @@ -46,9 +60,9 @@ async def _(): "Chat检测", target=_group.group_id, ) - except Exception as e: - logger.error( - "检测群组发言时间失败...", "Chat检测", target=group.group_id - ) + except Exception: + logger.error( + "检测群组发言时间失败...", "Chat检测", target=group.group_id + ) if update_list: await GroupConsole.bulk_update(update_list, ["block_task"], 10) diff --git a/zhenxun/builtin_plugins/scheduler/morning.py b/zhenxun/builtin_plugins/scheduler/morning.py index d12e0765..1ea7b003 100644 --- a/zhenxun/builtin_plugins/scheduler/morning.py +++ b/zhenxun/builtin_plugins/scheduler/morning.py @@ -1,12 +1,13 @@ import nonebot +from nonebot.adapters import Bot from nonebot.plugin import PluginMetadata from nonebot_plugin_apscheduler import scheduler -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.configs.utils import PluginExtraData, Task -from zhenxun.models.task_info import TaskInfo from zhenxun.services.log import logger +from zhenxun.utils.common_utils import CommonUtils from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import broadcast_group @@ -20,27 +21,21 @@ __plugin_meta__ = PluginMetadata( version="0.1", plugin_type=PluginType.HIDDEN, tasks=[ - Task(module="group_welcome", name="进群欢迎"), - Task(module="refund_group_remind", name="退群提醒"), + Task( + module="morning_goodnight", + name="早晚安", + create_status=False, + default_status=False, + ) ], - ).dict(), + ).to_dict(), ) driver = nonebot.get_driver() -@driver.on_startup -async def _(): - if not await TaskInfo.exists(module="morning_goodnight"): - await TaskInfo.create( - module="morning_goodnight", - name="早晚安", - status=True, - ) - - -async def check(group_id: str) -> bool: - return not await TaskInfo.is_block("morning_goodnight", group_id) +async def check(bot: Bot, group_id: str) -> bool: + return not await CommonUtils.task_is_block(bot, "morning_goodnight", group_id) # 早上好 @@ -63,7 +58,10 @@ async def _(): ) async def _(): message = MessageUtils.build_message( - [f"{NICKNAME}要睡觉了,你们也要早点睡呀", IMAGE_PATH / "zhenxun" / "sleep.jpg"] + [ + f"{BotConfig.self_nickname}要睡觉了,你们也要早点睡呀", + IMAGE_PATH / "zhenxun" / "sleep.jpg", + ] ) await broadcast_group( message, diff --git a/zhenxun/builtin_plugins/scripts.py b/zhenxun/builtin_plugins/scripts.py index 3059f870..27705301 100644 --- a/zhenxun/builtin_plugins/scripts.py +++ b/zhenxun/builtin_plugins/scripts.py @@ -1,11 +1,13 @@ from asyncio.exceptions import TimeoutError +import aiofiles import nonebot -import ujson as json from nonebot.drivers import Driver from nonebot_plugin_apscheduler import scheduler +import ujson as json from zhenxun.configs.path_config import TEXT_PATH +from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger from zhenxun.utils.http_utils import AsyncHttpx @@ -19,8 +21,8 @@ async def update_city(): 这里直接更新,避免插件内代码重复 """ china_city = TEXT_PATH / "china_city.json" - data = {} if not china_city.exists(): + data = {} try: logger.debug("开始更新城市列表...") res = await AsyncHttpx.get( @@ -38,7 +40,7 @@ async def update_city(): city_data = json.loads(res.text) for city in city_data.keys(): data[provinces_data[province]].append(city_data[city]) - with open(china_city, "w", encoding="utf8") as f: + async with aiofiles.open(china_city, "w", encoding="utf8") as f: json.dump(data, f, indent=4, ensure_ascii=False) logger.info("自动更新城市列表完成.....") except TimeoutError as e: @@ -46,7 +48,7 @@ async def update_city(): except ValueError as e: logger.warning("自动城市列表失败.....", e=e) except Exception as e: - logger.error(f"自动城市列表未知错误", e=e) + logger.error("自动城市列表未知错误", e=e) # 自动更新城市列表 @@ -57,3 +59,31 @@ async def update_city(): ) async def _(): await update_city() + + +@driver.on_startup +async def _(): + """开启/禁用插件格式修改""" + _, is_create = await GroupConsole.get_or_create(group_id=133133133) + """标记""" + if is_create: + data_list = [] + for group in await GroupConsole.all(): + if group.block_plugin: + if modules := group.block_plugin.split(","): + block_plugin = "".join( + (f"{module}," if module.startswith("<") else f"<{module},") + for module in modules + if module.strip() + ) + group.block_plugin = block_plugin.replace("<,", "") + if group.block_task: + if modules := group.block_task.split(","): + block_task = "".join( + (f"{module}," if module.startswith("<") else f"<{module},") + for module in modules + if module.strip() + ) + group.block_task = block_task.replace("<,", "") + data_list.append(group) + await GroupConsole.bulk_update(data_list, ["block_plugin", "block_task"], 10) diff --git a/zhenxun/builtin_plugins/shop/__init__.py b/zhenxun/builtin_plugins/shop/__init__.py index f31af2c6..32897811 100644 --- a/zhenxun/builtin_plugins/shop/__init__.py +++ b/zhenxun/builtin_plugins/shop/__init__.py @@ -2,23 +2,29 @@ from nonebot.adapters import Bot, Event from nonebot.plugin import PluginMetadata from nonebot_plugin_alconna import ( Alconna, + AlconnaQuery, Args, Arparma, + Match, + Option, + Query, Subcommand, UniMessage, UniMsg, on_alconna, + store_true, ) -from nonebot_plugin_session import EventSession -from nonebot_plugin_userinfo import EventUserInfo, UserInfo +from nonebot_plugin_uninfo import Uninfo -from zhenxun.configs.utils import BaseBlock, PluginExtraData +from zhenxun.configs.utils import BaseBlock, Command, PluginExtraData, RegisterConfig from zhenxun.services.log import logger +from zhenxun.utils.depends import UserName from zhenxun.utils.enum import BlockType, PluginType from zhenxun.utils.exception import GoodsNotFound from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils -from ._data_source import ShopManage +from ._data_source import ShopManage, gold_rank __plugin_meta__ = PluginMetadata( name="商店", @@ -30,24 +36,45 @@ __plugin_meta__ = PluginMetadata( 我的道具 使用道具 [名称/Id] 购买道具 [名称/Id] + 金币排行 ?[num=10] + 金币总排行 ?[num=10] """.strip(), extra=PluginExtraData( author="HibiKier", version="0.1", plugin_type=PluginType.NORMAL, menu_type="商店", + commands=[ + Command(command="我的金币"), + Command(command="我的道具"), + Command(command="购买道具"), + Command(command="使用道具"), + Command(command="金币排行"), + Command(command="金币总排行"), + ], limits=[BaseBlock(check_type=BlockType.GROUP)], - ).dict(), + configs=[ + RegisterConfig( + key="style", + value="zhenxun", + help="商店样式类型,[normal, zhenxun]", + default_value="zhenxun", + ) + ], + ).to_dict(), ) +from .goods_register import * # noqa: F403 _matcher = on_alconna( Alconna( "商店", + Option("--all", action=store_true), Subcommand("my-cost", help_text="我的金币"), Subcommand("my-props", help_text="我的道具"), - Subcommand("buy", Args["name", str]["num", int, 1], help_text="购买道具"), - Subcommand("use", Args["name", str]["num?", int, 1], help_text="使用道具"), + Subcommand("buy", Args["name?", str]["num?", int], help_text="购买道具"), + Subcommand("use", Args["name?", str]["num?", int], help_text="使用道具"), + Subcommand("gold-list", Args["num?", int], help_text="金币排行"), ), priority=5, block=True, @@ -68,68 +95,80 @@ _matcher.shortcut( ) _matcher.shortcut( - "购买道具", + "购买道具(?P.*?)", command="商店", - arguments=["buy", "{%0}"], + arguments=["buy", "{name}"], prefix=True, ) _matcher.shortcut( - "使用道具", + "使用道具(?P.*?)", command="商店", - arguments=["use", "{%0}"], + arguments=["use", "{name}"], + prefix=True, +) + +_matcher.shortcut( + "金币排行", + command="商店", + arguments=["gold-list"], + prefix=True, +) + +_matcher.shortcut( + r"金币总排行", + command="商店", + arguments=["--all", "gold-list"], prefix=True, ) @_matcher.assign("$main") -async def _(session: EventSession, arparma: Arparma): - image = await ShopManage.build_shop_image() +async def _(session: Uninfo, arparma: Arparma): + image = await ShopManage.get_shop_image() logger.info("查看商店", arparma.header_result, session=session) await MessageUtils.build_message(image).send() @_matcher.assign("my-cost") -async def _(session: EventSession, arparma: Arparma): - if session.id1: - logger.info("查看金币", arparma.header_result, session=session) - gold = await ShopManage.my_cost(session.id1, session.platform) - await MessageUtils.build_message(f"你的当前余额: {gold}").send(reply_to=True) - else: - await MessageUtils.build_message(f"用户id为空...").send(reply_to=True) +async def _(session: Uninfo, arparma: Arparma): + logger.info("查看金币", arparma.header_result, session=session) + gold = await ShopManage.my_cost( + session.user.id, PlatformUtils.get_platform(session) + ) + await MessageUtils.build_message(f"你的当前余额: {gold}").send(reply_to=True) @_matcher.assign("my-props") -async def _( - session: EventSession, arparma: Arparma, user_info: UserInfo = EventUserInfo() -): - if session.id1: - logger.info("查看道具", arparma.header_result, session=session) - if image := await ShopManage.my_props( - session.id1, - user_info.user_displayname or user_info.user_name, - session.platform, - ): - await MessageUtils.build_message(image.pic2bytes()).finish(reply_to=True) - return await MessageUtils.build_message(f"你的道具为空捏...").send( - reply_to=True - ) - else: - await MessageUtils.build_message(f"用户id为空...").send(reply_to=True) +async def _(session: Uninfo, arparma: Arparma, nickname: str = UserName()): + logger.info("查看道具", arparma.header_result, session=session) + if image := await ShopManage.my_props( + session.user.id, + nickname, + PlatformUtils.get_platform(session), + ): + await MessageUtils.build_message(image.pic2bytes()).finish(reply_to=True) + return await MessageUtils.build_message("你的道具为空捏...").send(reply_to=True) @_matcher.assign("buy") -async def _(session: EventSession, arparma: Arparma, name: str, num: int): - if session.id1: - logger.info( - f"购买道具 {name}, 数量: {num}", - arparma.header_result, - session=session, - ) - result = await ShopManage.buy_prop(session.id1, name, num, session.platform) - await MessageUtils.build_message(result).send(reply_to=True) - else: - await MessageUtils.build_message(f"用户id为空...").send(reply_to=True) +async def _( + session: Uninfo, + arparma: Arparma, + name: Match[str], + num: Query[int] = AlconnaQuery("num", 1), +): + if not name.available: + await MessageUtils.build_message( + "请在指令后跟需要购买的道具名称或id..." + ).finish(reply_to=True) + logger.info( + f"购买道具 {name}, 数量: {num}", + arparma.header_result, + session=session, + ) + result = await ShopManage.buy_prop(session.user.id, name.result, num.result) + await MessageUtils.build_message(result).send(reply_to=True) @_matcher.assign("use") @@ -137,21 +176,51 @@ async def _( bot: Bot, event: Event, message: UniMsg, - session: EventSession, + session: Uninfo, arparma: Arparma, - name: str, - num: int, + name: Match[str], + num: Query[int] = AlconnaQuery("num", 1), ): + if not name.available: + await MessageUtils.build_message( + "请在指令后跟需要使用的道具名称或id..." + ).finish(reply_to=True) try: - result = await ShopManage.use(bot, event, session, message, name, num, "") + result = await ShopManage.use( + bot, event, session, message, name.result, num.result, "" + ) logger.info( - f"使用道具 {name}, 数量: {num}", arparma.header_result, session=session + f"使用道具 {name.result}, 数量: {num.result}", + arparma.header_result, + session=session, ) if isinstance(result, str): await MessageUtils.build_message(result).send(reply_to=True) elif isinstance(result, UniMessage): await result.finish(reply_to=True) except GoodsNotFound: - await MessageUtils.build_message(f"没有找到道具 {name} 或道具数量不足...").send( - reply_to=True - ) + await MessageUtils.build_message( + f"没有找到道具 {name.result} 或道具数量不足..." + ).send(reply_to=True) + + +@_matcher.assign("gold-list") +async def _( + session: Uninfo, arparma: Arparma, num: Query[int] = AlconnaQuery("num", 10) +): + if num.result > 50: + await MessageUtils.build_message("排行榜人数不能超过50哦...").finish() + gid = session.group.id if session.group else None + if not arparma.find("all") and not gid: + await MessageUtils.build_message( + "私聊中无法查看 '金币排行',请发送 '金币总排行'" + ).finish() + if arparma.find("all"): + gid = None + result = await gold_rank(session, gid, num.result) + logger.info( + "查看金币排行", + arparma.header_result, + session=session, + ) + await MessageUtils.build_message(result).send(reply_to=True) diff --git a/zhenxun/builtin_plugins/shop/_data_source.py b/zhenxun/builtin_plugins/shop/_data_source.py index 19f7fc23..b5d693b4 100644 --- a/zhenxun/builtin_plugins/shop/_data_source.py +++ b/zhenxun/builtin_plugins/shop/_data_source.py @@ -1,33 +1,40 @@ import asyncio +from collections.abc import Callable +from datetime import datetime, timedelta import inspect import time from types import MappingProxyType -from typing import Any, Callable, Literal +from typing import Any, ClassVar, Literal from nonebot.adapters import Bot, Event +from nonebot.compat import model_dump from nonebot_plugin_alconna import UniMessage, UniMsg -from nonebot_plugin_session import EventSession -from pydantic import BaseModel, create_model +from nonebot_plugin_uninfo import Uninfo +from pydantic import BaseModel, Field, create_model +from tortoise.expressions import Q -from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.friend_user import FriendUser from zhenxun.models.goods_info import GoodsInfo +from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.user_console import UserConsole from zhenxun.models.user_gold_log import UserGoldLog from zhenxun.models.user_props_log import UserPropsLog from zhenxun.services.log import logger from zhenxun.utils.enum import GoldHandle, PropHandle -from zhenxun.utils.image_utils import BuildImage, ImageTemplate, text2image +from zhenxun.utils.image_utils import BuildImage, ImageTemplate +from zhenxun.utils.platform import PlatformUtils -ICON_PATH = IMAGE_PATH / "shop_icon" +from .config import ICON_PATH, PLATFORM_PATH, base_config +from .html_image import html_image +from .normal_image import normal_image class Goods(BaseModel): - name: str """商品名称""" - before_handle: list[Callable] = [] + before_handle: list[Callable] = Field(default_factory=list) """使用前函数""" - after_handle: list[Callable] = [] + after_handle: list[Callable] = Field(default_factory=list) """使用后函数""" func: Callable | None = None """使用函数""" @@ -39,17 +46,16 @@ class Goods(BaseModel): """单次使用最大次数""" model: Any = None """model""" - session: EventSession | None = None - """EventSession""" + session: Uninfo | None = None + """Uninfo""" class ShopParam(BaseModel): - goods_name: str """商品名称""" - user_id: int + user_id: str """用户id""" - group_id: int + group_id: str | None """群聊id""" bot: Any """bot""" @@ -59,24 +65,93 @@ class ShopParam(BaseModel): """道具单次使用数量""" text: str """text""" - send_success_msg: bool = True + send_success_msg: ClassVar[bool] = True """是否发送使用成功信息""" - max_num_limit: int = 1 + max_num_limit: ClassVar[int] = 1 """单次使用最大次数""" - session: EventSession | None = None - """EventSession""" + session: Uninfo | None = None + """Uninfo""" + message: UniMsg + """UniMessage""" + extra_data: ClassVar[dict[str, Any]] = {} + """额外数据""" + + class Config: + arbitrary_types_allowed = True + + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) + + +async def gold_rank( + session: Uninfo, group_id: str | None, num: int +) -> BuildImage | str: + query = UserConsole + if group_id: + uid_list = await GroupInfoUser.filter(group_id=group_id).values_list( + "user_id", flat=True + ) + if uid_list: + query = query.filter(user_id__in=uid_list) + user_list = await query.annotate().order_by("-gold").values_list("user_id", "gold") + if not user_list: + return "当前还没有人拥有金币哦..." + user_id_list = [user[0] for user in user_list] + if session.user.id in user_id_list: + index = user_id_list.index(session.user.id) + 1 + else: + index = "-1(未统计)" + user_list = user_list[:num] if num < len(user_list) else user_list + friend_user = await FriendUser.filter(user_id__in=user_id_list).values_list( + "user_id", "user_name" + ) + uid2name = {user[0]: user[1] for user in friend_user} + if diff_id := set(user_id_list).difference(set(uid2name.keys())): + group_user = await GroupInfoUser.filter(user_id__in=diff_id).values_list( + "user_id", "user_name" + ) + for g in group_user: + uid2name[g[0]] = g[1] + column_name = ["排名", "-", "名称", "金币", "平台"] + data_list = [] + platform = PlatformUtils.get_platform(session) + for i, user in enumerate(user_list): + ava_bytes = await PlatformUtils.get_user_avatar( + user[0], platform, session.self_id + ) + data_list.append( + [ + f"{i + 1}", + (ava_bytes, 30, 30) if platform == "qq" else "", + uid2name.get(user[0]), + user[1], + (PLATFORM_PATH.get(platform), 30, 30), + ] + ) + if group_id: + title = "金币群组内排行" + tip = f"你的排名在本群第 {index} 位哦!" + else: + title = "金币全局排行" + tip = f"你的排名在全局第 {index} 位哦!" + return await ImageTemplate.table_page(title, tip, column_name, data_list) class ShopManage: + uuid2goods: dict[str, Goods] = {} # noqa: RUF012 - uuid2goods: dict[str, Goods] = {} + @classmethod + async def get_shop_image(cls) -> bytes: + if base_config.get("style") == "zhenxun": + return await html_image() + return await normal_image() @classmethod def __build_params( cls, bot: Bot, event: Event, - session: EventSession, + session: Uninfo, message: UniMsg, goods: Goods, num: int, @@ -91,28 +166,35 @@ class ShopManage: num: 数量 text: 其他信息 """ + group_id = None + if session.group: + group_id = ( + session.group.parent.id if session.group.parent else session.group.id + ) _kwargs = goods.params model = goods.model( **{ "goods_name": goods.name, "bot": bot, "event": event, - "user_id": session.id1, - "group_id": session.id3 or session.id2, + "user_id": session.user.id, + "group_id": group_id, "num": num, "text": text, "session": session, + "message": message, } ) return model, { **_kwargs, "_bot": bot, "event": event, - "user_id": session.id1, - "group_id": session.id3 or session.id2, + "user_id": session.user.id, + "group_id": group_id, "num": num, "text": text, "goods_name": goods.name, + "message": message, } @classmethod @@ -120,8 +202,7 @@ class ShopManage: cls, args: MappingProxyType, param: ShopParam, - session: EventSession, - message: UniMsg, + session: Uninfo, **kwargs, ) -> list[Any]: """解析参数 @@ -136,7 +217,7 @@ class ShopManage: param_list = [] _bot = param.bot param.bot = None - param_json = param.dict() + param_json = {**param.to_dict(), **param.extra_data} param_json["bot"] = _bot for par in args.keys(): if par in ["shop_param"]: @@ -144,7 +225,7 @@ class ShopManage: elif par in ["session"]: param_list.append(session) elif par in ["message"]: - param_list.append(message) + param_list.append(kwargs.get("message")) elif par not in ["args", "kwargs"]: param_list.append(param_json.get(par)) if kwargs.get(par) is not None: @@ -170,24 +251,22 @@ class ShopManage: if fun_list: for func in fun_list: args = inspect.signature(func).parameters - if args and list(args.keys())[0] != "kwargs": + if args and next(iter(args.keys())) != "kwargs": if asyncio.iscoroutinefunction(func): await func(*cls.__parse_args(args, param, **kwargs)) else: func(*cls.__parse_args(args, param, **kwargs)) + elif asyncio.iscoroutinefunction(func): + await func(**kwargs) else: - if asyncio.iscoroutinefunction(func): - await func(**kwargs) - else: - func(**kwargs) + func(**kwargs) @classmethod async def __run( cls, goods: Goods, param: ShopParam, - session: EventSession, - message: UniMsg, + session: Uninfo, **kwargs, ) -> str | UniMessage | None: """运行道具函数 @@ -201,29 +280,25 @@ class ShopManage: """ args = inspect.signature(goods.func).parameters # type: ignore if goods.func: - if args and list(args.keys())[0] != "kwargs": - if asyncio.iscoroutinefunction(goods.func): - return await goods.func( - *cls.__parse_args(args, param, session, message, **kwargs) - ) - else: - return goods.func( - *cls.__parse_args(args, param, session, message, **kwargs) - ) + if args and next(iter(args.keys())) != "kwargs": + return ( + await goods.func(*cls.__parse_args(args, param, session, **kwargs)) + if asyncio.iscoroutinefunction(goods.func) + else goods.func(*cls.__parse_args(args, param, session, **kwargs)) + ) + if asyncio.iscoroutinefunction(goods.func): + return await goods.func( + **kwargs, + ) else: - if asyncio.iscoroutinefunction(goods.func): - return await goods.func( - **kwargs, - ) - else: - return goods.func(**kwargs) + return goods.func(**kwargs) @classmethod async def use( cls, bot: Bot, event: Event, - session: EventSession, + session: Uninfo, message: UniMsg, goods_name: str, num: int, @@ -244,26 +319,31 @@ class ShopManage: str | MessageFactory | None: 使用完成后返回信息 """ if goods_name.isdigit(): - user = await UserConsole.get_user(user_id=session.id1) # type: ignore - uuid = list(user.props.keys())[int(goods_name)] - goods_info = await GoodsInfo.get_or_none(uuid=uuid) + try: + user = await UserConsole.get_user(user_id=session.user.id) + uuid = list(user.props.keys())[int(goods_name)] + goods_info = await GoodsInfo.get_or_none(uuid=uuid) + except IndexError: + return "仓库中道具不存在..." else: goods_info = await GoodsInfo.get_or_none(goods_name=goods_name) if not goods_info: return f"{goods_name} 不存在..." if goods_info.is_passive: - return f"{goods_name} 是被动道具, 无法使用..." + return f"{goods_info.goods_name} 是被动道具, 无法使用..." goods = cls.uuid2goods.get(goods_info.uuid) if not goods or not goods.func: - return f"{goods_name} 未注册使用函数, 无法使用..." + return f"{goods_info.goods_name} 未注册使用函数, 无法使用..." param, kwargs = cls.__build_params( bot, event, session, message, goods, num, text ) if num > param.max_num_limit: return f"{goods_info.goods_name} 单次使用最大数量为{param.max_num_limit}..." await cls.run_before_after(goods, param, "before", **kwargs) - result = await cls.__run(goods, param, session, message, **kwargs) - await UserConsole.use_props(session.id1, goods_info.uuid, num, session.platform) # type: ignore + result = await cls.__run(goods, param, session, **kwargs) + await UserConsole.use_props( + session.user.id, goods_info.uuid, num, PlatformUtils.get_platform(session) + ) await cls.run_before_after(goods, param, "after", **kwargs) if not result and param.send_success_msg: result = f"使用道具 {goods.name} {num} 次成功!" @@ -296,10 +376,14 @@ class ShopManage: """ if uuid in cls.uuid2goods: raise ValueError("该商品使用函数已被注册!") - kwargs["send_success_msg"] = send_success_msg - kwargs["max_num_limit"] = max_num_limit cls.uuid2goods[uuid] = Goods( - model=create_model(f"{uuid}_model", __base__=ShopParam, **kwargs), + model=create_model( + f"{uuid}_model", + send_success_msg=send_success_msg, + max_num_limit=max_num_limit, + __base__=ShopParam, + extra_data=kwargs, + ), params=kwargs, before_handle=before_handle, after_handle=after_handle, @@ -322,29 +406,37 @@ class ShopManage: 返回: str: 返回小 """ - if name == "神秘药水": - return "你们看看就好啦,这是不可能卖给你们的~" if num < 0: return "购买的数量要大于0!" - goods_list = await GoodsInfo.annotate().order_by("id").all() - goods_list = [ - goods - for goods in goods_list - if goods.goods_limit_time > time.time() or goods.goods_limit_time == 0 - ] + goods_list = ( + await GoodsInfo.filter( + Q(goods_limit_time__gte=time.time()) | Q(goods_limit_time=0) + ) + .annotate() + .order_by("id") + .all() + ) if name.isdigit(): + if int(name) > len(goods_list) or int(name) <= 0: + return "道具编号不存在..." goods = goods_list[int(name) - 1] + elif filter_goods := [g for g in goods_list if g.goods_name == name]: + goods = filter_goods[0] else: - if filter_goods := [g for g in goods_list if g.goods_name == name]: - goods = filter_goods[0] - else: - return "道具名称不存在..." + return "道具名称不存在..." user = await UserConsole.get_user(user_id, platform) price = goods.goods_price * num * goods.goods_discount if user.gold < price: return "糟糕! 您的金币好像不太够哦..." + today = datetime.now() + create_time = today - timedelta( + hours=today.hour, minutes=today.minute, seconds=today.second + ) count = await UserPropsLog.filter( - user_id=user_id, handle=PropHandle.BUY + user_id=user_id, + handle=PropHandle.BUY, + uuid=goods.uuid, + create_time__gte=create_time, ).count() if goods.daily_limit and count >= goods.daily_limit: return "今天的购买已达限制了喔!" @@ -381,15 +473,26 @@ class ShopManage: user = await UserConsole.get_user(user_id, platform) if not user.props: return None + is_change = False + for uuid in list(user.props.keys()): + if user.props[uuid] <= 0: + is_change = True + del user.props[uuid] + if is_change: + await user.save(update_fields=["props"]) result = await GoodsInfo.filter(uuid__in=user.props.keys()).all() data_list = [] uuid2goods = {item.uuid: item for item in result} column_name = ["-", "使用ID", "名称", "数量", "简介"] for i, p in enumerate(user.props): if prop := uuid2goods.get(p): + icon = "" + icon_path = ICON_PATH / prop.icon + if icon_path.exists(): + icon = (icon_path, 33, 33) data_list.append( [ - (ICON_PATH / prop.icon, 33, 33) if prop.icon else "", + icon, i, prop.goods_name, user.props[p], @@ -414,203 +517,3 @@ class ShopManage: """ user = await UserConsole.get_user(user_id, platform) return user.gold - - @classmethod - async def build_shop_image(cls) -> BuildImage: - """制作商店图片 - - 返回: - BuildImage: 商店图片 - """ - goods_lst = await GoodsInfo.get_all_goods() - _dc = {} - font_h = BuildImage.get_text_size("正")[1] - h = 10 - _list: list[GoodsInfo] = [] - for goods in goods_lst: - if goods.goods_limit_time == 0 or time.time() < goods.goods_limit_time: - _list.append(goods) - # A = BuildImage(1100, h, color="#f9f6f2") - total_n = 0 - image_list = [] - for idx, goods in enumerate(_list): - name_image = BuildImage( - 580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf" - ) - await name_image.text( - (15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height" - ) - await name_image.line((380, -5, 280, 45), "#a29ad6", 5) - await name_image.text((390, 0), "售价:", center_type="height") - if goods.goods_discount != 1: - discount_price = int(goods.goods_discount * goods.goods_price) - old_price_image = await BuildImage.build_text_image( - str(goods.goods_price), font_color=(194, 194, 194), size=15 - ) - await old_price_image.line( - ( - 0, - int(old_price_image.height / 2), - old_price_image.width + 1, - int(old_price_image.height / 2), - ), - (0, 0, 0), - ) - await name_image.paste(old_price_image, (440, 0)) - await name_image.text((440, 15), str(discount_price), (255, 255, 255)) - else: - await name_image.text( - (440, 0), - str(goods.goods_price), - (255, 255, 255), - center_type="height", - ) - _tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25) - await name_image.text( - ( - 440 + _tmp.width, - 0, - ), - f" 金币", - center_type="height", - ) - des_image = None - font_img = BuildImage(600, 80, font_size=20, color="#a29ad6") - p = font_img.getsize("简介:")[0] + 20 - if goods.goods_description: - des_list = goods.goods_description.split("\n") - desc = "" - for des in des_list: - if font_img.getsize(des)[0] > font_img.width - p - 20: - msg = "" - tmp = "" - for i in range(len(des)): - if font_img.getsize(tmp)[0] < font_img.width - p - 20: - tmp += des[i] - else: - msg += tmp + "\n" - tmp = des[i] - desc += msg - if tmp: - desc += tmp - else: - desc += des + "\n" - if desc[-1] == "\n": - desc = desc[:-1] - des_image = await text2image(desc, color="#a29ad6") - goods_image = BuildImage( - 600, - (50 + des_image.height) if des_image else 50, - font_size=20, - color="#a29ad6", - font="CJGaoDeGuo.otf", - ) - if des_image: - await goods_image.text((15, 50), "简介:") - await goods_image.paste(des_image, (p, 50)) - await name_image.circle_corner(5) - await goods_image.paste(name_image, (0, 5), center_type="width") - await goods_image.circle_corner(20) - bk = BuildImage( - 1180, - (50 + des_image.height) if des_image else 50, - font_size=15, - color="#f9f6f2", - font="CJGaoDeGuo.otf", - ) - if goods.icon and (ICON_PATH / goods.icon).exists(): - icon = BuildImage(70, 70, background=ICON_PATH / goods.icon) - await bk.paste(icon) - await bk.paste(goods_image, (70, 0)) - n = 0 - _w = 650 - # 添加限时图标和时间 - if goods.goods_limit_time > 0: - n += 140 - _limit_time_logo = BuildImage( - 40, 40, background=f"{IMAGE_PATH}/other/time.png" - ) - await bk.paste(_limit_time_logo, (_w + 50, 0)) - _time_img = await BuildImage.build_text_image("限时!", size=23) - await bk.paste( - _time_img, - (_w + 90, 10), - ) - limit_time = time.strftime( - "%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time) - ).split() - y_m_d = limit_time[0] - _h_m = limit_time[1].split(":") - h_m = _h_m[0] + "时 " + _h_m[1] + "分" - await bk.text((_w + 55, 38), str(y_m_d)) - await bk.text((_w + 65, 57), str(h_m)) - _w += 140 - if goods.goods_discount != 1: - n += 140 - _discount_logo = BuildImage( - 30, 30, background=f"{IMAGE_PATH}/other/discount.png" - ) - await bk.paste(_discount_logo, (_w + 50, 10)) - _tmp = await BuildImage.build_text_image("折扣!", size=23) - await bk.paste(_tmp, (_w + 90, 15)) - _tmp = await BuildImage.build_text_image( - f"{10 * goods.goods_discount:.1f} 折", - size=30, - font_color=(85, 156, 75), - ) - await bk.paste(_tmp, (_w + 50, 44)) - _w += 140 - if goods.daily_limit != 0: - n += 140 - _daily_limit_logo = BuildImage( - 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" - ) - await bk.paste(_daily_limit_logo, (_w + 50, 10)) - _tmp = await BuildImage.build_text_image( - "限购!", - size=23, - ) - await bk.paste(_tmp, (_w + 90, 20)) - _tmp = await BuildImage.build_text_image( - f"{goods.daily_limit}", size=30 - ) - await bk.paste(_tmp, (_w + 72, 45)) - if total_n < n: - total_n = n - if n: - await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5) - # await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5) - - # 添加限时图标和时间 - image_list.append(bk) - # await A.apaste(bk, (0, current_h), True) - # current_h += 90 - h = 0 - current_h = 0 - for img in image_list: - h += img.height + 10 - A = BuildImage(1100, h, color="#f9f6f2") - for img in image_list: - await A.paste(img, (0, current_h)) - current_h += img.height + 10 - w = 950 - if total_n: - w += total_n - h = A.height + 230 + 100 - h = 1000 if h < 1000 else h - shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") - shop = BuildImage(w, h, font_size=20, color="#f9f6f2") - await shop.paste(A, (20, 230)) - await shop.paste(shop_logo, (450, 30)) - await shop.text( - ( - int((1000 - shop.getsize("注【通过 序号 或者 商品名称 购买】")[0]) / 2), - 170, - ), - "注【通过 序号 或者 商品名称 购买】", - ) - await shop.text( - (20, h - 100), - "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~", - ) - return shop diff --git a/zhenxun/builtin_plugins/shop/config.py b/zhenxun/builtin_plugins/shop/config.py new file mode 100644 index 00000000..7fa13a5d --- /dev/null +++ b/zhenxun/builtin_plugins/shop/config.py @@ -0,0 +1,20 @@ +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH + +base_config = Config.get("shop") + +ICON_PATH = IMAGE_PATH / "shop_icon" + + +RANK_ICON_PATH = IMAGE_PATH / "_icon" + +PLATFORM_PATH = { + "dodo": RANK_ICON_PATH / "dodo.png", + "discord": RANK_ICON_PATH / "discord.png", + "kaiheila": RANK_ICON_PATH / "kook.png", + "qq": RANK_ICON_PATH / "qq.png", +} + +LEFT_RIGHT_IMAGE = ["1.png", "2.png", "qq.png"] + +LEFT_RIGHT_PATH = TEMPLATE_PATH / "shop" / "res" / "img" diff --git a/zhenxun/builtin_plugins/shop/goods_register.py b/zhenxun/builtin_plugins/shop/goods_register.py new file mode 100644 index 00000000..cb67bee8 --- /dev/null +++ b/zhenxun/builtin_plugins/shop/goods_register.py @@ -0,0 +1,18 @@ +from zhenxun.models.user_console import UserConsole +from zhenxun.utils.decorator.shop import shop_register + + +@shop_register( + name="神秘药水", + price=999999, + des="鬼知道会有什么效果,要不试试?", + partition="小秘密", + icon="mysterious_potion.png", +) +async def _(user_id: str): + await UserConsole.add_gold( + user_id, + 1000000, + "shop", + ) + return "使用道具神秘药水成功!你滴金币+1000000!" diff --git a/zhenxun/builtin_plugins/shop/html_image.py b/zhenxun/builtin_plugins/shop/html_image.py new file mode 100644 index 00000000..2d7948cb --- /dev/null +++ b/zhenxun/builtin_plugins/shop/html_image.py @@ -0,0 +1,89 @@ +from datetime import datetime +import time + +from nonebot_plugin_htmlrender import template_to_pic +from pydantic import BaseModel +from tortoise.expressions import Q + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.goods_info import GoodsInfo +from zhenxun.utils._build_image import BuildImage + +from .config import ICON_PATH + + +class GoodsItem(BaseModel): + goods_list: list[dict] + """商品列表""" + partition: str + """分区名称""" + + +def get_limit_time(end_time: int): + now = int(time.time()) + if now > end_time: + return None + current_datetime = datetime.fromtimestamp(now) + end_datetime = datetime.fromtimestamp(end_time) + time_difference = end_datetime - current_datetime + total_seconds = time_difference.total_seconds() + hours = int(total_seconds // 3600) + minutes = int((total_seconds % 3600) // 60) + return f"{hours}:{minutes}" + + +def get_discount(price: int, discount: float): + return None if discount == 1.0 else int(price * discount) + + +async def html_image() -> bytes: + """构建图片""" + goods_list = ( + await GoodsInfo.filter( + Q(goods_limit_time__gte=time.time()) | Q(goods_limit_time=0) + ) + .annotate() + .order_by("id") + .all() + ) + partition_dict: dict[str, list[dict]] = {} + for idx, goods in enumerate(goods_list): + if not goods.partition: + goods.partition = "默认分区" + if goods.partition not in partition_dict: + partition_dict[goods.partition] = [] + icon = None + if goods.icon: + path = ICON_PATH / goods.icon + if path.exists(): + icon = ( + "data:image/png;base64," + f"{BuildImage.open(ICON_PATH / goods.icon).pic2bs4()[9:]}" + ) + partition_dict[goods.partition].append( + { + "id": idx + 1, + "price": goods.goods_price, + "discount_price": get_discount(goods.goods_price, goods.goods_discount), + "limit_time": get_limit_time(goods.goods_limit_time), + "daily_limit": goods.daily_limit or "∞", + "name": goods.goods_name, + "icon": icon, + "description": goods.goods_description, + } + ) + data_list = [ + GoodsItem(goods_list=value, partition=partition) + for partition, value in partition_dict.items() + ] + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "shop").absolute()), + template_name="main.html", + templates={"name": BotConfig.self_nickname, "data_list": data_list}, + pages={ + "viewport": {"width": 850, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) diff --git a/zhenxun/builtin_plugins/shop/normal_image.py b/zhenxun/builtin_plugins/shop/normal_image.py new file mode 100644 index 00000000..7b5004cf --- /dev/null +++ b/zhenxun/builtin_plugins/shop/normal_image.py @@ -0,0 +1,207 @@ +import time + +from tortoise.expressions import Q + +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.goods_info import GoodsInfo +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import text2image + +from .config import ICON_PATH + + +async def normal_image() -> bytes: + """制作商店图片 + + 返回: + BuildImage: 商店图片 + """ + h = 10 + goods_list = ( + await GoodsInfo.filter( + Q(goods_limit_time__gte=time.time()) | Q(goods_limit_time=0) + ) + .annotate() + .order_by("id") + .all() + ) + # A = BuildImage(1100, h, color="#f9f6f2") + total_n = 0 + image_list = [] + for idx, goods in enumerate(goods_list): + name_image = BuildImage( + 580, 40, font_size=25, color="#e67b6b", font="CJGaoDeGuo.otf" + ) + await name_image.text( + (15, 0), f"{idx + 1}.{goods.goods_name}", center_type="height" + ) + await name_image.line((380, -5, 280, 45), "#a29ad6", 5) + await name_image.text((390, 0), "售价:", center_type="height") + if goods.goods_discount != 1: + discount_price = int(goods.goods_discount * goods.goods_price) + old_price_image = await BuildImage.build_text_image( + str(goods.goods_price), font_color=(194, 194, 194), size=15 + ) + await old_price_image.line( + ( + 0, + int(old_price_image.height / 2), + old_price_image.width + 1, + int(old_price_image.height / 2), + ), + (0, 0, 0), + ) + await name_image.paste(old_price_image, (440, 0)) + await name_image.text((440, 15), str(discount_price), (255, 255, 255)) + else: + await name_image.text( + (440, 0), + str(goods.goods_price), + (255, 255, 255), + center_type="height", + ) + _tmp = await BuildImage.build_text_image(str(goods.goods_price), size=25) + await name_image.text( + ( + 440 + _tmp.width, + 0, + ), + " 金币", + center_type="height", + ) + des_image = None + font_img = BuildImage(600, 80, font_size=20, color="#a29ad6") + p = font_img.getsize("简介:")[0] + 20 + if goods.goods_description: + des_list = goods.goods_description.split("\n") + desc = "" + for des in des_list: + if font_img.getsize(des)[0] > font_img.width - p - 20: + msg = "" + tmp = "" + for i in range(len(des)): + if font_img.getsize(tmp)[0] < font_img.width - p - 20: + tmp += des[i] + else: + msg += tmp + "\n" + tmp = des[i] + desc += msg + if tmp: + desc += tmp + else: + desc += des + "\n" + if desc[-1] == "\n": + desc = desc[:-1] + des_image = await text2image(desc, color="#a29ad6") + goods_image = BuildImage( + 600, + (50 + des_image.height) if des_image else 50, + font_size=20, + color="#a29ad6", + font="CJGaoDeGuo.otf", + ) + if des_image: + await goods_image.text((15, 50), "简介:") + await goods_image.paste(des_image, (p, 50)) + await name_image.circle_corner(5) + await goods_image.paste(name_image, (0, 5), center_type="width") + await goods_image.circle_corner(20) + bk = BuildImage( + 1180, + (50 + des_image.height) if des_image else 50, + font_size=15, + color="#f9f6f2", + font="CJGaoDeGuo.otf", + ) + if goods.icon and (ICON_PATH / goods.icon).exists(): + icon = BuildImage(70, 70, background=ICON_PATH / goods.icon) + await bk.paste(icon) + await bk.paste(goods_image, (70, 0)) + n = 0 + _w = 650 + # 添加限时图标和时间 + if goods.goods_limit_time > 0: + n += 140 + _limit_time_logo = BuildImage( + 40, 40, background=f"{IMAGE_PATH}/other/time.png" + ) + await bk.paste(_limit_time_logo, (_w + 50, 0)) + _time_img = await BuildImage.build_text_image("限时!", size=23) + await bk.paste( + _time_img, + (_w + 90, 10), + ) + limit_time = time.strftime( + "%Y-%m-%d %H:%M", time.localtime(goods.goods_limit_time) + ).split() + y_m_d = limit_time[0] + _h_m = limit_time[1].split(":") + h_m = f"{_h_m[0]}时 {_h_m[1]}分" + await bk.text((_w + 55, 38), str(y_m_d)) + await bk.text((_w + 65, 57), str(h_m)) + _w += 140 + if goods.goods_discount != 1: + n += 140 + _discount_logo = BuildImage( + 30, 30, background=f"{IMAGE_PATH}/other/discount.png" + ) + await bk.paste(_discount_logo, (_w + 50, 10)) + _tmp = await BuildImage.build_text_image("折扣!", size=23) + await bk.paste(_tmp, (_w + 90, 15)) + _tmp = await BuildImage.build_text_image( + f"{10 * goods.goods_discount:.1f} 折", + size=30, + font_color=(85, 156, 75), + ) + await bk.paste(_tmp, (_w + 50, 44)) + _w += 140 + if goods.daily_limit != 0: + n += 140 + _daily_limit_logo = BuildImage( + 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" + ) + await bk.paste(_daily_limit_logo, (_w + 50, 10)) + _tmp = await BuildImage.build_text_image( + "限购!", + size=23, + ) + await bk.paste(_tmp, (_w + 90, 20)) + _tmp = await BuildImage.build_text_image(f"{goods.daily_limit}", size=30) + await bk.paste(_tmp, (_w + 72, 45)) + total_n = max(total_n, n) + if n: + await bk.line((650, -1, 650 + n, -1), "#a29ad6", 5) + # await bk.aline((650, 80, 650 + n, 80), "#a29ad6", 5) + + # 添加限时图标和时间 + image_list.append(bk) + # await A.apaste(bk, (0, current_h), True) + # current_h += 90 + current_h = 0 + h = sum(img.height + 10 for img in image_list) or 400 + A = BuildImage(1100, h, color="#f9f6f2") + for img in image_list: + await A.paste(img, (0, current_h)) + current_h += img.height + 10 + w = 950 + if total_n: + w += total_n + h = A.height + 230 + 100 + h = max(h, 1000) + shop_logo = BuildImage(100, 100, background=f"{IMAGE_PATH}/other/shop_text.png") + shop = BuildImage(w, h, font_size=20, color="#f9f6f2") + await shop.paste(A, (20, 230)) + await shop.paste(shop_logo, (450, 30)) + tip = "注【通过 购买道具 序号 或者 商品名称 购买】" + await shop.text( + ( + int((1000 - shop.getsize(tip)[0]) / 2), + 170, + ), + "注【通过 序号 或者 商品名称 购买】", + ) + await shop.text( + (20, h - 100), + "神秘药水\t\t售价:9999999金币\n\t\t鬼知道会有什么效果~", + ) + return shop.pic2bytes() diff --git a/zhenxun/builtin_plugins/sign_in/__init__.py b/zhenxun/builtin_plugins/sign_in/__init__.py index ac09256a..0b48a0e7 100644 --- a/zhenxun/builtin_plugins/sign_in/__init__.py +++ b/zhenxun/builtin_plugins/sign_in/__init__.py @@ -1,22 +1,29 @@ from nonebot.plugin import PluginMetadata from nonebot_plugin_alconna import ( Alconna, + AlconnaQuery, Args, Arparma, Option, + Query, on_alconna, store_true, ) from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig +from zhenxun.configs.utils import ( + Command, + PluginCdBlock, + PluginExtraData, + RegisterConfig, +) from zhenxun.services.log import logger from zhenxun.utils.depends import UserName from zhenxun.utils.message import MessageUtils from ._data_source import SignManage -from .goods_register import driver +from .goods_register import driver # noqa: F401 from .utils import clear_sign_data_pic __plugin_meta__ = PluginMetadata( @@ -28,13 +35,19 @@ __plugin_meta__ = PluginMetadata( 指令: 签到 我的签到 - 好感度排行 - 好感度总排行 + 好感度排行 ?[num=10] + 好感度总排行 ?[num=10] * 签到时有 3% 概率 * 2 * """.strip(), extra=PluginExtraData( author="HibiKier", version="0.1", + commands=[ + Command(command="签到"), + Command(command="我的签到"), + Command(command="签到排行"), + Command(command="签到总排行"), + ], configs=[ RegisterConfig( module="send_setu", @@ -72,9 +85,15 @@ __plugin_meta__ = PluginMetadata( default_value=0.05, type=float, ), + RegisterConfig( + key="IMAGE_STYLE", + value="zhenxun", + help="签到图片样式, [normal, zhenxun]", + default_value="zhenxun", + ), ], limits=[PluginCdBlock()], - ).dict(), + ).to_dict(), ) @@ -84,8 +103,7 @@ _sign_matcher = on_alconna( Option("--my", action=store_true, help_text="我的签到"), Option( "-l|--list", - Args["num", int, 10], - action=store_true, + Args["num?", int], help_text="好感度排行", ), Option("-g|--global", action=store_true, help_text="全局排行"), @@ -108,46 +126,58 @@ _sign_matcher.shortcut( prefix=True, ) +_sign_matcher.shortcut( + "签到排行", + command="签到", + arguments=["--list"], + prefix=True, +) + _sign_matcher.shortcut( "好感度总排行", command="签到", - arguments=["--list", "--global"], + arguments=["--global", "--list"], + prefix=True, +) + +_sign_matcher.shortcut( + "签到总排行", + command="签到", + arguments=["--global", "--list"], prefix=True, ) @_sign_matcher.assign("$main") -async def _(session: EventSession, arparma: Arparma, nickname: str = UserName()): - if session.id1: - if path := await SignManage.sign(session, nickname): - logger.info("签到成功", arparma.header_result, session=session) - await MessageUtils.build_message(path).finish() - return MessageUtils.build_message("用户id为空...").send() +async def _(session: Uninfo, arparma: Arparma, nickname: str = UserName()): + path = await SignManage.sign(session, nickname) + logger.info("签到成功", arparma.header_result, session=session) + await MessageUtils.build_message(path).finish() @_sign_matcher.assign("my") -async def _(session: EventSession, arparma: Arparma, nickname: str = UserName()): - if session.id1: - if image := await SignManage.sign(session, nickname, True): - logger.info("查看我的签到", arparma.header_result, session=session) - await MessageUtils.build_message(image).finish() - return MessageUtils.build_message("用户id为空...").send() +async def _(session: Uninfo, arparma: Arparma, nickname: str = UserName()): + path = await SignManage.sign(session, nickname, True) + logger.info("查看我的签到", arparma.header_result, session=session) + await MessageUtils.build_message(path).finish() @_sign_matcher.assign("list") -async def _(session: EventSession, arparma: Arparma, num: int): - gid = session.id3 or session.id2 +async def _( + session: Uninfo, arparma: Arparma, num: Query[int] = AlconnaQuery("num", 10) +): + if num.result > 50: + await MessageUtils.build_message("排行榜人数不能超过50哦...").finish() + gid = session.group.id if session.group else None if not arparma.find("global") and not gid: await MessageUtils.build_message( "私聊中无法查看 '好感度排行',请发送 '好感度总排行'" ).finish() - if session.id1: - if arparma.find("global"): - gid = None - if image := await SignManage.rank(session.id1, num, gid): - logger.info("查看签到排行", arparma.header_result, session=session) - await MessageUtils.build_message(image).finish() - return MessageUtils.build_message("用户id为空...").send() + if arparma.find("global"): + gid = None + image = await SignManage.rank(session, num.result, gid) + logger.info("查看签到排行", arparma.header_result, session=session) + await MessageUtils.build_message(image).send() @scheduler.scheduled_job( @@ -159,4 +189,4 @@ async def _(): clear_sign_data_pic() logger.info("清理日常签到图片数据数据完成...", "签到") except Exception as e: - logger.error(f"清理日常签到图片数据数据失败...", e=e) + logger.error("清理日常签到图片数据数据失败...", e=e) diff --git a/zhenxun/builtin_plugins/sign_in/_data_source.py b/zhenxun/builtin_plugins/sign_in/_data_source.py index 83080b59..607d135b 100644 --- a/zhenxun/builtin_plugins/sign_in/_data_source.py +++ b/zhenxun/builtin_plugins/sign_in/_data_source.py @@ -1,10 +1,10 @@ -import random -import secrets from datetime import datetime from pathlib import Path +import random +import secrets +from nonebot_plugin_uninfo import Uninfo import pytz -from nonebot_plugin_session import EventSession from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.models.friend_user import FriendUser @@ -14,7 +14,7 @@ from zhenxun.models.sign_user import SignUser from zhenxun.models.user_console import UserConsole from zhenxun.services.log import logger from zhenxun.utils.image_utils import BuildImage, ImageTemplate -from zhenxun.utils.utils import get_user_avatar +from zhenxun.utils.platform import PlatformUtils from ._random_event import random_event from .utils import get_card @@ -30,15 +30,14 @@ PLATFORM_PATH = { class SignManage: - @classmethod async def rank( - cls, user_id: str, num: int, group_id: str | None = None - ) -> BuildImage: + cls, session: Uninfo, num: int, group_id: str | None = None + ) -> BuildImage | str: # sourcery skip: avoid-builtin-shadow """好感度排行 参数: - user_id: 用户id + session: Uninfo num: 排行榜数量 group_id: 群组id @@ -50,36 +49,46 @@ class SignManage: user_list = await GroupInfoUser.filter(group_id=group_id).values_list( "user_id", flat=True ) - query = query.filter(user_id__in=user_list) - all_list = ( + if user_list: + query = query.filter(user_id__in=user_list) + user_list = ( await query.annotate() .order_by("-impression") - .values_list("user_id", flat=True) + .values_list("user_id", "impression", "sign_count", "platform") ) - index = all_list.index(user_id) + 1 # type: ignore - user_list = await query.annotate().order_by("-impression").limit(num).all() - user_id_list = [u.user_id for u in user_list] + if not user_list: + return "当前还没有人签到过哦..." + user_id_list = [user[0] for user in user_list] + if session.user.id in user_id_list: + index = user_id_list.index(session.user.id) + 1 + else: + index = "-1(未统计)" + user_list = user_list[:num] if num < len(user_list) else user_list column_name = ["排名", "-", "名称", "好感度", "签到次数", "平台"] friend_list = await FriendUser.filter(user_id__in=user_id_list).values_list( "user_id", "user_name" ) uid2name = {f[0]: f[1] for f in friend_list} - group_member_list = await GroupInfoUser.filter( - user_id__in=user_id_list - ).values_list("user_id", "user_name") - for gm in group_member_list: - uid2name[gm[0]] = gm[1] + if diff_id := set(user_id_list).difference(set(uid2name.keys())): + group_user = await GroupInfoUser.filter(user_id__in=diff_id).values_list( + "user_id", "user_name" + ) + for g in group_user: + uid2name[g[0]] = g[1] data_list = [] + platform = PlatformUtils.get_platform(session) for i, user in enumerate(user_list): - bytes = await get_user_avatar(user.user_id) + bytes = await PlatformUtils.get_user_avatar( + user[0], platform, session.self_id + ) data_list.append( [ - f"{i+1}", - (bytes, 30, 30) if user.platform == "qq" else "", - uid2name.get(user.user_id), - user.impression, - user.sign_count, - (PLATFORM_PATH.get(user.platform), 30, 30), + f"{i + 1}", + (bytes, 30, 30) if user[3] == "qq" else "", + uid2name.get(user[0]), + user[1], + user[2], + (PLATFORM_PATH.get(user[3]), 30, 30), ] ) if group_id: @@ -92,39 +101,45 @@ class SignManage: @classmethod async def sign( - cls, session: EventSession, nickname: str, is_card_view: bool = False - ) -> Path | None: + cls, session: Uninfo, nickname: str, is_card_view: bool = False + ) -> Path: """签到 参数: - session: Session + session: Uninfo nickname: 用户昵称 is_card_view: 是否展示卡片 返回: Path: 卡片路径 """ - if not session.id1: - return None + platform = PlatformUtils.get_platform(session) now = datetime.now(pytz.timezone("Asia/Shanghai")) - user_console = await UserConsole.get_user(session.id1, session.platform) + user_console = await UserConsole.get_user(session.user.id, platform) user, _ = await SignUser.get_or_create( - user_id=session.id1, - defaults={"user_console": user_console, "platform": session.platform}, + user_id=session.user.id, + defaults={"user_console": user_console, "platform": platform}, ) new_log = ( - await SignLog.filter(user_id=session.id1).order_by("-create_time").first() + await SignLog.filter(user_id=session.user.id) + .order_by("-create_time") + .first() ) log_time = None if new_log: log_time = new_log.create_time.astimezone( pytz.timezone("Asia/Shanghai") ).date() - if not is_card_view: - if not new_log or (log_time and log_time != now.date()): - return await cls._handle_sign_in(user, nickname, session) + if not is_card_view and (not new_log or (log_time and log_time != now.date())): + return await cls._handle_sign_in(user, nickname, session) return await get_card( - user, nickname, -1, user_console.gold, "", is_card_view=is_card_view + user, + session, + nickname, + -1, + user_console.gold, + "", + is_card_view=is_card_view, ) @classmethod @@ -132,38 +147,35 @@ class SignManage: cls, user: SignUser, nickname: str, - session: EventSession, + session: Uninfo, ) -> Path: """签到处理 参数: user: SignUser nickname: 用户昵称 - session: Session + session: Uninfo 返回: Path: 卡片路径 """ + platform = PlatformUtils.get_platform(session) impression_added = (secrets.randbelow(99) + 1) / 100 rand = random.random() add_probability = float(user.add_probability) specify_probability = user.specify_probability - if rand + add_probability > 0.97: + if rand + add_probability > 0.97 or rand < specify_probability: impression_added *= 2 - elif rand < specify_probability: - impression_added *= 2 - await SignUser.sign(user, impression_added, session.bot_id, session.platform) + await SignUser.sign(user, impression_added, session.self_id, platform) gold = random.randint(1, 100) gift = random_event(float(user.impression)) if isinstance(gift, int): gold += gift - await UserConsole.add_gold( - user.user_id, gold + gift, "sign_in", session.platform - ) + await UserConsole.add_gold(user.user_id, gold + gift, "sign_in", platform) gift = f"额外金币 +{gift}" else: - await UserConsole.add_gold(user.user_id, gold, "sign_in", session.platform) - await UserConsole.add_props_by_name(user.user_id, gift, 1, session.platform) + await UserConsole.add_gold(user.user_id, gold, "sign_in", platform) + await UserConsole.add_props_by_name(user.user_id, gift, 1, platform) gift += " + 1" logger.info( f"签到成功. score: {user.impression:.2f} " @@ -173,6 +185,7 @@ class SignManage: ) return await get_card( user, + session, nickname, impression_added, gold, diff --git a/zhenxun/builtin_plugins/sign_in/config.py b/zhenxun/builtin_plugins/sign_in/config.py index e2bfdbc6..d2016c5b 100644 --- a/zhenxun/builtin_plugins/sign_in/config.py +++ b/zhenxun/builtin_plugins/sign_in/config.py @@ -36,7 +36,6 @@ level2attitude = { weekdays = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"} lik2level = { - 9999: "9", 400: "8", 270: "7", 200: "6", diff --git a/zhenxun/builtin_plugins/sign_in/goods_register.py b/zhenxun/builtin_plugins/sign_in/goods_register.py index b73cffc0..f7a65359 100644 --- a/zhenxun/builtin_plugins/sign_in/goods_register.py +++ b/zhenxun/builtin_plugins/sign_in/goods_register.py @@ -2,11 +2,12 @@ from decimal import Decimal import nonebot from nonebot.drivers import Driver -from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo from zhenxun.models.sign_user import SignUser from zhenxun.models.user_console import UserConsole -from zhenxun.utils.decorator.shop import NotMeetUseConditionsException, shop_register +from zhenxun.utils.decorator.shop import shop_register +from zhenxun.utils.platform import PlatformUtils driver: Driver = nonebot.get_driver() @@ -32,17 +33,21 @@ driver: Driver = nonebot.get_driver() "favorability_card_2.png", "favorability_card_3.png", ), - **{"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, # type: ignore + **{ + "好感度双倍加持卡Ⅰ_prob": 0.1, + "好感度双倍加持卡Ⅱ_prob": 0.2, + "好感度双倍加持卡Ⅲ_prob": 0.3, + }, # type: ignore ) -async def _(session: EventSession, user_id: int, group_id: int, prob: float): - if session.id1: - user_console = await UserConsole.get_user(session.id1, session.platform) - user, _ = await SignUser.get_or_create( - user_id=user_id, - defaults={"platform": session.platform, "user_console": user_console}, - ) - user.add_probability = Decimal(prob) - await user.save(update_fields=["add_probability"]) +async def _(session: Uninfo, user_id: int, prob: float): + platform = PlatformUtils.get_platform(session) + user_console = await UserConsole.get_user(session.user.id, platform) + user, _ = await SignUser.get_or_create( + user_id=user_id, + defaults={"platform": platform, "user_console": user_console}, + ) + user.add_probability = Decimal(prob) + await user.save(update_fields=["add_probability"]) @shop_register( @@ -53,20 +58,24 @@ async def _(session: EventSession, user_id: int, group_id: int, prob: float): icon="sword.png", ) async def _(user_id: int, group_id: int): - print(user_id, group_id, "使用测试道具") + # print(user_id, group_id, "使用测试道具") + pass @shop_register.before_handle(name="测试道具A", load_status=False) async def _(user_id: int, group_id: int): - print(user_id, group_id, "第一个使用前函数(before handle)") + # print(user_id, group_id, "第一个使用前函数(before handle)") + pass @shop_register.before_handle(name="测试道具A", load_status=False) async def _(user_id: int, group_id: int): - print(user_id, group_id, "第二个使用前函数(before handle)222") - raise NotMeetUseConditionsException("太笨了!") # 抛出异常,阻断使用,并返回信息 + # print(user_id, group_id, "第二个使用前函数(before handle)222") + # raise NotMeetUseConditionsException("太笨了!") # 抛出异常,阻断使用,并返回信息 + pass @shop_register.after_handle(name="测试道具A", load_status=False) async def _(user_id: int, group_id: int): - print(user_id, group_id, "第一个使用后函数(after handle)") + # print(user_id, group_id, "第一个使用后函数(after handle)") + pass diff --git a/zhenxun/builtin_plugins/sign_in/utils.py b/zhenxun/builtin_plugins/sign_in/utils.py index b8fe2853..9faf1120 100644 --- a/zhenxun/builtin_plugins/sign_in/utils.py +++ b/zhenxun/builtin_plugins/sign_in/utils.py @@ -1,19 +1,22 @@ -import os -import random from datetime import datetime from io import BytesIO +import os from pathlib import Path +import random import nonebot -import pytz from nonebot.drivers import Driver +from nonebot_plugin_htmlrender import template_to_pic +from nonebot_plugin_uninfo import Uninfo +import pytz -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.configs.config import BotConfig, Config +from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH from zhenxun.models.sign_log import SignLog from zhenxun.models.sign_user import SignUser +from zhenxun.utils.http_utils import AsyncHttpx from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.utils import get_user_avatar +from zhenxun.utils.platform import PlatformUtils from .config import ( SIGN_BACKGROUND_PATH, @@ -25,19 +28,43 @@ from .config import ( lik2relation, ) +assert ( + len(level2attitude) == len(lik2level) == len(lik2relation) +), "好感度态度、等级、关系长度不匹配!" + +AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160" + driver: Driver = nonebot.get_driver() +base_config = Config.get("sign_in") + + +MORNING_MESSAGE = [ + "早上好,希望今天是美好的一天!", + "醒了吗,今天也要元气满满哦!", + "早上好呀,今天也要开心哦!", + "早安,愿你拥有美好的一天!", +] + +LG_MESSAGE = [ + "今天要早点休息哦~", + "可不要熬夜到太晚呀", + "请尽早休息吧!", + "不要熬夜啦!", +] + @driver.on_startup async def init_image(): SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True) SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True) - await generate_progress_bar_pic() + # await generate_progress_bar_pic() clear_sign_data_pic() async def get_card( user: SignUser, + session: Uninfo, nickname: str, add_impression: float, gold: int | None, @@ -49,6 +76,7 @@ async def get_card( 参数: user: SignUser + session: Uninfo nickname: 用户昵称 impression: 新增的好感度 gold: 金币 @@ -59,6 +87,7 @@ async def get_card( 返回: Path: 卡片路径 """ + await generate_progress_bar_pic() user_id = user.user_id date = datetime.now().date() _type = "view" if is_card_view else "sign" @@ -67,21 +96,27 @@ async def get_card( card_file = Path(SIGN_TODAY_CARD_PATH) / file_name if card_file.exists(): return IMAGE_PATH / "sign" / "today_card" / file_name - else: - if add_impression == -1: - card_file = Path(SIGN_TODAY_CARD_PATH) / view_name - if card_file.exists(): - return card_file - is_card_view = True - return await _generate_card( - user, nickname, add_impression, gold, gift, is_double, is_card_view + if add_impression == -1: + card_file = Path(SIGN_TODAY_CARD_PATH) / view_name + if card_file.exists(): + return card_file + is_card_view = True + return ( + await _generate_html_card( + user, session, nickname, add_impression, gold, gift, is_double, is_card_view ) + if base_config.get("IMAGE_STYLE") == "zhenxun" + else await _generate_card( + user, session, nickname, add_impression, gold, gift, is_double, is_card_view + ) + ) async def _generate_card( user: SignUser, + session: Uninfo, nickname: str, - impression: float, + add_impression: float, gold: int | None, gift: str, is_double: bool = False, @@ -91,8 +126,9 @@ async def _generate_card( 参数: user: SignUser + session: Uninfo nickname: 用户昵称 - impression: 新增的好感度 + add_impression: 新增的好感度 gold: 金币 gift: 礼物 is_double: 是否触发双倍. @@ -107,39 +143,35 @@ async def _generate_card( 140, background=SIGN_BORDER_PATH / "ava_border_01.png", ) - if user.platform == "qq" and (byt := await get_user_avatar(user.user_id)): + if session.user.avatar and ( + byt := await AsyncHttpx.get_content(session.user.avatar) + ): ava = BuildImage(107, 107, background=BytesIO(byt)) else: ava = BuildImage(107, 107, (0, 0, 0)) await ava.circle() await ava_bk.paste(ava, (19, 18)) await ava_bk.paste(ava_border, center_type="center") - add_impression = impression impression = float(user.impression) info_img = BuildImage(250, 150, color=(255, 255, 255, 0), font_size=15) level, next_impression, previous_impression = get_level_and_next_impression( impression ) interpolation = next_impression - impression - if level == "9": - level = "8" - interpolation = 0 await info_img.text((0, 0), f"· 好感度等级:{level} [{lik2relation[level]}]") - await info_img.text((0, 20), f"· {NICKNAME}对你的态度:{level2attitude[level]}") + await info_img.text( + (0, 20), f"· {BotConfig.self_nickname}对你的态度:{level2attitude[level]}" + ) await info_img.text((0, 40), f"· 距离升级还差 {interpolation:.2f} 好感度") bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png") bar = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar.png") - ratio = 1 - (next_impression - user.impression) / ( - next_impression - previous_impression - ) + ratio = 1 - (next_impression - impression) / (next_impression - previous_impression) if next_impression == 0: ratio = 0 await bar.resize(width=int(bar.width * ratio) or 1, height=bar.height) await bar_bk.paste(bar) - font_size = 30 - if "好感度双倍加持卡" in gift: - font_size = 20 + font_size = 20 if "好感度双倍加持卡" in gift else 30 gift_border = BuildImage( 270, 100, @@ -164,9 +196,9 @@ async def _generate_card( nickname, size=50, font_color=(255, 255, 255) ) user_console = await user.user_console - if user_console and user_console.uid: + if user_console and user_console.uid is not None: uid = f"{user_console.uid}".rjust(12, "0") - uid = uid[:4] + " " + uid[4:8] + " " + uid[8:] + uid = f"{uid[:4]} {uid[4:8]} {uid[8:]}" else: uid = "XXXX XXXX XXXX" uid_img = await BuildImage.build_text_image( @@ -189,7 +221,9 @@ async def _generate_card( f"好感度:{user.impression:.2f}", size=30 ) watermark = await BuildImage.build_text_image( - f"{NICKNAME}@{datetime.now().year}", size=15, font_color=(155, 155, 155) + f"{BotConfig.self_nickname}@{datetime.now().year}", + size=15, + font_color=(155, 155, 155), ) today_data = BuildImage(300, 300, color=(255, 255, 255, 0), font_size=20) if is_card_view: @@ -220,9 +254,12 @@ async def _generate_card( default_setu_prob = ( Config.get_config("send_setu", "INITIAL_SETU_PROBABILITY") * 100 # type: ignore ) + setu_prob = ( + default_setu_prob + float(user.impression) if user.impression < 100 else 100 + ) await today_data.text( (0, 50), - f"色图概率:{(default_setu_prob + float(user.impression) if user.impression < 100 else 100):.2f}%", + f"色图概率:{setu_prob:.2f}%", ) await today_data.text((0, 75), f"开箱次数:{(20 + int(user.impression / 3))}") _type = "view" @@ -237,19 +274,15 @@ async def _generate_card( _type = "sign" current_date = datetime.now() current_datetime_str = current_date.strftime("%Y-%m-%d %a %H:%M:%S") - data = current_date.date() - data_img = await BuildImage.build_text_image( + date = current_date.date() + date_img = await BuildImage.build_text_image( f"时间:{current_datetime_str}", size=20 ) await bk.paste(nickname_img, (30, 15)) await bk.paste(uid_img, (30, 85)) await bk.paste(A, (0, 150)) - # await bk.text((30, 167), "Accumulative check-in for") - # _x = bk.getsize("Accumulative check-in for")[0] + sign_day_img.width + 45 - # await bk.paste(sign_day_img, (398, 158)) - # await bk.text((_x, 167), "days") await bk.paste(tip_image, (10, 167)) - await bk.paste(data_img, (220, 370)) + await bk.paste(date_img, (220, 370)) await bk.paste(lik_text1_img, (220, 240)) await bk.paste(lik_text2_img, (262, 234)) await bk.paste(bar_bk, (225, 275)) @@ -257,14 +290,18 @@ async def _generate_card( await bk.paste(today_sign_text_img, (550, 180)) await bk.paste(today_data, (580, 220)) await bk.paste(watermark, (15, 400)) - await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{data}.png") - return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{data}.png" + await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{date}.png") + return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{date}.png" async def generate_progress_bar_pic(): """ 初始化进度条图片 """ + bar_white_file = SIGN_RESOURCE_PATH / "bar_white.png" + if bar_white_file.exists(): + return + bg_2 = (254, 1, 254) bg_1 = (0, 245, 246) @@ -282,11 +319,11 @@ async def generate_progress_bar_pic(): step_g = (bg_2[1] - bg_1[1]) / width step_b = (bg_2[2] - bg_1[2]) / width - for y in range(0, width): + for y in range(width): bg_r = round(bg_1[0] + step_r * y) bg_g = round(bg_1[1] + step_g * y) bg_b = round(bg_1[2] + step_b * y) - for x in range(0, height): + for x in range(height): await A.point((y, x), fill=(bg_r, bg_g, bg_b)) await bk.paste(img_y, (0, 0)) await bk.paste(A, (25, 0)) @@ -304,25 +341,36 @@ async def generate_progress_bar_pic(): await bk.paste(img_y, (0, 0)) await bk.paste(A, (25, 0)) await bk.paste(img_x, (975, 0)) - await bk.save(SIGN_RESOURCE_PATH / "bar_white.png") + await bk.save(bar_white_file) -def get_level_and_next_impression(impression: float) -> tuple[str, int, int]: +def get_level_and_next_impression(impression: float) -> tuple[str, int | float, int]: """获取当前好感等级与下一等级的差距 参数: impression: 好感度 返回: - tuple[str, int, int]: 好感度等级中文,好感度等级,下一等级好感差距 + tuple[str, int, int]: 好感度等级,下一等级好感度要求,已达到的好感度要求 """ - if impression == 0: - return lik2level[10], 10, 0 + keys = list(lik2level.keys()) + level, next_impression, previous_impression = ( + lik2level[keys[-1]], + keys[-2], + keys[-1], + ) for i in range(len(keys)): - if impression > keys[i]: - return lik2level[keys[i]], keys[i - 1], keys[i] - return lik2level[10], 10, 0 + if impression >= keys[i]: + level, next_impression, previous_impression = ( + lik2level[keys[i]], + keys[i - 1], + keys[i], + ) + if i == 0: + next_impression = impression + break + return level, next_impression, previous_impression def clear_sign_data_pic(): @@ -333,3 +381,101 @@ def clear_sign_data_pic(): for file in os.listdir(SIGN_TODAY_CARD_PATH): if str(date) not in file: os.remove(SIGN_TODAY_CARD_PATH / file) + + +async def _generate_html_card( + user: SignUser, + session: Uninfo, + nickname: str, + add_impression: float, + gold: int | None, + gift: str, + is_double: bool = False, + is_card_view: bool = False, +) -> Path: + """生成签到卡片 + + 参数: + user: SignUser + session: Uninfo + nickname: 用户昵称 + add_impression: 新增的好感度 + gold: 金币 + gift: 礼物 + is_double: 是否触发双倍. + is_card_view: 是否展示好感度卡片. + + 返回: + Path: 卡片路径 + """ + impression = float(user.impression) + user_console = await user.user_console + if user_console and user_console.uid is not None: + uid = f"{user_console.uid}".rjust(12, "0") + uid = f"{uid[:4]} {uid[4:8]} {uid[8:]}" + else: + uid = "XXXX XXXX XXXX" + level, next_impression, previous_impression = get_level_and_next_impression( + impression + ) + interpolation = next_impression - impression + message = f"{BotConfig.self_nickname}希望你开心!" + hour = datetime.now().hour + if hour > 6 and hour < 10: + message = random.choice(MORNING_MESSAGE) + elif hour >= 0 and hour < 6: + message = random.choice(LG_MESSAGE) + _impression = f"{add_impression}(×2)" if is_double else add_impression + process = 1 - (next_impression - impression) / ( + next_impression - previous_impression + ) + now = datetime.now() + data = { + "ava_url": PlatformUtils.get_user_avatar_url( + user.user_id, PlatformUtils.get_platform(session), session.self_id + ), + "name": nickname, + "uid": uid, + "sign_count": f"{user.sign_count}", + "message": f"{BotConfig.self_nickname}说: {message}", + "cur_impression": f"{impression:.2f}", + "impression": f"好感度+{_impression}", + "gold": f"金币+{gold}", + "gift": gift, + "level": f"{level} [{lik2relation[level]}]", + "attitude": f"对你的态度: {level2attitude[level]}", + "interpolation": f"{interpolation:.2f}", + "heart2": [1 for _ in range(int(level))], + "heart1": [1 for _ in range(len(lik2level) - int(level) - 1)], + "process": process * 100, + "date": str(now.replace(microsecond=0)), + "font_size": 45, + } + if len(nickname) > 6: + data["font_size"] = 27 + _type = "sign" + if is_card_view: + _type = "view" + value_list = ( + await SignUser.annotate() + .order_by("-impression") + .values_list("user_id", flat=True) + ) + index = value_list.index(user.user_id) + 1 # type: ignore + data["impression"] = f"好感度排名第 {index} 位" + data["gold"] = f"总金币:{gold}" + data["gift"] = "" + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "sign").absolute()), + template_name="main.html", + templates={"data": data}, + pages={ + "viewport": {"width": 465, "height": 926}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + image = BuildImage.open(pic) + date = now.date() + await image.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{date}.png") + return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{date}.png" diff --git a/zhenxun/plugins/statistics/__init__.py b/zhenxun/builtin_plugins/statistics/__init__.py similarity index 99% rename from zhenxun/plugins/statistics/__init__.py rename to zhenxun/builtin_plugins/statistics/__init__.py index 5cf30279..8090e1cf 100644 --- a/zhenxun/plugins/statistics/__init__.py +++ b/zhenxun/builtin_plugins/statistics/__init__.py @@ -25,7 +25,7 @@ statistics_user_file = DATA_PATH / "statistics" / "_prefix_user_count.json" for file in [statistics_group_file, statistics_user_file]: if file.exists(): - with open(file, "r", encoding="utf8") as f: + with open(file, encoding="utf8") as f: data = json.load(f) if not (statistics_group_file.parent / f"{file}.bak").exists(): with open(f"{file}.bak", "w", encoding="utf8") as wf: diff --git a/zhenxun/plugins/statistics/_data_source.py b/zhenxun/builtin_plugins/statistics/_data_source.py similarity index 80% rename from zhenxun/plugins/statistics/_data_source.py rename to zhenxun/builtin_plugins/statistics/_data_source.py index e83707b1..d51cb685 100644 --- a/zhenxun/plugins/statistics/_data_source.py +++ b/zhenxun/builtin_plugins/statistics/_data_source.py @@ -6,12 +6,13 @@ from zhenxun.models.group_console import GroupConsole from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.statistics import Statistics +from zhenxun.utils.echart_utils import ChartUtils +from zhenxun.utils.echart_utils.models import Barh from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType +from zhenxun.utils.image_utils import BuildImage class StatisticsManage: - @classmethod async def get_statistics( cls, @@ -26,12 +27,12 @@ class StatisticsManage: if search_type == "day": day = 1 day_type = "日" - if search_type == "week": - day = 7 - day_type = "周" - if search_type == "month": + elif search_type == "month": day = 30 day_type = "月" + elif search_type == "week": + day = 7 + day_type = "周" if day_type: day_type += f"({day}天)" title = "" @@ -51,7 +52,7 @@ class StatisticsManage: else: title = "功能调用统计" if is_global and not user_id: - title = "全局 " + title + title = f"全局 {title}" return await cls.get_global_statistics(plugin_name, day, title) if user_id: return await cls.get_my_statistics(user_id, group_id, day, title) @@ -74,9 +75,11 @@ class StatisticsManage: .group_by("plugin_name") .values_list("plugin_name", "count") ) - if not data_list: - return "统计数据为空..." - return await cls.__build_image(data_list, title) + return ( + await cls.__build_image(data_list, title) + if data_list + else "统计数据为空..." + ) @classmethod async def get_my_statistics( @@ -93,9 +96,11 @@ class StatisticsManage: .group_by("plugin_name") .values_list("plugin_name", "count") ) - if not data_list: - return "统计数据为空..." - return await cls.__build_image(data_list, title) + return ( + await cls.__build_image(data_list, title) + if data_list + else "统计数据为空..." + ) @classmethod async def get_group_statistics(cls, group_id: str, day: int | None, title: str): @@ -108,23 +113,24 @@ class StatisticsManage: .group_by("plugin_name") .values_list("plugin_name", "count") ) - if not data_list: - return "统计数据为空..." - return await cls.__build_image(data_list, title) + return ( + await cls.__build_image(data_list, title) + if data_list + else "统计数据为空..." + ) @classmethod async def __build_image(cls, data_list: list[tuple[str, int]], title: str): - mat = BuildMat(MatType.BARH) module2count = {x[0]: x[1] for x in data_list} plugin_info = await PluginInfo.filter( - module__in=module2count.keys(), plugin_type=PluginType.NORMAL + module__in=module2count.keys(), + load_status=True, + plugin_type=PluginType.NORMAL, ).all() x_index = [] data = [] for plugin in plugin_info: x_index.append(plugin.name) data.append(module2count.get(plugin.module, 0)) - mat.x_index = x_index - mat.data = data - mat.title = title - return await mat.build() + barh = Barh(data=data, category_data=x_index, title=title) + return await ChartUtils.barh(barh) diff --git a/zhenxun/plugins/statistics/statistics_handle.py b/zhenxun/builtin_plugins/statistics/statistics_handle.py similarity index 89% rename from zhenxun/plugins/statistics/statistics_handle.py rename to zhenxun/builtin_plugins/statistics/statistics_handle.py index fc070e0c..c36d1c64 100644 --- a/zhenxun/plugins/statistics/statistics_handle.py +++ b/zhenxun/builtin_plugins/statistics/statistics_handle.py @@ -10,7 +10,7 @@ from nonebot_plugin_alconna import ( ) from nonebot_plugin_session import EventSession -from zhenxun.configs.utils import PluginExtraData +from zhenxun.configs.utils import Command, PluginExtraData from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils @@ -45,7 +45,16 @@ __plugin_meta__ = PluginMetadata( "全局周功能调用统计", "全局月功能调用统计", """.strip(), - ).dict(), + commands=[ + Command(command="功能调用统计"), + Command(command="日功能调用统计"), + Command(command="周功能调用统计"), + Command(command="我的功能调用统计"), + Command(command="我的日功能调用统计"), + Command(command="我的周功能调用统计"), + Command(command="我的月功能调用统计"), + ], + ).to_dict(), ) diff --git a/zhenxun/builtin_plugins/statistics/statistics_hook.py b/zhenxun/builtin_plugins/statistics/statistics_hook.py new file mode 100644 index 00000000..f3776ece --- /dev/null +++ b/zhenxun/builtin_plugins/statistics/statistics_hook.py @@ -0,0 +1,68 @@ +from datetime import datetime + +from nonebot.adapters import Bot, Event +from nonebot.adapters.onebot.v11 import PokeNotifyEvent +from nonebot.matcher import Matcher +from nonebot.message import run_postprocessor +from nonebot.plugin import PluginMetadata +from nonebot_plugin_apscheduler import scheduler +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.statistics import Statistics +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType + +__plugin_meta__ = PluginMetadata( + name="功能调用统计", + description="功能调用统计", + usage="""""".strip(), + extra=PluginExtraData( + author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN + ).to_dict(), +) + +TEMP_LIST = [] + + +@run_postprocessor +async def _( + matcher: Matcher, + exception: Exception | None, + bot: Bot, + session: EventSession, + event: Event, +): + if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent): + """过滤除poke外的notice""" + return + if session.id1 and matcher.plugin: + plugin = await PluginInfo.get_plugin(module_path=matcher.plugin.module_name) + plugin_type = plugin.plugin_type if plugin else None + if plugin_type == PluginType.NORMAL: + logger.debug(f"提交调用记录: {matcher.plugin_name}...", session=session) + TEMP_LIST.append( + Statistics( + user_id=session.id1, + group_id=session.id3 or session.id2, + plugin_name=matcher.plugin_name, + create_time=datetime.now(), + bot_id=bot.self_id, + ) + ) + + +@scheduler.scheduled_job( + "interval", + minutes=1, +) +async def _(): + try: + call_list = TEMP_LIST.copy() + TEMP_LIST.clear() + if call_list: + await Statistics.bulk_create(call_list) + logger.debug(f"批量添加调用记录 {len(call_list)} 条", "定时任务") + except Exception as e: + logger.error("定时批量添加调用记录", "定时任务", e=e) diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/__init__.py b/zhenxun/builtin_plugins/superuser/bot_manage/__init__.py new file mode 100644 index 00000000..f7923b53 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/bot_manage/__init__.py @@ -0,0 +1,90 @@ +from typing import cast + +import nonebot +from nonebot.adapters import Bot +from nonebot.plugin import PluginMetadata + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.models.bot_console import BotConsole +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.platform import PlatformUtils + +driver = nonebot.get_driver() + +__plugin_meta__ = PluginMetadata( + name="Bot管理", + description="指定bot对象的功能/被动开关和状态", + usage=""" + 指令: + bot被动状态 : bot的被动技能状态 + bot开启/关闭被动[被动名称] : 被动技能开关 + bot开启/关闭所有被动 : 所有被动技能开关 + bot插件列表: bot插件列表状态 : bot插件列表 + bot开启/关闭所有插件 : 所有插件开关 + bot开启/关闭插件[插件名称] : 插件开关 + bot休眠 : bot休眠,屏蔽所有消息 + bot醒来 : bot醒来 + """.strip(), + extra=PluginExtraData( + author="", + version="0.1", + plugin_type=PluginType.SUPERUSER, + ).to_dict(), +) + +from .bot_switch import * # noqa: F403 +from .plugin import * # noqa: F403 +from .task import * # noqa: F403 + + +@driver.on_bot_connect +async def init_bot_console(bot: Bot): + """初始化Bot管理 + + 参数: + bot: Bot + """ + + async def _filter_blocked_items( + items_list: list[str], block_list: list[str] + ) -> list[str]: + """过滤被block的项目 + + 参数: + items_list: 需要过滤的项目列表 + block_list: block列表 + + 返回: + list: 过滤后且经过格式化的项目列表 + """ + return [item for item in items_list if item not in block_list] + + plugin_list = [ + plugin.module + for plugin in await PluginInfo.get_plugins( + plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT, PluginType.ADMIN] + ) + ] + task_list = cast( + list[str], await TaskInfo.filter(status=True).values_list("module", flat=True) + ) + platform = PlatformUtils.get_platform(bot) + bot_data, created = await BotConsole.get_or_create( + bot_id=bot.self_id, platform=platform + ) + + if not created: + task_list = await _filter_blocked_items( + task_list, await bot_data.get_tasks(bot.self_id, False) + ) + plugin_list = await _filter_blocked_items( + plugin_list, await bot_data.get_plugins(bot.self_id, False) + ) + + bot_data.available_plugins = BotConsole.convert_module_format(plugin_list) + bot_data.available_tasks = BotConsole.convert_module_format(task_list) + await bot_data.save(update_fields=["available_plugins", "available_tasks"]) + logger.info("初始化Bot管理完成...") diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/bot_switch.py b/zhenxun/builtin_plugins/superuser/bot_manage/bot_switch.py new file mode 100644 index 00000000..5618fcf7 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/bot_manage/bot_switch.py @@ -0,0 +1,51 @@ +from nonebot_plugin_alconna import AlconnaMatch, Match +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.builtin_plugins.superuser.bot_manage.command import bot_manage +from zhenxun.models.bot_console import BotConsole +from zhenxun.services.log import logger +from zhenxun.utils.message import MessageUtils + + +@bot_manage.assign("bot_switch.enable") +async def enable_bot_switch( + session: Uninfo, + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if not bot_id.available: + await MessageUtils.build_message("bot_id 不能为空").finish() + + else: + logger.info( + f"开启 {bot_id.result} ", + "bot_manage.bot_switch.enable", + session=session, + ) + try: + await BotConsole.set_bot_status(True, bot_id.result) + except ValueError: + await MessageUtils.build_message(f"bot_id {bot_id.result} 不存在").finish() + + await MessageUtils.build_message(f"已开启 {bot_id.result} ").finish() + + +@bot_manage.assign("bot_switch.disable") +async def diasble_bot_switch( + session: Uninfo, + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if not bot_id.available: + await MessageUtils.build_message("bot_id 不能为空").finish() + + else: + logger.info( + f"禁用 {bot_id.result} ", + "bot_manage.bot_switch.disable", + session=session, + ) + try: + await BotConsole.set_bot_status(False, bot_id.result) + except ValueError: + await MessageUtils.build_message(f"bot_id {bot_id.result} 不存在").finish() + + await MessageUtils.build_message(f"已禁用 {bot_id.result} ").finish() diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/command.py b/zhenxun/builtin_plugins/superuser/bot_manage/command.py new file mode 100644 index 00000000..3df9f6bd --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/bot_manage/command.py @@ -0,0 +1,153 @@ +from arclet.alconna import Alconna, Args, Option, Subcommand +from arclet.alconna.action import store_false +from nonebot.permission import SUPERUSER +from nonebot_plugin_alconna import on_alconna + +bot_manage = on_alconna( + Alconna( + "bot_manage", + Subcommand( + "task", + Option( + "list", + action=store_false, + help_text="查看 bot_id 下的所有可用被动", + ), + Option("-b|--bot", Args["bot_id", str], help_text="指定 bot_id"), + Subcommand( + "enable", + Args["feature_name?", str], + ), + Subcommand( + "disable", + Args["feature_name?", str], + ), + ), + Subcommand( + "plugin", + Option( + "list", + action=store_false, + help_text="查看 bot_id 下的所有可用插件", + ), + Option("-b|--bot", Args["bot_id", str], help_text="指定 bot_id"), + Subcommand( + "enable", + Args["plugin_name?", str], + ), + Subcommand( + "disable", + Args["plugin_name?", str], + ), + ), + Subcommand( + "full_function", + Subcommand( + "enable", + Args["bot_id?", str], + ), + Subcommand( + "disable", + Args["bot_id?", str], + ), + ), + Subcommand( + "bot_switch", + Subcommand( + "enable", + Args["bot_id?", str], + ), + Subcommand( + "disable", + Args["bot_id?", str], + ), + ), + ), + permission=SUPERUSER, + priority=5, + block=True, +) + +bot_manage.shortcut( + r"bot被动状态", + command="bot_manage", + arguments=["task", "list"], + prefix=True, +) + +bot_manage.shortcut( + r"bot开启被动\s*(?P.+)", + command="bot_manage", + arguments=["task", "enable", "{name}"], + prefix=True, +) + +bot_manage.shortcut( + r"bot关闭被动\s*(?P.+)", + command="bot_manage", + arguments=["task", "disable", "{name}"], + prefix=True, +) + +bot_manage.shortcut( + r"bot开启(全部|所有)被动", + command="bot_manage", + arguments=["task", "enable"], + prefix=True, +) + +bot_manage.shortcut( + r"bot关闭(全部|所有)被动", + command="bot_manage", + arguments=["task", "disable"], + prefix=True, +) + +bot_manage.shortcut( + r"bot插件列表", + command="bot_manage", + arguments=["plugin", "list"], + prefix=True, +) + +bot_manage.shortcut( + r"bot开启(全部|所有)插件", + command="bot_manage", + arguments=["plugin", "enable"], + prefix=True, +) + +bot_manage.shortcut( + r"bot关闭(全部|所有)插件", + command="bot_manage", + arguments=["plugin", "disable"], + prefix=True, +) + +bot_manage.shortcut( + r"bot开启\s*(?P.+)", + command="bot_manage", + arguments=["plugin", "enable", "{name}"], + prefix=True, +) + +bot_manage.shortcut( + r"bot关闭\s*(?P.+)", + command="bot_manage", + arguments=["plugin", "disable", "{name}"], + prefix=True, +) + +bot_manage.shortcut( + r"bot休眠\s*(?P.+)?", + command="bot_manage", + arguments=["bot_switch", "disable", "{bot_id}"], + prefix=True, +) + +bot_manage.shortcut( + r"bot醒来\s*(?P.+)?", + command="bot_manage", + arguments=["bot_switch", "enable", "{bot_id}"], + prefix=True, +) diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/full_function.py b/zhenxun/builtin_plugins/superuser/bot_manage/full_function.py new file mode 100644 index 00000000..010381f5 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/bot_manage/full_function.py @@ -0,0 +1,51 @@ +from nonebot_plugin_alconna import AlconnaMatch, Match +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.builtin_plugins.superuser.bot_manage.command import bot_manage +from zhenxun.models.bot_console import BotConsole +from zhenxun.services.log import logger +from zhenxun.utils.message import MessageUtils + + +@bot_manage.assign("full_function.enable") +async def enable_full_function( + session: Uninfo, + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if not bot_id.available: + await MessageUtils.build_message("bot_id 不能为空").finish() + + else: + logger.info( + f"开启 {bot_id.result} 的所有可用插件及被动", + "bot_manage.full_function.enable", + session=session, + ) + await BotConsole.enable_all(bot_id.result, "tasks") + await BotConsole.enable_all(bot_id.result, "plugins") + + await MessageUtils.build_message( + f"已开启 {bot_id.result} 的所有插件及被动" + ).finish() + + +@bot_manage.assign("full_function.disable") +async def diasble_full_function( + session: Uninfo, + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if not bot_id.available: + await MessageUtils.build_message("bot_id 不能为空").finish() + + else: + logger.info( + f"禁用 {bot_id.result} 的所有可用插件及被动", + "bot_manage.full_function.disable", + session=session, + ) + await BotConsole.disable_all(bot_id.result, "tasks") + await BotConsole.disable_all(bot_id.result, "plugins") + + await MessageUtils.build_message( + f"已禁用 {bot_id.result} 的所有插件及被动" + ).finish() diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py b/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py new file mode 100644 index 00000000..df6d7f35 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py @@ -0,0 +1,182 @@ +from nonebot_plugin_alconna import AlconnaMatch, Match +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.builtin_plugins.superuser.bot_manage.command import bot_manage +from zhenxun.models.bot_console import BotConsole +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.services.log import logger +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils._image_template import ImageTemplate, RowStyle +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils + + +def task_row_style(column: str, text: str) -> RowStyle: + """被动技能文本风格 + + 参数: + column: 表头 + text: 文本内容 + + 返回: + RowStyle: RowStyle + """ + style = RowStyle() + if column in {"全局状态"}: + style.font_color = "#67C23A" if text == "开启" else "#F56C6C" + return style + + +@bot_manage.assign("plugin.list") +async def bot_plugin(session: Uninfo, bot_id: Match[str] = AlconnaMatch("bot_id")): + logger.info("获取全部 bot 的所有可用插件", "bot_manage.plugin", session=session) + column_name = [ + "ID", + "模块", + "名称", + "全局状态", + "禁用类型", + "加载状态", + "菜单分类", + "作者", + "版本", + "金币花费", + ] + if bot_id.available: + data_dict = { + bot_id.result: await BotConsole.get_plugins( + bot_id=bot_id.result, status=False + ) + } + else: + data_dict = await BotConsole.get_plugins(status=False) + db_plugin_list = await PluginInfo.filter( + load_status=True, plugin_type__not=PluginType.HIDDEN + ).all() + img_list = [] + for __bot_id, tk in data_dict.items(): + column_data = [ + [ + plugin.id, + plugin.module, + plugin.name, + "开启" if plugin.module not in tk else "关闭", + plugin.block_type, + "SUCCESS" if plugin.load_status else "ERROR", + plugin.menu_type, + plugin.author, + plugin.version, + plugin.cost_gold, + ] + for plugin in db_plugin_list + ] + img = await ImageTemplate.table_page( + f"{__bot_id}插件列表", + None, + column_name, + column_data, + text_style=task_row_style, + ) + img_list.append(img) + result = await BuildImage.auto_paste(img_list, 3) + await MessageUtils.build_message(result).finish() + + +@bot_manage.assign("plugin.enable") +async def enable_plugin( + session: Uninfo, + plugin_name: Match[str] = AlconnaMatch("plugin_name"), + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if plugin_name.available: + plugin: PluginInfo | None = await PluginInfo.get_plugin(name=plugin_name.result) + if not plugin: + await MessageUtils.build_message("未找到该插件...").finish() + if bot_id.available: + logger.info( + f"开启 {bot_id.result} 的插件 {plugin_name.result}", + "bot_manage.plugin.disable", + session=session, + ) + await BotConsole.enable_plugin(bot_id.result, plugin.module) + await MessageUtils.build_message( + f"已开启 {bot_id.result} 的插件 {plugin_name.result}" + ).finish() + else: + logger.info( + f"开启全部 bot 的插件: {plugin_name.result}", + "bot_manage.plugin.disable", + session=session, + ) + await BotConsole.enable_plugin(None, plugin.module) + await MessageUtils.build_message( + f"已禁用全部 bot 的插件: {plugin_name.result}" + ).finish() + elif bot_id.available: + logger.info( + f"开启 {bot_id.result} 全部插件", + "bot_manage.plugin.disable", + session=session, + ) + await BotConsole.enable_all(bot_id.result, "plugins") + await MessageUtils.build_message(f"已开启 {bot_id.result} 全部插件").finish() + else: + bot_id_list = await BotConsole.annotate().values_list("bot_id", flat=True) + for __bot_id in bot_id_list: + await BotConsole.enable_all(__bot_id, "plugins") # type: ignore + logger.info( + "开启全部 bot 全部插件", + "bot_manage.plugin.disable", + session=session, + ) + await MessageUtils.build_message("开启全部 bot 全部插件").finish() + + +@bot_manage.assign("plugin.disable") +async def disable_plugin( + session: Uninfo, + plugin_name: Match[str] = AlconnaMatch("plugin_name"), + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if plugin_name.available: + plugin = await PluginInfo.get_plugin(name=plugin_name.result) + if not plugin: + await MessageUtils.build_message("未找到该插件...").finish() + if bot_id.available: + logger.info( + f"禁用 {bot_id.result} 的插件 {plugin_name.result}", + "bot_manage.plugin.disable", + session=session, + ) + await BotConsole.disable_plugin(bot_id.result, plugin.module) + await MessageUtils.build_message( + f"已禁用 {bot_id.result} 的插件 {plugin_name.result}" + ).finish() + else: + logger.info( + f"禁用全部 bot 的插件: {plugin_name.result}", + "bot_manage.plugin.disable", + session=session, + ) + await BotConsole.disable_plugin(None, plugin.module) + await MessageUtils.build_message( + f"已禁用全部 bot 的插件: {plugin_name.result}" + ).finish() + elif bot_id.available: + logger.info( + f"禁用 {bot_id.result} 全部插件", + "bot_manage.plugin.disable", + session=session, + ) + await BotConsole.disable_all(bot_id.result, "plugins") + await MessageUtils.build_message(f"已禁用 {bot_id.result} 全部插件").finish() + else: + bot_id_list = await BotConsole.annotate().values_list("bot_id", flat=True) + for __bot_id in bot_id_list: + await BotConsole.disable_all(__bot_id, "plugins") # type: ignore + logger.info( + "禁用全部 bot 全部插件", + "bot_manage.plugin.disable", + session=session, + ) + await MessageUtils.build_message("禁用全部 bot 全部插件").finish() diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/task.py b/zhenxun/builtin_plugins/superuser/bot_manage/task.py new file mode 100644 index 00000000..005ab188 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/bot_manage/task.py @@ -0,0 +1,164 @@ +from nonebot_plugin_alconna import AlconnaMatch, Match +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.builtin_plugins.superuser.bot_manage.command import bot_manage +from zhenxun.models.bot_console import BotConsole +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils._image_template import RowStyle +from zhenxun.utils.image_utils import ImageTemplate +from zhenxun.utils.message import MessageUtils + + +def task_row_style(column: str, text: str) -> RowStyle: + """被动技能文本风格 + + 参数: + column: 表头 + text: 文本内容 + + 返回: + RowStyle: RowStyle + """ + style = RowStyle() + if column in {"全局状态"}: + style.font_color = "#67C23A" if text == "开启" else "#F56C6C" + return style + + +@bot_manage.assign("task.list") +async def bot_task(session: Uninfo, bot_id: Match[str] = AlconnaMatch("bot_id")): + logger.info("获取全部 bot 的所有可用被动", "bot_manage.task", session=session) + if bot_id.available: + data_dict = { + bot_id.result: await BotConsole.get_tasks( + bot_id=bot_id.result, status=False + ) + } + else: + data_dict = await BotConsole.get_tasks(status=False) + db_task_list = await TaskInfo.all() + column_name = ["ID", "模块", "名称", "全局状态", "运行时间"] + img_list = [] + for __bot_id, tk in data_dict.items(): + column_data = [ + [ + task.id, + task.module, + task.name, + "开启" if task.module not in tk else "关闭", + task.run_time or "-", + ] + for task in db_task_list + ] + img = await ImageTemplate.table_page( + f"{__bot_id}被动技能状态", + None, + column_name, + column_data, + text_style=task_row_style, + ) + img_list.append(img) + result = await BuildImage.auto_paste(img_list, 3) + await MessageUtils.build_message(result).finish() + + +@bot_manage.assign("task.enable") +async def enable_task( + session: Uninfo, + task_name: Match[str] = AlconnaMatch("feature_name"), + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if task_name.available: + task: TaskInfo | None = await TaskInfo.get_or_none(name=task_name.result) + if not task: + await MessageUtils.build_message("未找到被动...").finish() + if bot_id.available: + logger.info( + f"开启 {bot_id.result} 被动的 {task_name.available}", + "bot_manage.task.disable", + session=session, + ) + await BotConsole.enable_task(bot_id.result, task.module) + await MessageUtils.build_message( + f"已开启 {bot_id.result} 被动的 {task_name.available}" + ).finish() + else: + logger.info( + f"开启全部 bot 的被动: {task_name.available}", + "bot_manage.task.disable", + session=session, + ) + await BotConsole.enable_task(None, task.module) + await MessageUtils.build_message( + f"已禁用全部 bot 的被动: {task_name.available}" + ).finish() + elif bot_id.available: + logger.info( + f"开启 {bot_id.result} 全部被动", + "bot_manage.task.disable", + session=session, + ) + await BotConsole.enable_all(bot_id.result, "tasks") + await MessageUtils.build_message(f"已开启 {bot_id.result} 全部被动").finish() + else: + bot_id_list = await BotConsole.annotate().values_list("bot_id", flat=True) + for __bot_id in bot_id_list: + await BotConsole.enable_all(__bot_id, "tasks") # type: ignore + logger.info( + "开启全部 bot 全部被动", + "bot_manage.task.disable", + session=session, + ) + await MessageUtils.build_message("开启全部 bot 全部被动").finish() + + +@bot_manage.assign("task.disable") +async def disable_task( + session: Uninfo, + task_name: Match[str] = AlconnaMatch("feature_name"), + bot_id: Match[str] = AlconnaMatch("bot_id"), +): + if task_name.available: + task: TaskInfo | None = await TaskInfo.get_or_none(name=task_name.result) + if not task: + await MessageUtils.build_message("未找到被动...").finish() + if bot_id.available: + logger.info( + f"禁用 {bot_id.result} 被动的 {task_name.available}", + "bot_manage.task.disable", + session=session, + ) + await BotConsole.disable_task(bot_id.result, task.module) + await MessageUtils.build_message( + f"已禁用 {bot_id.result} 被动的 {task_name.available}" + ).finish() + else: + logger.info( + f"禁用全部 bot 的被动: {task_name.available}", + "bot_manage.task.disable", + session=session, + ) + await BotConsole.disable_task(None, task.module) + await MessageUtils.build_message( + f"已禁用全部 bot 的被动: {task_name.available}" + ).finish() + elif bot_id.available: + logger.info( + f"禁用 {bot_id.result} 全部被动", + "bot_manage.task.disable", + session=session, + ) + await BotConsole.disable_all(bot_id.result, "tasks") + await MessageUtils.build_message(f"已禁用 {bot_id.result} 全部被动").finish() + else: + bot_id_list = await BotConsole.annotate().values_list("bot_id", flat=True) + for __bot_id in bot_id_list: + await BotConsole.disable_all(__bot_id, "tasks") # type: ignore + logger.info( + "禁用全部 bot 全部被动", + "bot_manage.task.disable", + session=session, + ) + await MessageUtils.build_message("禁用全部 bot 全部被动").finish() diff --git a/zhenxun/builtin_plugins/superuser/broadcast/__init__.py b/zhenxun/builtin_plugins/superuser/broadcast/__init__.py index 7395ff90..020a260c 100644 --- a/zhenxun/builtin_plugins/superuser/broadcast/__init__.py +++ b/zhenxun/builtin_plugins/superuser/broadcast/__init__.py @@ -38,7 +38,7 @@ __plugin_meta__ = PluginMetadata( ) ], tasks=[Task(module="broadcast", name="广播")], - ).dict(), + ).to_dict(), ) _matcher = on_command("广播", priority=1, permission=SUPERUSER, block=True) diff --git a/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py b/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py index 617f0d44..540eaeff 100644 --- a/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py +++ b/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py @@ -1,5 +1,5 @@ -import nonebot_plugin_alconna as alc from nonebot.adapters import Bot +import nonebot_plugin_alconna as alc # from nonebot.adapters.discord import Bot as DiscordBot # from nonebot.adapters.dodo import Bot as DodoBot @@ -9,14 +9,13 @@ from nonebot.adapters import Bot from nonebot_plugin_alconna import Image, UniMsg from nonebot_plugin_session import EventSession -from zhenxun.models.task_info import TaskInfo from zhenxun.services.log import logger +from zhenxun.utils.common_utils import CommonUtils from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils class BroadcastManage: - @classmethod async def send( cls, bot: Bot, message: UniMsg, session: EventSession @@ -42,12 +41,13 @@ class BroadcastManage: error_count = 0 for group in group_list: try: - if not await TaskInfo.is_block( - group.group_id, + if not await CommonUtils.task_is_block( + bot, "broadcast", # group.channel_id + group.group_id, ): target = PlatformUtils.get_target( - bot, None, group.channel_id or group.group_id + group_id=group.group_id, channel_id=group.channel_id ) if target: await MessageUtils.build_message(message_list).send( diff --git a/zhenxun/builtin_plugins/superuser/clear_data.py b/zhenxun/builtin_plugins/superuser/clear_data.py index 10770bf9..cf13fea3 100644 --- a/zhenxun/builtin_plugins/superuser/clear_data.py +++ b/zhenxun/builtin_plugins/superuser/clear_data.py @@ -26,7 +26,7 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) @@ -47,10 +47,10 @@ async def _(session: EventSession): await MessageUtils.build_message("开始清理临时数据...").send() size = await _clear_data() await MessageUtils.build_message( - "共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024) + f"共清理了 {size / 1024 / 1024:.2f}MB 的数据..." ).send() logger.info( - "清理临时数据完成,共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024), + f"清理临时数据完成,共清理了 {size / 1024 / 1024:.2f}MB 的数据...", session=session, ) @@ -79,9 +79,7 @@ def _clear_data() -> float: "清理临时数据", e=e, ) - logger.debug( - "清理临时文件夹大小: {:.2f}MB".format(size / 1024 / 1024), "清理临时数据" - ) + logger.debug(f"清理临时文件夹大小: {size / 1024 / 1024:.2f}MB", "清理临时数据") return float(size) @@ -93,6 +91,6 @@ def _clear_data() -> float: async def _(): size = await _clear_data() logger.info( - "自动清理临时数据完成,共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024), + f"自动清理临时数据完成,共清理了 {size / 1024 / 1024:.2f}MB 的数据...", "定时任务", ) diff --git a/zhenxun/builtin_plugins/superuser/exec_sql.py b/zhenxun/builtin_plugins/superuser/exec_sql.py index e99cd8f6..7abad1de 100644 --- a/zhenxun/builtin_plugins/superuser/exec_sql.py +++ b/zhenxun/builtin_plugins/superuser/exec_sql.py @@ -6,8 +6,9 @@ from nonebot_plugin_alconna import UniMsg from nonebot_plugin_session import EventSession from tortoise import Tortoise +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.db_context import TestSQL +from zhenxun.models.ban_console import BanConsole from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.image_utils import ImageTemplate @@ -24,7 +25,7 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) _matcher = on_command( @@ -43,13 +44,31 @@ _table_matcher = on_command( block=True, ) -SELECT_TABLE_SQL = """ +SELECT_TABLE_MYSQL_SQL = """ +SELECT table_name AS name, table_comment AS `desc` +FROM information_schema.tables +WHERE table_schema = DATABASE(); +""" + +SELECT_TABLE_SQLITE_SQL = """ +SELECT name FROM sqlite_master WHERE type='table'; +""" + +SELECT_TABLE_PSQL_SQL = """ select a.tablename as name,d.description as desc from pg_tables a left join pg_class c on relname=tablename - left join pg_description d on oid=objoid and objsubid=0 where a.schemaname = 'public' + left join pg_description d on oid=objoid and objsubid=0 + where a.schemaname = 'public' """ +type2sql = { + "mysql": SELECT_TABLE_MYSQL_SQL, + "sqlite": SELECT_TABLE_SQLITE_SQL, + "postgres": SELECT_TABLE_PSQL_SQL, +} + + @_matcher.handle() async def _(session: EventSession, message: UniMsg): sql_text = message.extract_plain_text().strip() @@ -60,7 +79,7 @@ async def _(session: EventSession, message: UniMsg): logger.info(f"执行SQL语句: {sql_text}", "exec", session=session) try: if not sql_text.lower().startswith("select"): - await TestSQL.raw(sql_text) + await BanConsole.raw(sql_text) else: db = Tortoise.get_connection("default") res = await db.execute_query_dict(sql_text) @@ -70,14 +89,14 @@ async def _(session: EventSession, message: UniMsg): _column = r.keys() data_list = [] for r in res: - data = [] - for c in _column: - data.append(r.get(c)) + data = [r.get(c) for c in _column] data_list.append(data) + if not data_list: + return await MessageUtils.build_message("查询结果为空!").send() table = await ImageTemplate.table_page( "EXEC", f"总共有 {len(data_list)} 条数据捏", list(_column), data_list ) - await MessageUtils.build_message(table).send() + return await MessageUtils.build_message(table).send() except Exception as e: logger.error("执行 SQL 语句失败...", session=session, e=e) await MessageUtils.build_message(f"执行 SQL 语句失败... {type(e)}").finish() @@ -88,11 +107,13 @@ async def _(session: EventSession, message: UniMsg): async def _(session: EventSession): try: db = Tortoise.get_connection("default") - query = await db.execute_query_dict(SELECT_TABLE_SQL) + sql_type = BotConfig.get_sql_type() + select_sql = type2sql[sql_type] + query = await db.execute_query_dict(select_sql) column_name = ["表名", "简介"] data_list = [] for table in query: - data_list.append([table["name"], table["desc"]]) + data_list.append([table["name"], table.get("desc")]) logger.info("查看数据库所有表", "查看所有表", session=session) table = await ImageTemplate.table_page( "数据库表", f"总共有 {len(data_list)} 张表捏", column_name, data_list diff --git a/zhenxun/builtin_plugins/superuser/fg_manage.py b/zhenxun/builtin_plugins/superuser/fg_manage.py index e2ff51d7..12a5ff1f 100644 --- a/zhenxun/builtin_plugins/superuser/fg_manage.py +++ b/zhenxun/builtin_plugins/superuser/fg_manage.py @@ -1,5 +1,4 @@ from nonebot.adapters import Bot -from nonebot.adapters.kaiheila.exception import ApiNotAvailable from nonebot.permission import SUPERUSER from nonebot.plugin import PluginMetadata from nonebot.rule import to_me @@ -10,7 +9,6 @@ from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import admin_check, ensure_group __plugin_meta__ = PluginMetadata( name="好友群组列表", @@ -23,7 +21,7 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) _friend_matcher = on_alconna( @@ -72,8 +70,6 @@ async def _( msg = f"| UID | 昵称 | 共{len(fl)}个好友\n" + msg await MessageUtils.build_message(msg).send() logger.info("查看好友列表", "好友列表", session=session) - except (ApiNotAvailable, AttributeError) as e: - await MessageUtils.build_message("Api未实现...").send() except Exception as e: logger.error("好友列表发生错误", "好友列表", session=session, e=e) await MessageUtils.build_message("其他未知错误...").send() @@ -92,8 +88,6 @@ async def _( msg = f"| GID | 名称 | 共{len(gl)}个群组\n" + msg await MessageUtils.build_message(msg).send() logger.info("查看群组列表", "群组列表", session=session) - except (ApiNotAvailable, AttributeError) as e: - await MessageUtils.build_message("Api未实现...").send() except Exception as e: logger.error("查看群组列表发生错误", "群组列表", session=session, e=e) await MessageUtils.build_message("其他未知错误...").send() diff --git a/zhenxun/builtin_plugins/superuser/group_manage.py b/zhenxun/builtin_plugins/superuser/group_manage.py index 41044f94..fb8c0d2e 100644 --- a/zhenxun/builtin_plugins/superuser/group_manage.py +++ b/zhenxun/builtin_plugins/superuser/group_manage.py @@ -16,7 +16,7 @@ from nonebot_plugin_alconna import ( ) from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger @@ -53,25 +53,24 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) _matcher = on_alconna( Alconna( "group-manage", + Option("--delete", action=store_true, help_text="删除"), Subcommand( "modify-level", Args["level", int]["group_id?", int], help_text="修改群权限" ), Subcommand( "super-handle", - Option("--del", action=store_true, help_text="删除"), Args["group_id", int], help_text="添加/删除群白名单", ), Subcommand( "auth-handle", - Option("--del", action=store_true, help_text="删除"), Args["group_id", int], help_text="添加/删除群认证", ), @@ -99,7 +98,7 @@ _matcher.shortcut( _matcher.shortcut( "删除群白名单", command="group-manage", - arguments=["super-handle", "{%0}", "--del"], + arguments=["super-handle", "{%0}", "--delete"], prefix=True, ) @@ -113,7 +112,7 @@ _matcher.shortcut( _matcher.shortcut( "删除群认证", command="group-manage", - arguments=["auth-handle", "{%0}", "--del"], + arguments=["auth-handle", "{%0}", "--delete"], prefix=True, ) @@ -167,8 +166,8 @@ async def _(session: EventSession, arparma: Arparma, state: T_State): group = await GroupConsole.get_or_none(group_id=gid) if not group: await MessageUtils.build_message("群组信息不存在, 请更新群组信息...").finish() - s = "删除" if arparma.find("del") else "添加" - group.is_super = not arparma.find("del") + s = "删除" if arparma.find("delete") else "添加" + group.is_super = not arparma.find("delete") await group.save(update_fields=["is_super"]) await MessageUtils.build_message(f"{s}群白名单成功!").send(reply_to=True) logger.info(f"{s}群白名单", arparma.header_result, session=session, target=gid) @@ -178,9 +177,9 @@ async def _(session: EventSession, arparma: Arparma, state: T_State): async def _(session: EventSession, arparma: Arparma, state: T_State): gid = state["group_id"] await GroupConsole.update_or_create( - group_id=gid, defaults={"group_flag": 0 if arparma.find("del") else 1} + group_id=gid, defaults={"group_flag": 0 if arparma.find("delete") else 1} ) - s = "删除" if arparma.find("del") else "添加" + s = "删除" if arparma.find("delete") else "添加" await MessageUtils.build_message(f"{s}群认证成功!").send(reply_to=True) logger.info(f"{s}群白名单", arparma.header_result, session=session, target=gid) @@ -191,17 +190,22 @@ async def _(bot: Bot, session: EventSession, arparma: Arparma, group_id: int): group_list = [g["group_id"] for g in await bot.get_group_list()] if group_id not in group_list: logger.debug("群组不存在", "退群", session=session, target=group_id) - await MessageUtils.build_message(f"{NICKNAME}未在该群组中...").finish() + await MessageUtils.build_message( + f"{BotConfig.self_nickname}未在该群组中..." + ).finish() try: await bot.set_group_leave(group_id=group_id) logger.info( - f"{NICKNAME}退出群组成功", "退群", session=session, target=group_id + f"{BotConfig.self_nickname}退出群组成功", + "退群", + session=session, + target=group_id, ) await MessageUtils.build_message(f"退出群组 {group_id} 成功!").send() await GroupConsole.filter(group_id=group_id).delete() except Exception as e: - logger.error(f"退出群组失败", "退群", session=session, target=group_id, e=e) + logger.error("退出群组失败", "退群", session=session, target=group_id, e=e) await MessageUtils.build_message(f"退出群组 {group_id} 失败...").send() else: # TODO: 其他平台的退群操作 - await MessageUtils.build_message(f"暂未支持退群操作...").send() + await MessageUtils.build_message("暂未支持退群操作...").send() diff --git a/zhenxun/builtin_plugins/superuser/reload_setting.py b/zhenxun/builtin_plugins/superuser/reload_setting.py index d4c6d3a1..019e4c37 100644 --- a/zhenxun/builtin_plugins/superuser/reload_setting.py +++ b/zhenxun/builtin_plugins/superuser/reload_setting.py @@ -37,7 +37,7 @@ __plugin_meta__ = PluginMetadata( type=int, ), ], - ).dict(), + ).to_dict(), ) _matcher = on_alconna( diff --git a/zhenxun/builtin_plugins/superuser/request_manage.py b/zhenxun/builtin_plugins/superuser/request_manage.py index 7f7c4497..23b235bf 100644 --- a/zhenxun/builtin_plugins/superuser/request_manage.py +++ b/zhenxun/builtin_plugins/superuser/request_manage.py @@ -16,6 +16,7 @@ from nonebot_plugin_alconna import ( ) from nonebot_plugin_session import EventSession +from zhenxun.configs.config import BotConfig from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.configs.utils import PluginExtraData from zhenxun.models.fg_request import FgRequest @@ -46,7 +47,7 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) @@ -134,14 +135,15 @@ async def _( "r": RequestHandleType.REFUSED, "i": RequestHandleType.IGNORE, } + req = None handle_type = type_dict[handle[-1]] try: if handle_type == RequestHandleType.APPROVE: - await FgRequest.approve(bot, id) + req = await FgRequest.approve(bot, id) if handle_type == RequestHandleType.REFUSED: - await FgRequest.refused(bot, id) + req = await FgRequest.refused(bot, id) if handle_type == RequestHandleType.IGNORE: - await FgRequest.ignore(id) + req = await FgRequest.ignore(id) except NotFoundError: await MessageUtils.build_message("未发现此id的请求...").finish(reply_to=True) except Exception: @@ -149,7 +151,14 @@ async def _( reply_to=True ) logger.info("处理请求", arparma.header_result, session=session) - await MessageUtils.build_message("成功处理请求!").finish(reply_to=True) + await MessageUtils.build_message("成功处理请求!").send(reply_to=True) + if req and handle_type == RequestHandleType.APPROVE: + await bot.send_private_msg( + user_id=req.user_id, + message=f"管理员已同意此次群组邀请,请不要让{BotConfig.self_nickname}受委屈哦(狠狠监控)" + "\n在群组中 群组管理员与群主 允许使用管理员帮助" + "(包括ban与功能开关等)\n请在群组中发送 '管理员帮助'", + ) @_read_matcher.handle() @@ -224,8 +233,8 @@ async def _( await _id_img.circle_corner(10) await background.paste(_id_img, (10, 0), center_type="height") img_list.append(background) - A = await BuildImage.auto_paste(img_list, 1) - if A: + if img_list: + A = await BuildImage.auto_paste(img_list, 1) result_image = BuildImage( A.width, A.height + 30, color=(255, 255, 255), font_size=20 ) @@ -237,8 +246,8 @@ async def _( await MessageUtils.build_message("没有任何请求喔...").finish(reply_to=True) if len(req_image_list) == 1: await MessageUtils.build_message(req_image_list[0]).finish() - width = sum([img.width for img in req_image_list]) - height = max([img.height for img in req_image_list]) + width = sum(img.width for img in req_image_list) + height = max(img.height for img in req_image_list) background = BuildImage(width, height) await background.paste(req_image_list[0]) await req_image_list[1].line((0, 10, 1, req_image_list[1].height - 10), width=1) diff --git a/zhenxun/builtin_plugins/superuser/set_admin.py b/zhenxun/builtin_plugins/superuser/set_admin.py index 033d7a3d..a9d5856f 100644 --- a/zhenxun/builtin_plugins/superuser/set_admin.py +++ b/zhenxun/builtin_plugins/superuser/set_admin.py @@ -6,6 +6,7 @@ from nonebot_plugin_alconna import ( Arparma, At, Match, + Option, Subcommand, on_alconna, ) @@ -21,21 +22,21 @@ __plugin_meta__ = PluginMetadata( name="用户权限管理", description="设置用户权限", usage=""" - 权限设置 add [level: 权限等级] [at: at对象或用户id] [gid: 群组] - 权限设置 delete [at: at对象或用户id] - - 权限设置 add 5 @user - 权限设置 add 5 422 352352 + 权限设置 add [level: 权限等级] [at: at对象或用户id] ?[-g gid: 群组] + 权限设置 delete [at: at对象或用户id] ?[-g gid: 群组] + + 添加权限 5 @user + 权限设置 add 5 422 -g 352352 + + 删除权限 @user + 删除权限 1234123 -g 123123 - 权限设置 delete @user - 权限设置 delete 123456 - """.strip(), extra=PluginExtraData( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) @@ -44,16 +45,31 @@ _matcher = on_alconna( "权限设置", Subcommand( "add", - Args["level", int]["uid", [str, At]]["gid?", str], + Args["level", int]["uid", [str, At]], help_text="添加权限", ), - Subcommand("delete", Args["uid", [str, At]]["gid?", str], help_text="删除权限"), + Subcommand("delete", Args["uid", [str, At]], help_text="删除权限"), + Option("-g|--group", Args["gid", str], help_text="指定群组"), ), permission=SUPERUSER, priority=5, block=True, ) +_matcher.shortcut( + "添加权限", + command="权限设置", + arguments=["add", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + "删除权限", + command="权限设置", + arguments=["delete", "{%0}"], + prefix=True, +) + @_matcher.assign("add") async def _( @@ -82,9 +98,10 @@ async def _( ] ).finish(reply_to=True) await MessageUtils.build_message( - f"成功为 \n群组:{group_id}\n用户:{uid} \n设置权限!\n权限:{old_level} -> {level}" + f"成功为 \n群组:{group_id}\n用户:{uid} \n" + f"设置权限!\n权限:{old_level} -> {level}" ).finish() - await MessageUtils.build_message(f"设置权限时群组不能为空...").finish() + await MessageUtils.build_message("设置权限时群组不能为空...").finish() @_matcher.assign("delete") @@ -107,7 +124,7 @@ async def _( session=session, ) await MessageUtils.build_message( - ["成功删除 ", At(flag="user", target=uid), f" 的权限等级!"] + ["成功删除 ", At(flag="user", target=uid), " 的权限等级!"] ).finish(reply_to=True) logger.info( f"删除群组用户权限: {user.user_level} -> 0", @@ -115,7 +132,8 @@ async def _( session=session, ) await MessageUtils.build_message( - f"成功删除 \n群组:{group_id}\n用户:{uid} \n的权限等级!\n权限:{user.user_level} -> 0" + f"成功删除 \n群组:{group_id}\n用户:{uid} \n" + f"的权限等级!\n权限:{user.user_level} -> 0" ).finish() - await MessageUtils.build_message(f"对方目前暂无权限喔...").finish() - await MessageUtils.build_message(f"设置权限时群组不能为空...").finish() + await MessageUtils.build_message("对方目前暂无权限喔...").finish() + await MessageUtils.build_message("设置权限时群组不能为空...").finish() diff --git a/zhenxun/builtin_plugins/superuser/super_help.py b/zhenxun/builtin_plugins/superuser/super_help.py deleted file mode 100644 index 5fa1e09e..00000000 --- a/zhenxun/builtin_plugins/superuser/super_help.py +++ /dev/null @@ -1,159 +0,0 @@ -import nonebot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_alconna.matcher import AlconnaMatcher -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.exception import EmptyError -from zhenxun.utils.image_utils import ( - BuildImage, - build_sort_image, - group_image, - text2image, -) -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import admin_check, ensure_group - -__plugin_meta__ = PluginMetadata( - name="超级用户帮助", - description="超级用户帮助", - usage=""" - 超级用户帮助 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.SUPERUSER, - ).dict(), -) - -_matcher = on_alconna( - Alconna("超级用户帮助"), - permission=SUPERUSER, - priority=5, - block=True, -) - - -SUPERUSER_HELP_IMAGE = IMAGE_PATH / "SUPERUSER_HELP.png" -if SUPERUSER_HELP_IMAGE.exists(): - SUPERUSER_HELP_IMAGE.unlink() - - -async def build_help() -> BuildImage: - """构造超级用户帮助图片 - - 异常: - EmptyError: 超级用户帮助为空 - - 返回: - BuildImage: 超级用户帮助图片 - """ - plugin_list = await PluginInfo.filter(plugin_type=PluginType.SUPERUSER).all() - data_list = [] - for plugin in plugin_list: - if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): - if _plugin.metadata: - data_list.append({"plugin": plugin, "metadata": _plugin.metadata}) - font = BuildImage.load_font("HYWenHei-85W.ttf", 20) - image_list = [] - for data in data_list: - plugin = data["plugin"] - metadata = data["metadata"] - try: - usage = None - description = None - if metadata.usage: - usage = await text2image( - metadata.usage, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - if metadata.description: - description = await text2image( - metadata.description, - padding=5, - color=(255, 255, 255), - font_color=(0, 0, 0), - ) - width = 0 - height = 100 - if usage: - width = usage.width - height += usage.height - if description and description.width > width: - width = description.width - height += description.height - font_width, font_height = BuildImage.get_text_size( - plugin.name + f"[{plugin.level}]", font - ) - if font_width > width: - width = font_width - A = BuildImage(width + 30, height + 120, "#EAEDF2") - await A.text((15, 10), plugin.name + f"[{plugin.level}]") - await A.text((15, 70), "简介:") - if not description: - description = BuildImage(A.width - 30, 30, (255, 255, 255)) - await description.circle_corner(10) - await A.paste(description, (15, 100)) - if not usage: - usage = BuildImage(A.width - 30, 30, (255, 255, 255)) - await usage.circle_corner(10) - await A.text((15, description.height + 115), "用法:") - await A.paste(usage, (15, description.height + 145)) - await A.circle_corner(10) - image_list.append(A) - except Exception as e: - logger.warning( - f"获取超级用户管理员插件 {plugin.module}: {plugin.name} 设置失败...", - "超级用户帮助", - e=e, - ) - if task_list := await TaskInfo.all(): - task_str = "\n".join([task.name for task in task_list]) - task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str - task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) - await task_image.circle_corner(10) - A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") - await A.text((25, 10), "被动技能") - await A.paste(task_image, (25, 50)) - await A.circle_corner(10) - image_list.append(A) - if not image_list: - raise EmptyError() - image_group, _ = group_image(image_list) - A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) - text = await BuildImage.build_text_image( - "超级用户帮助", - size=40, - ) - tip = await BuildImage.build_text_image( - "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" - ) - await A.paste(text, (50, 30)) - await A.paste(tip, (50, 90)) - await A.save(SUPERUSER_HELP_IMAGE) - return BuildImage(1, 1) - - -@_matcher.handle() -async def _( - session: EventSession, - matcher: AlconnaMatcher, - arparma: Arparma, -): - if not SUPERUSER_HELP_IMAGE.exists(): - try: - await build_help() - except EmptyError: - await MessageUtils.build_message("超级用户帮助为空").finish(reply_to=True) - await MessageUtils.build_message(SUPERUSER_HELP_IMAGE).send() - logger.info("查看超级用户帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/superuser/super_help/__init__.py b/zhenxun/builtin_plugins/superuser/super_help/__init__.py new file mode 100644 index 00000000..258af8db --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/__init__.py @@ -0,0 +1,59 @@ +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.config import Config +from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError +from zhenxun.utils.message import MessageUtils + +from .config import SUPERUSER_HELP_IMAGE +from .normal_help import build_help +from .zhenxun_help import build_html_help + +__plugin_meta__ = PluginMetadata( + name="超级用户帮助", + description="超级用户帮助", + usage=""" + 超级用户帮助 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + configs=[ + RegisterConfig( + key="type", + value="zhenxun", + help="超级用户帮助样式,normal, zhenxun", + default_value="zhenxun", + ) + ], + ).to_dict(), +) + +_matcher = on_alconna( + Alconna("超级用户帮助"), + permission=SUPERUSER, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _(session: EventSession, arparma: Arparma): + if not SUPERUSER_HELP_IMAGE.exists(): + try: + if Config.get_config("admin_help", "type") == "zhenxun": + await build_html_help() + else: + await build_help() + except EmptyError: + await MessageUtils.build_message("当前超级用户帮助为空...").finish( + reply_to=True + ) + await MessageUtils.build_message(SUPERUSER_HELP_IMAGE).send() + logger.info("查看超级用户帮助", arparma.header_result, session=session) diff --git a/zhenxun/builtin_plugins/superuser/super_help/config.py b/zhenxun/builtin_plugins/superuser/super_help/config.py new file mode 100644 index 00000000..5f5371d9 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/config.py @@ -0,0 +1,23 @@ +from nonebot.plugin import PluginMetadata +from pydantic import BaseModel + +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.models.plugin_info import PluginInfo + +SUPERUSER_HELP_IMAGE = IMAGE_PATH / "SUPERUSER_HELP.png" +if SUPERUSER_HELP_IMAGE.exists(): + SUPERUSER_HELP_IMAGE.unlink() + + +class PluginData(BaseModel): + """ + 插件信息 + """ + + plugin: PluginInfo + """插件信息""" + metadata: PluginMetadata + """元数据""" + + class Config: + arbitrary_types_allowed = True diff --git a/zhenxun/builtin_plugins/superuser/super_help/normal_help.py b/zhenxun/builtin_plugins/superuser/super_help/normal_help.py new file mode 100644 index 00000000..7407e9e3 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/normal_help.py @@ -0,0 +1,127 @@ +from nonebot.plugin import PluginMetadata +from PIL.ImageFont import FreeTypeFont + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger +from zhenxun.utils._build_image import BuildImage +from zhenxun.utils.image_utils import build_sort_image, group_image, text2image + +from .config import SUPERUSER_HELP_IMAGE +from .utils import get_plugins + + +async def build_usage_des_image( + metadata: PluginMetadata, +) -> tuple[BuildImage | None, BuildImage | None]: + """构建用法和描述图片 + + 参数: + metadata: PluginMetadata + + 返回: + tuple[BuildImage | None, BuildImage | None]: 用法和描述图片 + """ + usage = None + description = None + if metadata.usage: + usage = await text2image( + metadata.usage, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + if metadata.description: + description = await text2image( + metadata.description, + padding=5, + color=(255, 255, 255), + font_color=(0, 0, 0), + ) + return usage, description + + +async def build_image( + plugin: PluginInfo, metadata: PluginMetadata, font: FreeTypeFont +) -> BuildImage: + """构建帮助图片 + + 参数: + plugin: PluginInfo + metadata: PluginMetadata + font: FreeTypeFont + + 返回: + BuildImage: 帮助图片 + + """ + usage, description = await build_usage_des_image(metadata) + width = 0 + height = 100 + if usage: + width = usage.width + height += usage.height + if description and description.width > width: + width = description.width + height += description.height + font_width, _ = BuildImage.get_text_size(f"{plugin.name}[{plugin.level}]", font) + if font_width > width: + width = font_width + A = BuildImage(width + 30, height + 120, "#EAEDF2") + await A.text((15, 10), f"{plugin.name}[{plugin.level}]") + await A.text((15, 70), "简介:") + if not description: + description = BuildImage(A.width - 30, 30, (255, 255, 255)) + await description.circle_corner(10) + await A.paste(description, (15, 100)) + if not usage: + usage = BuildImage(A.width - 30, 30, (255, 255, 255)) + await usage.circle_corner(10) + await A.text((15, description.height + 115), "用法:") + await A.paste(usage, (15, description.height + 145)) + await A.circle_corner(10) + return A + + +async def build_help(): + """构造超级用户帮助图片 + + 返回: + BuildImage: 超级用户帮助图片 + """ + font = BuildImage.load_font("HYWenHei-85W.ttf", 20) + image_list = [] + for data in await get_plugins(): + plugin = data.plugin + metadata = data.metadata + try: + A = await build_image(plugin, metadata, font) + image_list.append(A) + except Exception as e: + logger.warning( + f"获取群超级用户插件 {plugin.module}: {plugin.name} 设置失败...", + "超级用户帮助", + e=e, + ) + if task_list := await TaskInfo.all(): + task_str = "\n".join([task.name for task in task_list]) + task_str = "通过 开启/关闭群被动 来控制群被动\n----------\n" + task_str + task_image = await text2image(task_str, padding=5, color=(255, 255, 255)) + await task_image.circle_corner(10) + A = BuildImage(task_image.width + 50, task_image.height + 85, "#EAEDF2") + await A.text((25, 10), "被动技能") + await A.paste(task_image, (25, 50)) + await A.circle_corner(10) + image_list.append(A) + image_group, _ = group_image(image_list) + A = await build_sort_image(image_group, color=(255, 255, 255), padding_top=160) + text = await BuildImage.build_text_image( + "群超级用户帮助", + size=40, + ) + tip = await BuildImage.build_text_image( + "注: ‘*’ 代表可有多个相同参数 ‘?’ 代表可省略该参数", size=25, font_color="red" + ) + await A.paste(text, (50, 30)) + await A.paste(tip, (50, 90)) + await A.save(SUPERUSER_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/superuser/super_help/utils.py b/zhenxun/builtin_plugins/superuser/super_help/utils.py new file mode 100644 index 00000000..201687ec --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/utils.py @@ -0,0 +1,22 @@ +import nonebot + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.enum import PluginType +from zhenxun.utils.exception import EmptyError + +from .config import PluginData + + +async def get_plugins() -> list[PluginData]: + """获取插件数据""" + plugin_list = await PluginInfo.filter( + plugin_type__in=[PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN] + ).all() + data_list = [] + for plugin in plugin_list: + if _plugin := nonebot.get_plugin_by_module_name(plugin.module_path): + if _plugin.metadata: + data_list.append(PluginData(plugin=plugin, metadata=_plugin.metadata)) + if not data_list: + raise EmptyError() + return data_list diff --git a/zhenxun/builtin_plugins/superuser/super_help/zhenxun_help.py b/zhenxun/builtin_plugins/superuser/super_help/zhenxun_help.py new file mode 100644 index 00000000..0a93a160 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_help/zhenxun_help.py @@ -0,0 +1,59 @@ +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.configs.config import BotConfig +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.task_info import TaskInfo +from zhenxun.utils._build_image import BuildImage + +from .config import SUPERUSER_HELP_IMAGE +from .utils import get_plugins + + +async def get_task() -> dict[str, str] | None: + """获取被动技能帮助""" + if task_list := await TaskInfo.all(): + return { + "name": "被动技能", + "description": "控制群组中的被动技能状态", + "usage": "通过 开启/关闭群被动 来控制群被动
----------
" + + "
".join([task.name for task in task_list]), + } + return None + + +async def build_html_help(): + """构建帮助图片""" + plugins = await get_plugins() + plugin_list = [] + for data in plugins: + if data.metadata.extra: + if superuser_help := data.metadata.extra.get("superuser_help"): + data.metadata.usage += f"
以下为超级用户额外命令
{superuser_help}" + plugin_list.append( + { + "name": data.plugin.name, + "description": data.metadata.description.replace("\n", "
"), + "usage": data.metadata.usage.replace("\n", "
"), + } + ) + if task := await get_task(): + plugin_list.append(task) + plugin_list.sort(key=lambda p: len(p["description"]) + len(p["usage"])) + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "help").absolute()), + template_name="main.html", + templates={ + "data": { + "plugin_list": plugin_list, + "nickname": BotConfig.self_nickname, + "help_name": "超级用户", + } + }, + pages={ + "viewport": {"width": 1024, "height": 1024}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + result = await BuildImage.open(pic).resize(0.5) + await result.save(SUPERUSER_HELP_IMAGE) diff --git a/zhenxun/builtin_plugins/superuser/super_power.py b/zhenxun/builtin_plugins/superuser/super_power.py new file mode 100644 index 00000000..508ac861 --- /dev/null +++ b/zhenxun/builtin_plugins/superuser/super_power.py @@ -0,0 +1,114 @@ +from decimal import Decimal + +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Args, Arparma, At, Field, on_alconna +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.models.sign_user import SignUser +from zhenxun.models.user_console import UserConsole +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils + +__plugin_meta__ = PluginMetadata( + name="高贵的作弊器", + description="这是一个作弊器,设置用户金币数量和好感度", + usage=""" + 金币设置 100(金币数量) @用户 + 好感度设置 100(好感度) @用户 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + ).to_dict(), +) + +_gold_matcher = on_alconna( + Alconna( + "金币设置", + Args[ + "gold", + int, + Field( + missing_tips=lambda: "请在命令后跟随金币数量!", + unmatch_tips=lambda _: "金币数量必须为数字!", + ), + ][ + "at_user", + At, + Field( + missing_tips=lambda: "必须要at一名指定用户!", + ), + ], + ), + skip_for_unmatch=False, + permission=SUPERUSER, + priority=5, + block=True, +) + + +_impression_matcher = on_alconna( + Alconna( + "好感度设置", + Args[ + "impression", + float, + Field( + missing_tips=lambda: "请在命令后跟随好感度!", + unmatch_tips=lambda _: "好感度数量必须为数字!", + ), + ][ + "at_user", + At, + Field( + missing_tips=lambda: "必须要at一名指定用户!", + ), + ], + ), + skip_for_unmatch=False, + permission=SUPERUSER, + priority=5, + block=True, +) + + +@_gold_matcher.handle() +async def _(session: Uninfo, arparma: Arparma, gold: int, at_user: At): + user = await UserConsole.get_user( + at_user.target, PlatformUtils.get_platform(session) + ) + user.gold = gold + await user.save(update_fields=["gold"]) + await MessageUtils.build_message( + ["成功将用户", at_user, f"的金币设置为 {gold}"] + ).send(reply_to=True) + logger.info( + f"成功将用户{at_user.target}的金币设置为{gold}", + arparma.header_result, + session=session, + ) + + +@_impression_matcher.handle() +async def _(session: Uninfo, arparma: Arparma, impression: float, at_user: At): + platform = PlatformUtils.get_platform(session) + user_console = await UserConsole.get_user(at_user.target, platform) + user, _ = await SignUser.get_or_create( + user_id=at_user.target, + defaults={"user_console": user_console, "platform": platform}, + ) + user.impression = Decimal(impression) + await user.save(update_fields=["impression"]) + await MessageUtils.build_message( + ["成功将用户", at_user, f"的好感度设置为 {impression}"] + ).send(reply_to=True) + logger.info( + f"成功将用户{at_user.target}的好感度设置为{impression}", + arparma.header_result, + session=session, + ) diff --git a/zhenxun/builtin_plugins/superuser/update_fg_info.py b/zhenxun/builtin_plugins/superuser/update_fg_info.py index 6afac3d4..4932a976 100644 --- a/zhenxun/builtin_plugins/superuser/update_fg_info.py +++ b/zhenxun/builtin_plugins/superuser/update_fg_info.py @@ -22,7 +22,7 @@ __plugin_meta__ = PluginMetadata( author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER, - ).dict(), + ).to_dict(), ) diff --git a/zhenxun/plugins/web_ui/__init__.py b/zhenxun/builtin_plugins/web_ui/__init__.py similarity index 60% rename from zhenxun/plugins/web_ui/__init__.py rename to zhenxun/builtin_plugins/web_ui/__init__.py index 35fcf9fe..d8d71025 100644 --- a/zhenxun/plugins/web_ui/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/__init__.py @@ -1,25 +1,30 @@ import asyncio +import secrets -import nonebot from fastapi import APIRouter, FastAPI +import nonebot from nonebot.log import default_filter, default_format from nonebot.plugin import PluginMetadata from zhenxun.configs.config import Config as gConfig -from zhenxun.configs.utils import PluginExtraData +from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger, logger_ from zhenxun.utils.enum import PluginType from .api.logs import router as ws_log_routes from .api.logs.log_manager import LOG_STORAGE +from .api.menu import router as menu_router +from .api.tabs.dashboard import router as dashboard_router from .api.tabs.database import router as database_router from .api.tabs.main import router as main_router from .api.tabs.main import ws_router as status_routes from .api.tabs.manage import router as manage_router from .api.tabs.manage.chat import ws_router as chat_routes from .api.tabs.plugin_manage import router as plugin_router +from .api.tabs.plugin_manage.store import router as store_router from .api.tabs.system import router as system_router from .auth import router as auth_router +from .public import init_public __plugin_meta__ = PluginMetadata( name="WebUi", @@ -27,15 +32,40 @@ __plugin_meta__ = PluginMetadata( usage=""" """.strip(), extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN - ).dict(), + author="HibiKier", + version="0.1", + plugin_type=PluginType.HIDDEN, + configs=[ + RegisterConfig( + module="web-ui", + key="username", + value="admin", + help="前端管理用户名", + type=str, + default_value="admin", + ), + RegisterConfig( + module="web-ui", + key="password", + value=None, + help="前端管理密码", + type=str, + default_value=None, + ), + RegisterConfig( + module="web-ui", + key="secret", + value=secrets.token_urlsafe(32), + help="JWT密钥", + type=str, + default_value=None, + ), + ], + ).to_dict(), ) driver = nonebot.get_driver() -gConfig.add_plugin_config("web-ui", "username", "admin", help="前端管理用户名") - -gConfig.add_plugin_config("web-ui", "password", None, help="前端管理密码") gConfig.set_name("web-ui", "web-ui") @@ -44,11 +74,14 @@ BaseApiRouter = APIRouter(prefix="/zhenxun/api") BaseApiRouter.include_router(auth_router) +BaseApiRouter.include_router(store_router) +BaseApiRouter.include_router(dashboard_router) BaseApiRouter.include_router(main_router) BaseApiRouter.include_router(manage_router) BaseApiRouter.include_router(database_router) BaseApiRouter.include_router(plugin_router) BaseApiRouter.include_router(system_router) +BaseApiRouter.include_router(menu_router) WsApiRouter = APIRouter(prefix="/zhenxun/socket") @@ -59,7 +92,7 @@ WsApiRouter.include_router(chat_routes) @driver.on_startup -def _(): +async def _(): try: async def log_sink(message: str): @@ -71,7 +104,7 @@ def _(): logger.warning("Web Ui log_sink", e=e) if not loop: loop = asyncio.new_event_loop() - loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) + loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) # noqa: RUF006 logger_.add( log_sink, colorize=True, filter=default_filter, format=default_format @@ -80,6 +113,7 @@ def _(): app: FastAPI = nonebot.get_app() app.include_router(BaseApiRouter) app.include_router(WsApiRouter) - logger.info("API启动成功", "Web UI") + await init_public(app) + logger.info("API启动成功", "WebUi") except Exception as e: - logger.error("API启动失败", "Web UI", e=e) + logger.error("API启动失败", "WebUi", e=e) diff --git a/zhenxun/builtin_plugins/web_ui/api/__init__.py b/zhenxun/builtin_plugins/web_ui/api/__init__.py new file mode 100644 index 00000000..3608647c --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/__init__.py @@ -0,0 +1,2 @@ +from .menu import * # noqa: F403f +from .tabs import * # noqa: F403f diff --git a/zhenxun/builtin_plugins/web_ui/api/logs/__init__.py b/zhenxun/builtin_plugins/web_ui/api/logs/__init__.py new file mode 100644 index 00000000..5f44e443 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/logs/__init__.py @@ -0,0 +1 @@ +from .logs import * # noqa: F403 diff --git a/zhenxun/plugins/web_ui/api/logs/log_manager.py b/zhenxun/builtin_plugins/web_ui/api/logs/log_manager.py similarity index 79% rename from zhenxun/plugins/web_ui/api/logs/log_manager.py rename to zhenxun/builtin_plugins/web_ui/api/logs/log_manager.py index 71992c91..3938c525 100644 --- a/zhenxun/plugins/web_ui/api/logs/log_manager.py +++ b/zhenxun/builtin_plugins/web_ui/api/logs/log_manager.py @@ -1,7 +1,6 @@ import asyncio -from typing import Awaitable, Callable, Generic, TypeVar - -PATTERN = r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))" +from collections.abc import Awaitable, Callable +from typing import Generic, TypeVar _T = TypeVar("_T") LogListener = Callable[[_T], Awaitable[None]] @@ -22,14 +21,13 @@ class LogStorage(Generic[_T]): self.logs[seq] = log asyncio.get_running_loop().call_later(self.rotation, self.remove, seq) await asyncio.gather( - *map(lambda listener: listener(log), self.listeners), + *(listener(log) for listener in self.listeners), return_exceptions=True, ) return seq def remove(self, seq: int): del self.logs[seq] - return LOG_STORAGE: LogStorage[str] = LogStorage[str]() diff --git a/zhenxun/plugins/web_ui/api/logs/logs.py b/zhenxun/builtin_plugins/web_ui/api/logs/logs.py similarity index 74% rename from zhenxun/plugins/web_ui/api/logs/logs.py rename to zhenxun/builtin_plugins/web_ui/api/logs/logs.py index 01c78096..b7fc660c 100644 --- a/zhenxun/plugins/web_ui/api/logs/logs.py +++ b/zhenxun/builtin_plugins/web_ui/api/logs/logs.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, WebSocket +from fastapi import APIRouter from loguru import logger from nonebot.utils import escape_tag from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState @@ -8,16 +8,6 @@ from .log_manager import LOG_STORAGE router = APIRouter() -@router.get("/logs", response_model=list[str]) -async def system_logs_history(reverse: bool = False): - """历史日志 - - 参数: - reverse: 反转顺序. - """ - return LOG_STORAGE.list(reverse=reverse) # type: ignore - - @router.websocket("/logs") async def system_logs_realtime(websocket: WebSocket): await websocket.accept() @@ -37,4 +27,3 @@ async def system_logs_realtime(websocket: WebSocket): pass finally: LOG_STORAGE.listeners.remove(log_listener) - return diff --git a/zhenxun/builtin_plugins/web_ui/api/menu/__init__.py b/zhenxun/builtin_plugins/web_ui/api/menu/__init__.py new file mode 100644 index 00000000..f89a7eea --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/menu/__init__.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter +from fastapi.responses import JSONResponse + +from zhenxun.services.log import logger + +from ...base_model import Result +from ...utils import authentication +from .data_source import menu_manage +from .model import MenuData + +router = APIRouter(prefix="/menu") + + +@router.get( + "/get_menus", + dependencies=[authentication()], + response_model=Result[MenuData], + response_class=JSONResponse, + description="获取菜单列表", +) +async def _() -> Result[MenuData]: + try: + return Result.ok(menu_manage.get_menus(), "拿到菜单了哦!") + except Exception as e: + logger.error(f"{router.prefix}/get_menus 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") diff --git a/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py b/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py new file mode 100644 index 00000000..9cfcd244 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py @@ -0,0 +1,64 @@ +import ujson as json + +from zhenxun.configs.path_config import DATA_PATH +from zhenxun.services.log import logger + +from .model import MenuData, MenuItem + + +class MenuManage: + def __init__(self) -> None: + self.file = DATA_PATH / "web_ui" / "menu.json" + self.menu = [] + if self.file.exists(): + try: + self.menu = json.load(self.file.open(encoding="utf8")) + except Exception as e: + logger.warning("菜单文件损坏,已重新生成...", "WebUi", e=e) + if not self.menu: + self.menu = [ + MenuItem( + name="仪表盘", + module="dashboard", + router="/dashboard", + icon="dashboard", + default=True, + ), + MenuItem( + name="真寻控制台", + module="command", + router="/command", + icon="command", + ), + MenuItem( + name="插件列表", module="plugin", router="/plugin", icon="plugin" + ), + MenuItem( + name="插件商店", module="store", router="/store", icon="store" + ), + MenuItem( + name="好友/群组", module="manage", router="/manage", icon="user" + ), + MenuItem( + name="数据库管理", + module="database", + router="/database", + icon="database", + ), + MenuItem( + name="系统信息", module="system", router="/system", icon="system" + ), + ] + self.save() + + def get_menus(self): + return MenuData(menus=self.menu) + + def save(self): + self.file.parent.mkdir(parents=True, exist_ok=True) + temp = [menu.to_dict() for menu in self.menu] + with self.file.open("w", encoding="utf8") as f: + json.dump(temp, f, ensure_ascii=False, indent=4) + + +menu_manage = MenuManage() diff --git a/zhenxun/builtin_plugins/web_ui/api/menu/model.py b/zhenxun/builtin_plugins/web_ui/api/menu/model.py new file mode 100644 index 00000000..d4a26ab6 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/menu/model.py @@ -0,0 +1,25 @@ +from nonebot.compat import model_dump +from pydantic import BaseModel + + +class MenuItem(BaseModel): + module: str + """模块名称""" + name: str + """菜单名称""" + router: str + """路由""" + icon: str + """图标""" + default: bool = False + """默认选中""" + + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) + + +class MenuData(BaseModel): + bot_type: str = "zhenxun" + """bot类型""" + menus: list[MenuItem] + """菜单列表""" diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/__init__.py new file mode 100644 index 00000000..19482ba7 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/__init__.py @@ -0,0 +1,5 @@ +from .database import * # noqa: F403 +from .main import * # noqa: F403 +from .manage import * # noqa: F403 +from .plugin_manage import * # noqa: F403 +from .system import * # noqa: F403 diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/__init__.py new file mode 100644 index 00000000..fa719cdf --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/__init__.py @@ -0,0 +1,112 @@ +from fastapi import APIRouter +from fastapi.responses import JSONResponse +import nonebot +from nonebot import require +from nonebot.config import Config + +from zhenxun.services.log import logger + +from ....base_model import BaseResultModel, QueryModel, Result +from ....utils import authentication +from .data_source import ApiDataSource +from .model import AllChatAndCallCount, BotInfo, ChatCallMonthCount, QueryChatCallCount + +require("plugin_store") + +router = APIRouter(prefix="/dashboard") + +driver = nonebot.get_driver() + + +@router.get( + "/get_bot_list", + dependencies=[authentication()], + response_model=Result[list[BotInfo]], + response_class=JSONResponse, + description="获取bot列表", # type: ignore +) +async def _() -> Result[list[BotInfo]]: + try: + return Result.ok(await ApiDataSource.get_bot_list(), "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_bot_list 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_chat_and_call_count", + dependencies=[authentication()], + response_model=Result[QueryChatCallCount], + response_class=JSONResponse, + description="获取聊天/调用记录的全部和今日数量", +) +async def _(bot_id: str | None = None) -> Result[QueryChatCallCount]: + try: + return Result.ok( + await ApiDataSource.get_chat_and_call_count(bot_id), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_chat_and_call_count 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_all_chat_and_call_count", + dependencies=[authentication()], + response_model=Result[AllChatAndCallCount], + response_class=JSONResponse, + description="获取聊天/调用记录的全部数据次数", +) +async def _(bot_id: str | None = None) -> Result[AllChatAndCallCount]: + try: + return Result.ok( + await ApiDataSource.get_all_chat_and_call_count(bot_id), "拿到信息啦!" + ) + except Exception as e: + logger.error( + f"{router.prefix}/get_all_chat_and_call_count 调用错误", "WebUi", e=e + ) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_chat_and_call_month", + dependencies=[authentication()], + response_model=Result[ChatCallMonthCount], + response_class=JSONResponse, + description="获取聊天/调用记录的一个月数量", # type: ignore +) +async def _(bot_id: str | None = None) -> Result[ChatCallMonthCount]: + try: + return Result.ok( + await ApiDataSource.get_chat_and_call_month(bot_id), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_chat_and_call_month 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/get_connect_log", + dependencies=[authentication()], + response_model=Result[BaseResultModel], + response_class=JSONResponse, + description="获取Bot连接记录", # type: ignore +) +async def _(query: QueryModel) -> Result[BaseResultModel]: + try: + return Result.ok(await ApiDataSource.get_connect_log(query), "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_connect_log 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_nonebot_config", + dependencies=[authentication()], + response_model=Result[Config], + response_class=JSONResponse, + description="获取nb配置", # type: ignore +) +async def _() -> Result[Config]: + return Result.ok(driver.config) diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py new file mode 100644 index 00000000..6c312db3 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py @@ -0,0 +1,267 @@ +import asyncio +from datetime import datetime, timedelta +import time + +import nonebot +from nonebot.adapters import Bot +from nonebot.drivers import Driver +from tortoise.expressions import RawSQL +from tortoise.functions import Count + +from zhenxun.configs.config import BotConfig +from zhenxun.models.bot_connect_log import BotConnectLog +from zhenxun.models.chat_history import ChatHistory +from zhenxun.models.statistics import Statistics +from zhenxun.services.log import logger +from zhenxun.utils.platform import PlatformUtils + +from ....base_model import BaseResultModel, QueryModel +from ..main.data_source import bot_live +from .model import ( + AllChatAndCallCount, + BotConnectLogInfo, + BotInfo, + ChatCallMonthCount, + QueryChatCallCount, +) + +driver: Driver = nonebot.get_driver() + + +CONNECT_TIME = 0 + + +@driver.on_startup +async def _(): + global CONNECT_TIME + CONNECT_TIME = int(time.time()) + + +class ApiDataSource: + @classmethod + async def __build_bot_info(cls, bot: Bot) -> BotInfo: + """构建Bot信息 + + 参数: + bot: Bot + + 返回: + BotInfo: Bot信息 + """ + now = datetime.now() + platform = PlatformUtils.get_platform(bot) or "" + if platform == "qq": + login_info = await bot.get_login_info() + nickname = login_info["nickname"] + ava_url = ( + PlatformUtils.get_user_avatar_url( + bot.self_id, "qq", BotConfig.get_qbot_uid(bot.self_id) + ) + or "" + ) + else: + nickname = bot.self_id + ava_url = "" + bot_info = BotInfo( + self_id=bot.self_id, nickname=nickname, ava_url=ava_url, platform=platform + ) + try: + group, friend = await asyncio.gather( + PlatformUtils.get_group_list(bot, True), + PlatformUtils.get_friend_list(bot), + ) + bot_info.group_count = len(group[0]) + bot_info.friend_count = len(friend[0]) + except Exception as e: + logger.warning("获取bot好友/群组信息失败...", "WebUi", e=e) + bot_info.group_count = 0 + bot_info.friend_count = 0 + bot_info.day_call = await Statistics.filter( + create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute), + bot_id=bot.self_id, + ).count() + bot_info.received_messages = await ChatHistory.filter( + bot_id=bot_info.self_id, + create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute), + ).count() + bot_info.connect_time = bot_live.get(bot.self_id) or 0 + if bot_info.connect_time: + connect_date = datetime.fromtimestamp(CONNECT_TIME) + bot_info.connect_date = connect_date.strftime("%Y-%m-%d %H:%M:%S") + return bot_info + + @classmethod + async def get_bot_list(cls) -> list[BotInfo]: + """获取bot列表 + + 返回: + list[BotInfo]: Bot列表 + """ + bot_list: list[BotInfo] = [] + for _, bot in nonebot.get_bots().items(): + bot_list.append(await cls.__build_bot_info(bot)) + return bot_list + + @classmethod + async def get_chat_and_call_count(cls, bot_id: str | None) -> QueryChatCallCount: + """获取今日聊天和调用次数 + + 参数: + bot_id: bot id + + 返回: + QueryChatCallCount: 数据内容 + """ + now = datetime.now() + query = ChatHistory + if bot_id: + query = query.filter(bot_id=bot_id) + chat_all_count = await query.annotate().count() + chat_day_count = await query.filter( + create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute) + ).count() + query = Statistics + if bot_id: + query = query.filter(bot_id=bot_id) + call_all_count = await query.annotate().count() + call_day_count = await query.filter( + create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute) + ).count() + return QueryChatCallCount( + chat_num=chat_all_count, + chat_day=chat_day_count, + call_num=call_all_count, + call_day=call_day_count, + ) + + @classmethod + async def get_all_chat_and_call_count( + cls, bot_id: str | None + ) -> AllChatAndCallCount: + """获取全部聊天和调用记录 + + 参数: + bot_id: bot id + + 返回: + AllChatAndCallCount: 数据内容 + """ + now = datetime.now() + query = ChatHistory + if bot_id: + query = query.filter(bot_id=bot_id) + chat_week_count = await query.filter( + create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute) + ).count() + chat_month_count = await query.filter( + create_time__gte=now + - timedelta(days=30, hours=now.hour, minutes=now.minute) + ).count() + chat_year_count = await query.filter( + create_time__gte=now + - timedelta(days=365, hours=now.hour, minutes=now.minute) + ).count() + query = Statistics + if bot_id: + query = query.filter(bot_id=bot_id) + call_week_count = await query.filter( + create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute) + ).count() + call_month_count = await query.filter( + create_time__gte=now + - timedelta(days=30, hours=now.hour, minutes=now.minute) + ).count() + call_year_count = await query.filter( + create_time__gte=now + - timedelta(days=365, hours=now.hour, minutes=now.minute) + ).count() + return AllChatAndCallCount( + chat_week=chat_week_count, + chat_month=chat_month_count, + chat_year=chat_year_count, + call_week=call_week_count, + call_month=call_month_count, + call_year=call_year_count, + ) + + @classmethod + async def get_chat_and_call_month(cls, bot_id: str | None) -> ChatCallMonthCount: + """获取一个月内的调用/消息记录次数,并根据日期对数据填充0 + + 参数: + bot_id: bot id + + 返回: + ChatCallMonthCount: 数据内容 + """ + now = datetime.now() + filter_date = now - timedelta(days=30, hours=now.hour, minutes=now.minute) + chat_query = ChatHistory + call_query = Statistics + if bot_id: + chat_query = chat_query.filter(bot_id=bot_id) + call_query = call_query.filter(bot_id=bot_id) + chat_date_list = ( + await chat_query.filter(create_time__gte=filter_date) + .annotate(date=RawSQL("DATE(create_time)"), count=Count("id")) + .group_by("date") + .values("date", "count") + ) + call_date_list = ( + await call_query.filter(create_time__gte=filter_date) + .annotate(date=RawSQL("DATE(create_time)"), count=Count("id")) + .group_by("date") + .values("date", "count") + ) + date_list = [] + chat_count_list = [] + call_count_list = [] + chat_date2cnt = {str(date["date"]): date["count"] for date in chat_date_list} + call_date2cnt = {str(date["date"]): date["count"] for date in call_date_list} + date = now.date() + for _ in range(30): + if str(date) in chat_date2cnt: + chat_count_list.append(chat_date2cnt[str(date)]) + else: + chat_count_list.append(0) + if str(date) in call_date2cnt: + call_count_list.append(call_date2cnt[str(date)]) + else: + call_count_list.append(0) + date_list.append(str(date)[5:]) + date -= timedelta(days=1) + chat_count_list.reverse() + call_count_list.reverse() + date_list.reverse() + return ChatCallMonthCount( + chat=chat_count_list, call=call_count_list, date=date_list + ) + + @classmethod + async def get_connect_log(cls, query: QueryModel) -> BaseResultModel: + """获取bot连接日志 + + 参数: + query: 查询模型 + + 返回: + BaseResultModel: 数据内容 + """ + total = await BotConnectLog.all().count() + if total % query.size: + total += 1 + data = ( + await BotConnectLog.all() + .order_by("-id") + .offset((query.index - 1) * query.size) + .limit(query.size) + ) + result_list = [] + for v in data: + v.connect_time = v.connect_time.replace(tzinfo=None).replace(microsecond=0) + result_list.append( + BotConnectLogInfo( + bot_id=v.bot_id, connect_time=v.connect_time, type=v.type + ) + ) + return BaseResultModel(total=total, data=result_list) diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/model.py new file mode 100644 index 00000000..d4161b39 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/model.py @@ -0,0 +1,82 @@ +from datetime import datetime + +from pydantic import BaseModel + + +class BotConnectLogInfo(BaseModel): + bot_id: str + """机器人ID""" + connect_time: datetime + """连接日期""" + type: int + """连接类型""" + + +class BotInfo(BaseModel): + self_id: str + """SELF ID""" + nickname: str + """昵称""" + ava_url: str + """头像url""" + platform: str + """平台""" + friend_count: int = 0 + """好友数量""" + group_count: int = 0 + """群聊数量""" + received_messages: int = 0 + """今日消息接收""" + day_call: int = 0 + """今日调用插件次数""" + connect_time: int = 0 + """连接时间""" + connect_date: str | None = None + """连接日期""" + + +class QueryChatCallCount(BaseModel): + """ + 查询聊天/调用记录次数 + """ + + chat_num: int + """聊天记录总数""" + chat_day: int + """今日消息""" + call_num: int + """调用记录总数""" + call_day: int + """今日调用""" + + +class ChatCallMonthCount(BaseModel): + """ + 查询聊天/调用一个月记录次数 + """ + + chat: list[int] + """一个月内聊天总数""" + call: list[int] + """一个月内调用数据""" + date: list[str] + """日期""" + + +class AllChatAndCallCount(BaseModel): + """ + 查询聊天/调用记录次数 + """ + + chat_week: int + """一周内聊天次数""" + chat_month: int + """一月内聊天次数""" + chat_year: int + """一年内聊天次数""" + call_week: int + """一周内调用次数""" + call_month: int + """一月内调用次数""" + call_year: int + """一年内调用次数""" diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py new file mode 100644 index 00000000..ed6df2e3 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py @@ -0,0 +1,145 @@ +from fastapi import APIRouter, Request +from fastapi.responses import JSONResponse +import nonebot +from nonebot.drivers import Driver +from tortoise import Tortoise + +from zhenxun.configs.config import BotConfig +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger + +from ....base_model import BaseResultModel, QueryModel, Result +from ....utils import authentication +from .data_source import ApiDataSource, type2sql +from .models.model import Column, SqlLogInfo, SqlModel, SqlText +from .models.sql_log import SqlLog + +router = APIRouter(prefix="/database") + + +driver: Driver = nonebot.get_driver() + + +@driver.on_startup +async def _(): + for plugin in nonebot.get_loaded_plugins(): + module = plugin.name + sql_list = [] + if plugin.metadata and plugin.metadata.extra: + sql_list = plugin.metadata.extra.get("sql_list") + if module in ApiDataSource.SQL_DICT: + raise ValueError(f"{module} 常用SQL module 重复") + if sql_list: + SqlModel( + name="", + module=module, + sql_list=sql_list, + ) + ApiDataSource.SQL_DICT[module] = SqlModel + if ApiDataSource.SQL_DICT: + result = await PluginInfo.filter( + module__in=ApiDataSource.SQL_DICT.keys() + ).values_list("module", "name") + module2name = {r[0]: r[1] for r in result} + for s in ApiDataSource.SQL_DICT: + module = ApiDataSource.SQL_DICT[s].module + ApiDataSource.SQL_DICT[s].name = module2name.get(module, module) + + +@router.get( + "/get_table_list", + dependencies=[authentication()], + response_model=Result[list[dict]], + response_class=JSONResponse, + description="获取数据库表", +) +async def _() -> Result[list[dict]]: + try: + db = Tortoise.get_connection("default") + sql_type = BotConfig.get_sql_type() + query = await db.execute_query_dict(type2sql[sql_type]) + return Result.ok(query) + except Exception as e: + logger.error(f"{router.prefix}/get_table_list 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_table_column", + dependencies=[authentication()], + response_model=Result[list[Column]], + response_class=JSONResponse, + description="获取表字段", +) +async def _(table_name: str) -> Result[list[Column]]: + try: + return Result.ok( + await ApiDataSource.get_table_column(table_name), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_table_column 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/exec_sql", + dependencies=[authentication()], + response_model=Result[list[dict]], + response_class=JSONResponse, + description="执行sql", +) +async def _(sql: SqlText, request: Request) -> Result[list[dict]]: + ip = request.client.host if request.client else "unknown" + try: + if sql.sql.lower().startswith("select"): + db = Tortoise.get_connection("default") + res = await db.execute_query_dict(sql.sql) + await SqlLog.add(ip or "0.0.0.0", sql.sql, "") + return Result.ok(res, "执行成功啦!") + else: + result = await TaskInfo.raw(sql.sql) + await SqlLog.add(ip or "0.0.0.0", sql.sql, str(result)) + return Result.ok(info="执行成功啦!") + except Exception as e: + logger.error(f"{router.prefix}/exec_sql 调用错误", "WebUi", e=e) + await SqlLog.add(ip or "0.0.0.0", sql.sql, str(e), False) + return Result.warning_(f"sql执行错误: {e}") + + +@router.post( + "/get_sql_log", + dependencies=[authentication()], + response_model=Result[BaseResultModel], + response_class=JSONResponse, + description="sql日志列表", +) +async def _(query: QueryModel) -> Result[BaseResultModel]: + try: + total = await SqlLog.all().count() + if total % query.size: + total += 1 + data = ( + await SqlLog.all() + .order_by("-id") + .offset((query.index - 1) * query.size) + .limit(query.size) + ) + result_list = [SqlLogInfo(sql=e.sql) for e in data] + return Result.ok(BaseResultModel(total=total, data=result_list)) + except Exception as e: + logger.error(f"{router.prefix}/get_sql_log 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_common_sql", + dependencies=[authentication()], + response_model=Result[dict], + response_class=JSONResponse, + description="常用sql", +) +async def _(plugin_name: str | None = None) -> Result[dict]: + if plugin_name: + return Result.ok(ApiDataSource.SQL_DICT.get(plugin_name)) + return Result.ok(str(ApiDataSource.SQL_DICT)) diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/database/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/data_source.py new file mode 100644 index 00000000..9e937837 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/database/data_source.py @@ -0,0 +1,90 @@ +from tortoise import Tortoise + +from zhenxun.configs.config import BotConfig + +from .models.model import Column + +SELECT_TABLE_MYSQL_SQL = """ +SELECT table_name AS name, table_comment AS `desc` +FROM information_schema.tables +WHERE table_schema = DATABASE(); +""" + +SELECT_TABLE_SQLITE_SQL = """ +SELECT name FROM sqlite_master WHERE type='table'; +""" + +SELECT_TABLE_PSQL_SQL = """ +select a.tablename as name,d.description as desc from pg_tables a + left join pg_class c on relname=tablename + left join pg_description d on oid=objoid and objsubid=0 where a.schemaname='public' +""" + +SELECT_TABLE_COLUMN_PSQL_SQL = """ +SELECT column_name, data_type, character_maximum_length as max_length, is_nullable +FROM information_schema.columns +WHERE table_name = '{}'; +""" + +SELECT_TABLE_COLUMN_MYSQL_SQL = """ +SHOW COLUMNS FROM {}; +""" + +SELECT_TABLE_COLUMN_SQLITE_SQL = """ +PRAGMA table_info({}); +""" + +type2sql = { + "mysql": SELECT_TABLE_MYSQL_SQL, + "sqlite": SELECT_TABLE_SQLITE_SQL, + "postgres": SELECT_TABLE_PSQL_SQL, +} + +type2sql_column = { + "mysql": SELECT_TABLE_COLUMN_MYSQL_SQL, + "sqlite": SELECT_TABLE_COLUMN_SQLITE_SQL, + "postgres": SELECT_TABLE_COLUMN_PSQL_SQL, +} + + +class ApiDataSource: + SQL_DICT = {} # noqa: RUF012 + + @classmethod + async def get_table_column(cls, table_name: str) -> list[Column]: + """获取表字段信息 + + 参数: + table_name: 表名 + + 返回: + list[Column]: 字段数据 + """ + db = Tortoise.get_connection("default") + sql_type = BotConfig.get_sql_type() + sql = type2sql_column[sql_type] + query = await db.execute_query_dict(sql.format(table_name)) + result_list = [] + if sql_type == "sqlite": + result_list.extend( + Column( + column_name=result["name"], + data_type=result["type"], + max_length=-1, + is_nullable="YES" if result["notnull"] == 1 else "NO", + ) + for result in query + ) + elif sql_type == "mysql": + result_list.extend( + Column( + column_name=result["Field"], + data_type=result["Type"], + max_length=-1, + is_nullable=result["Null"], + ) + for result in query + ) + else: + result_list.extend(Column(**result) for result in query) + return result_list diff --git a/zhenxun/plugins/web_ui/api/tabs/database/models/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/models/model.py similarity index 54% rename from zhenxun/plugins/web_ui/api/tabs/database/models/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/database/models/model.py index e18e4cfb..73367d1f 100644 --- a/zhenxun/plugins/web_ui/api/tabs/database/models/model.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/database/models/model.py @@ -3,6 +3,11 @@ from pydantic import BaseModel from zhenxun.utils.plugin_models.base import CommonSql +class SqlLogInfo(BaseModel): + sql: str + """sql语句""" + + class SqlText(BaseModel): """ sql语句 @@ -22,3 +27,18 @@ class SqlModel(BaseModel): """插件名称""" sql_list: list[CommonSql] """插件列表""" + + +class Column(BaseModel): + """ + 列 + """ + + column_name: str + """列名""" + data_type: str + """数据类型""" + max_length: int | None + """最大长度""" + is_nullable: str + """是否可为空""" diff --git a/zhenxun/plugins/web_ui/api/tabs/database/models/sql_log.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/models/sql_log.py similarity index 92% rename from zhenxun/plugins/web_ui/api/tabs/database/models/sql_log.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/database/models/sql_log.py index 691f1b5a..b5ef7189 100644 --- a/zhenxun/plugins/web_ui/api/tabs/database/models/sql_log.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/database/models/sql_log.py @@ -4,7 +4,6 @@ from zhenxun.services.db_context import Model class SqlLog(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" ip = fields.CharField(255) @@ -18,7 +17,7 @@ class SqlLog(Model): create_time = fields.DatetimeField(auto_now_add=True) """创建时间""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "sql_log" table_description = "sql执行日志" diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/main/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/main/__init__.py new file mode 100644 index 00000000..36059101 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/main/__init__.py @@ -0,0 +1,253 @@ +import asyncio +import contextlib +import time + +from fastapi import APIRouter +from fastapi.responses import JSONResponse +import nonebot +from nonebot.config import Config +from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState +from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK + +from zhenxun.models.bot_console import BotConsole +from zhenxun.services.log import logger +from zhenxun.utils.common_utils import CommonUtils +from zhenxun.utils.platform import PlatformUtils + +from ....base_model import Result +from ....config import QueryDateType +from ....utils import authentication, get_system_status +from .data_source import ApiDataSource +from .model import ( + ActiveGroup, + BaseInfo, + BotBlockModule, + BotManageUpdateParam, + BotStatusParam, + HotPlugin, + NonebotData, + QueryCount, +) + +driver = nonebot.get_driver() +run_time = time.time() + +ws_router = APIRouter() +router = APIRouter(prefix="/main") + + +@router.get( + "/get_base_info", + dependencies=[authentication()], + response_model=Result[list[BaseInfo]], + response_class=JSONResponse, + description="基础信息", +) +async def _(bot_id: str | None = None) -> Result[list[BaseInfo]]: + """获取Bot基础信息 + + 参数: + bot_id (Optional[str], optional): bot_id. Defaults to None. + + 返回: + Result: 获取指定bot信息与bot列表 + """ + try: + result = await ApiDataSource.get_base_info(bot_id) + if not result: + Result.warning_("无Bot连接...") + return Result.ok(result, "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_base_info 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_all_chat_count", + dependencies=[authentication()], + response_model=Result[QueryCount], + response_class=JSONResponse, + description="获取接收消息数量", +) +async def _(bot_id: str | None = None) -> Result[QueryCount]: + try: + return Result.ok(await ApiDataSource.get_all_chat_count(bot_id), "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_all_chat_count 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_all_call_count", + dependencies=[authentication()], + response_model=Result[QueryCount], + response_class=JSONResponse, + description="获取调用次数", +) +async def _(bot_id: str | None = None) -> Result[QueryCount]: + try: + return Result.ok(await ApiDataSource.get_all_call_count(bot_id), "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_all_call_count 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "get_fg_count", + dependencies=[authentication()], + response_model=Result[dict[str, int]], + response_class=JSONResponse, + description="好友/群组数量", +) +async def _(bot_id: str) -> Result[dict[str, int]]: + try: + bot = nonebot.get_bot(bot_id) + data = { + "friend_count": len(await PlatformUtils.get_friend_list(bot)), + "group_count": len(await PlatformUtils.get_group_list(bot)), + } + return Result.ok(data, "拿到信息啦!") + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/get_fg_count 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_nb_data", + dependencies=[authentication()], + response_model=Result[NonebotData], + response_class=JSONResponse, + description="获取nb数据", +) +async def _() -> Result[NonebotData]: + global run_time + return Result.ok(NonebotData(config=driver.config, run_time=int(run_time))) + + +@router.get( + "/get_nb_config", + dependencies=[authentication()], + response_model=Result[Config], + response_class=JSONResponse, + description="获取nb配置", +) +async def _() -> Result[Config]: + return Result.ok(driver.config) + + +@router.get( + "/get_run_time", + dependencies=[authentication()], + response_model=Result[int], + response_class=JSONResponse, + description="获取nb运行时间", +) +async def _() -> Result[int]: + global run_time + return Result.ok(int(run_time)) + + +@router.get( + "/get_active_group", + dependencies=[authentication()], + response_model=Result[list[ActiveGroup]], + response_class=JSONResponse, + description="获取活跃群聊", +) +async def _( + date_type: QueryDateType | None = None, bot_id: str | None = None +) -> Result[list[ActiveGroup]]: + try: + return Result.ok( + await ApiDataSource.get_active_group(date_type, bot_id), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_active_group 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_hot_plugin", + dependencies=[authentication()], + response_model=Result[list[HotPlugin]], + response_class=JSONResponse, + description="获取热门插件", +) +async def _( + date_type: QueryDateType | None = None, bot_id: str | None = None +) -> Result[list[HotPlugin]]: + try: + return Result.ok( + await ApiDataSource.get_hot_plugin(date_type, bot_id), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_hot_plugin 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/change_bot_status", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="修改bot全局开关", +) +async def _(param: BotStatusParam): + try: + await BotConsole.set_bot_status(param.status, param.bot_id) + return Result.ok(info="修改bot全局开关成功!") + except (ValueError, KeyError): + return Result.fail("Bot未初始化...") + + +@router.get( + "/get_bot_block_module", + dependencies=[authentication()], + response_model=Result[BotBlockModule], + response_class=JSONResponse, + description="获取bot层面的禁用模块", +) +async def _(bot_id: str) -> Result[BotBlockModule]: + try: + return Result.ok( + await ApiDataSource.get_bot_block_module(bot_id), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_bot_block_module 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/update_bot_manage", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="修改bot全局开关", +) +async def _(param: BotManageUpdateParam): + try: + bot_data = await BotConsole.get_or_none(bot_id=param.bot_id) + if not bot_data: + return Result.fail("Bot数据不存在...") + bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins) + bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks) + await bot_data.save(update_fields=["block_plugins", "block_tasks"]) + return Result.ok() + except Exception as e: + logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@ws_router.websocket("/system_status") +async def system_logs_realtime(websocket: WebSocket, sleep: int = 5): + await websocket.accept() + logger.debug("ws system_status is connect") + with contextlib.suppress( + WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK + ): + while websocket.client_state == WebSocketState.CONNECTED: + system_status = await get_system_status() + await websocket.send_text(system_status.json()) + await asyncio.sleep(sleep) diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/main/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/main/data_source.py new file mode 100644 index 00000000..f9ff6fca --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/main/data_source.py @@ -0,0 +1,395 @@ +from datetime import datetime, timedelta +from pathlib import Path +import time + +import nonebot +from nonebot.adapters import Bot +from nonebot.drivers import Driver +from tortoise.functions import Count + +from zhenxun.models.bot_connect_log import BotConnectLog +from zhenxun.models.bot_console import BotConsole +from zhenxun.models.chat_history import ChatHistory +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.statistics import Statistics +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger +from zhenxun.utils.common_utils import CommonUtils +from zhenxun.utils.enum import PluginType +from zhenxun.utils.platform import PlatformUtils + +from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType +from .model import ( + ActiveGroup, + BaseInfo, + BotBlockModule, + HotPlugin, + QueryCount, + TemplateBaseInfo, +) + +driver: Driver = nonebot.get_driver() + + +class BotLive: + def __init__(self): + self._data = {} + + def add(self, bot_id: str): + self._data[bot_id] = int(time.time()) + + def get(self, bot_id: str) -> int | None: + return self._data.get(bot_id) + + def remove(self, bot_id: str): + if bot_id in self._data: + del self._data[bot_id] + + +bot_live = BotLive() + + +@driver.on_bot_connect +async def _(bot: Bot): + bot_live.add(bot.self_id) + + +@driver.on_bot_disconnect +async def _(bot: Bot): + bot_live.remove(bot.self_id) + + +class ApiDataSource: + @classmethod + async def __build_bot_info(cls, bot: Bot) -> TemplateBaseInfo: + """构建bot信息 + + 参数: + bot: bot实例 + + 返回: + TemplateBaseInfo: bot信息 + """ + login_info = None + try: + login_info = await bot.get_login_info() + except Exception as e: + logger.warning("调用接口get_login_info失败", "WebUi", e=e) + return TemplateBaseInfo( + bot=bot, + self_id=bot.self_id, + nickname=login_info["nickname"] if login_info else bot.self_id, + ava_url=AVA_URL.format(bot.self_id), + ) + + @classmethod + def __get_bot_version(cls) -> str: + """获取bot版本 + + 返回: + str | None: 版本 + """ + version_file = Path() / "__version__" + if version_file.exists(): + if text := version_file.open().read(): + return text.replace("__version__: ", "").strip() + return "unknown" + + @classmethod + async def __init_bot_base_data(cls, select_bot: TemplateBaseInfo): + """初始化bot的基础数据 + + 参数: + select_bot: bot + """ + now = datetime.now() + # 今日累计接收消息 + select_bot.received_messages = await ChatHistory.filter( + bot_id=select_bot.self_id, + create_time__gte=now - timedelta(hours=now.hour), + ).count() + # 群聊数量 + try: + select_bot.group_count = len( + (await PlatformUtils.get_group_list(select_bot.bot, True))[0] + ) + # 好友数量 + select_bot.friend_count = len( + (await PlatformUtils.get_friend_list(select_bot.bot))[0] + ) + except Exception as e: + logger.warning("获取bot好友/群组信息失败...", "WebUi", e=e) + select_bot.group_count = 0 + select_bot.friend_count = 0 + select_bot.status = await BotConsole.get_bot_status(select_bot.self_id) + # 连接时间 + select_bot.connect_time = bot_live.get(select_bot.self_id) or 0 + if select_bot.connect_time: + connect_date = datetime.fromtimestamp(select_bot.connect_time) + select_bot.connect_date = connect_date.strftime("%Y-%m-%d %H:%M:%S") + select_bot.version = cls.__get_bot_version() + day_call = await Statistics.filter( + create_time__gte=now - timedelta(hours=now.hour) + ).count() + select_bot.day_call = day_call + select_bot.connect_count = await BotConnectLog.filter( + bot_id=select_bot.self_id + ).count() + + @classmethod + async def get_base_info(cls, bot_id: str | None) -> list[BaseInfo] | None: + """获取bot信息 + + 参数: + bot_id: bot id + + 返回: + list[BaseInfo] | None: bot列表 + """ + bots = nonebot.get_bots() + if not bots: + return None + select_bot: BaseInfo + bot_list = [await cls.__build_bot_info(bot) for _, bot in bots.items()] + # 获取指定qq号的bot信息,若无指定 则获取第一个 + if _bl := [b for b in bot_list if b.self_id == bot_id]: + select_bot = _bl[0] + else: + select_bot = bot_list[0] + await cls.__init_bot_base_data(select_bot) + for bot in bot_list: + bot.bot = None # type: ignore + select_bot.is_select = True + return [BaseInfo(**e.to_dict()) for e in bot_list] + + @classmethod + async def get_all_chat_count(cls, bot_id: str | None) -> QueryCount: + """获取年/月/周/日聊天次数 + + 参数: + bot_id: bot id + + 返回: + QueryCount: 数据内容 + """ + now = datetime.now() + query = ChatHistory + if bot_id: + query = query.filter(bot_id=bot_id) + all_count = await query.annotate().count() + day_count = await query.filter( + create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute) + ).count() + week_count = await query.filter( + create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute) + ).count() + month_count = await query.filter( + create_time__gte=now + - timedelta(days=30, hours=now.hour, minutes=now.minute) + ).count() + year_count = await query.filter( + create_time__gte=now + - timedelta(days=365, hours=now.hour, minutes=now.minute) + ).count() + return QueryCount( + num=all_count, + day=day_count, + week=week_count, + month=month_count, + year=year_count, + ) + + @classmethod + async def get_all_call_count(cls, bot_id: str | None) -> QueryCount: + """获取年/月/周/日调用次数 + + 参数: + bot_id: bot id + + 返回: + QueryCount: 数据内容 + """ + now = datetime.now() + query = Statistics + if bot_id: + query = query.filter(bot_id=bot_id) + all_count = await query.annotate().count() + day_count = await query.filter( + create_time__gte=now - timedelta(hours=now.hour, minutes=now.minute) + ).count() + week_count = await query.filter( + create_time__gte=now - timedelta(days=7, hours=now.hour, minutes=now.minute) + ).count() + month_count = await query.filter( + create_time__gte=now + - timedelta(days=30, hours=now.hour, minutes=now.minute) + ).count() + year_count = await query.filter( + create_time__gte=now + - timedelta(days=365, hours=now.hour, minutes=now.minute) + ).count() + return QueryCount( + num=all_count, + day=day_count, + week=week_count, + month=month_count, + year=year_count, + ) + + @classmethod + def __get_query( + cls, + base_query: type[ChatHistory | Statistics], + date_type: QueryDateType | None = None, + bot_id: str | None = None, + ): + """构建日期查询条件 + + 参数: + date_type: 日期类型. + bot_id: bot id. + """ + query = base_query + now = datetime.now() + if bot_id: + query = query.filter(bot_id=bot_id) + if date_type == QueryDateType.DAY: + query = query.filter( + create_time__gte=now + - timedelta(hours=now.hour, minutes=now.minute, seconds=now.second) + ) + if date_type == QueryDateType.WEEK: + query = query.filter( + create_time__gte=now + - timedelta( + days=7, hours=now.hour, minutes=now.minute, seconds=now.second + ) + ) + if date_type == QueryDateType.MONTH: + query = query.filter( + create_time__gte=now + - timedelta( + days=30, hours=now.hour, minutes=now.minute, seconds=now.second + ) + ) + if date_type == QueryDateType.YEAR: + query = query.filter( + create_time__gte=now + - timedelta( + days=365, hours=now.hour, minutes=now.minute, seconds=now.second + ) + ) + return query + + @classmethod + async def get_active_group( + cls, date_type: QueryDateType | None = None, bot_id: str | None = None + ) -> list[ActiveGroup]: + """获取活跃群组 + + 参数: + date_type: 日期类型. + bot_id: bot id. + + 返回: + list[ActiveGroup]: 活跃群组列表 + """ + query = cls.__get_query(ChatHistory, date_type, bot_id) + data_list = ( + await query.annotate(count=Count("id")) + .filter(group_id__not_isnull=True) + .group_by("group_id") + .order_by("-count") + .limit(5) + .values_list("group_id", "count") + ) + id2name = {} + if data_list: + if info_list := await GroupConsole.filter( + group_id__in=[x[0] for x in data_list] + ).all(): + for group_info in info_list: + id2name[group_info.group_id] = group_info.group_name + active_group_list = [ + ActiveGroup( + group_id=data[0], + name=id2name.get(data[0]) or data[0], + chat_num=data[1], + ava_img=GROUP_AVA_URL.format(data[0], data[0]), + ) + for data in data_list + ] + active_group_list = sorted( + active_group_list, key=lambda x: x.chat_num, reverse=True + ) + if len(active_group_list) > 5: + active_group_list = active_group_list[:5] + return active_group_list + + @classmethod + async def get_hot_plugin( + cls, date_type: QueryDateType | None = None, bot_id: str | None = None + ) -> list[HotPlugin]: + """获取热门插件 + + 参数: + date_type: 日期类型. + bot_id: bot id. + + 返回: + list[HotPlugin]: 热门插件列表 + """ + query = cls.__get_query(Statistics, date_type, bot_id) + data_list = ( + await query.annotate(count=Count("id")) + .group_by("plugin_name") + .order_by("-count") + .limit(5) + .values_list("plugin_name", "count") + ) + hot_plugin_list = [] + module_list = [x[0] for x in data_list] + plugins = await PluginInfo.filter(module__in=module_list).all() + module2name = {p.module: p.name for p in plugins} + for data in data_list: + module = data[0] + name = module2name.get(module) or module + hot_plugin_list.append(HotPlugin(module=module, name=name, count=data[1])) + hot_plugin_list = sorted(hot_plugin_list, key=lambda x: x.count, reverse=True) + if len(hot_plugin_list) > 5: + hot_plugin_list = hot_plugin_list[:5] + return hot_plugin_list + + @classmethod + async def get_bot_block_module(cls, bot_id: str) -> BotBlockModule | None: + """获取bot层面的禁用模块 + + 参数: + bot_id: bot id + + 返回: + BotBlockModule | None: 数据内容 + """ + bot_data = await BotConsole.get_or_none(bot_id=bot_id) + if not bot_data: + return None + block_tasks = [] + block_plugins = [] + all_plugins = await PluginInfo.filter( + load_status=True, plugin_type=PluginType.NORMAL + ).values("module", "name") + all_task = await TaskInfo.annotate().values("module", "name") + if bot_data.block_tasks: + tasks = CommonUtils.convert_module_format(bot_data.block_tasks) + block_tasks = [t["module"] for t in all_task if t["module"] in tasks] + if bot_data.block_plugins: + plugins = CommonUtils.convert_module_format(bot_data.block_plugins) + block_plugins = [t["module"] for t in all_plugins if t["module"] in plugins] + return BotBlockModule( + bot_id=bot_id, + block_tasks=block_tasks, + block_plugins=block_plugins, + all_plugins=all_plugins, + all_tasks=all_task, + ) diff --git a/zhenxun/plugins/web_ui/api/tabs/main/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/main/model.py similarity index 56% rename from zhenxun/plugins/web_ui/api/tabs/main/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/main/model.py index c9d76706..19932393 100644 --- a/zhenxun/plugins/web_ui/api/tabs/main/model.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/main/model.py @@ -1,10 +1,46 @@ -from datetime import datetime +from typing import Any from nonebot.adapters import Bot +from nonebot.compat import model_dump from nonebot.config import Config from pydantic import BaseModel +class BotManageUpdateParam(BaseModel): + """bot更新参数""" + + bot_id: str + """bot id""" + block_plugins: list[str] + """禁用插件""" + block_tasks: list[str] + """禁用被动""" + + +class BotStatusParam(BaseModel): + """bot状态参数""" + + bot_id: str + """bot id""" + status: bool + """状态""" + + +class BotBlockModule(BaseModel): + """bot禁用模块参数""" + + bot_id: str + """bot id""" + block_plugins: list[str] + """禁用插件""" + block_tasks: list[str] + """禁用被动""" + all_plugins: list[dict[str, Any]] + """所有插件""" + all_tasks: list[dict[str, Any]] + """所有被动""" + + class SystemStatus(BaseModel): """ 系统状态 @@ -20,8 +56,6 @@ class BaseInfo(BaseModel): 基础信息 """ - bot: Bot - """Bot""" self_id: str """SELF ID""" nickname: str @@ -36,21 +70,15 @@ class BaseInfo(BaseModel): """今日 累计接收消息""" connect_time: int = 0 """连接时间""" - connect_date: datetime | None = None + connect_date: str | None = None """连接日期""" - - plugin_count: int = 0 - """加载插件数量""" - success_plugin_count: int = 0 - """加载成功插件数量""" - fail_plugin_count: int = 0 - """加载失败插件数量""" + connect_count: int = 0 + """连接次数""" + status: bool = False + """全局状态""" is_select: bool = False """当前选择""" - - config: Config | None = None - """nb配置""" day_call: int = 0 """今日调用插件次数""" version: str = "unknown" @@ -59,8 +87,20 @@ class BaseInfo(BaseModel): class Config: arbitrary_types_allowed = True + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) -class ChatHistoryCount(BaseModel): + +class TemplateBaseInfo(BaseInfo): + """ + 基础信息 + """ + + bot: Bot + """bot""" + + +class QueryCount(BaseModel): """ 聊天记录数量 """ @@ -103,3 +143,10 @@ class HotPlugin(BaseModel): """插件名称""" count: int """调用次数""" + + +class NonebotData(BaseModel): + config: Config + """nb配置""" + run_time: int + """运行时间""" diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/manage/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/__init__.py new file mode 100644 index 00000000..dfa9dd31 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/__init__.py @@ -0,0 +1,338 @@ +from fastapi import APIRouter +from fastapi.responses import JSONResponse +import nonebot +from nonebot.adapters.onebot.v11 import ActionFailed + +from zhenxun.models.fg_request import FgRequest +from zhenxun.models.group_console import GroupConsole +from zhenxun.services.log import logger +from zhenxun.utils.enum import RequestHandleType, RequestType +from zhenxun.utils.exception import NotFoundError +from zhenxun.utils.platform import PlatformUtils + +from ....base_model import Result +from ....config import AVA_URL, GROUP_AVA_URL +from ....utils import authentication +from .data_source import ApiDataSource +from .model import ( + ClearRequest, + DeleteFriend, + Friend, + GroupDetail, + GroupResult, + HandleRequest, + LeaveGroup, + ReqResult, + SendMessageParam, + UpdateGroup, + UserDetail, +) + +router = APIRouter(prefix="/manage") + + +@router.get( + "/get_group_list", + dependencies=[authentication()], + response_model=Result[list[GroupResult]], + response_class=JSONResponse, + description="获取群组列表", +) +async def _(bot_id: str) -> Result: + """ + 获取群信息 + """ + group_list_result = [] + try: + bot = nonebot.get_bot(bot_id) + group_list, _ = await PlatformUtils.get_group_list(bot) + for g in group_list: + ava_url = GROUP_AVA_URL.format(g.group_id, g.group_id) + group_list_result.append( + GroupResult( + group_id=g.group_id, group_name=g.group_name, ava_url=ava_url + ) + ) + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/get_group_list 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + return Result.ok(group_list_result, "拿到了新鲜出炉的数据!") + + +@router.post( + "/update_group", + dependencies=[authentication()], + response_model=Result[str], + response_class=JSONResponse, + description="修改群组信息", +) +async def _(group: UpdateGroup) -> Result[str]: + try: + await ApiDataSource.update_group(group) + return Result.ok(info="已完成记录!") + except Exception as e: + logger.error(f"{router.prefix}/update_group 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_friend_list", + dependencies=[authentication()], + response_model=Result[list[Friend]], + response_class=JSONResponse, + description="获取好友列表", +) +async def _(bot_id: str) -> Result[list[Friend]]: + try: + bot = nonebot.get_bot(bot_id) + friend_list, _ = await PlatformUtils.get_friend_list(bot) + result_list = [] + for f in friend_list: + ava_url = AVA_URL.format(f.user_id) + result_list.append( + Friend(user_id=f.user_id, nickname=f.user_name or "", ava_url=ava_url) + ) + return Result.ok( + result_list, + "拿到了新鲜出炉的数据!", + ) + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error("调用API错误", "/get_group_list", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.get( + "/get_request_count", + dependencies=[authentication()], + response_model=Result[dict[str, int]], + response_class=JSONResponse, + description="获取请求数量", +) +async def _() -> Result[dict[str, int]]: + try: + f_count = await FgRequest.filter( + request_type=RequestType.FRIEND, handle_type__isnull=True + ).count() + g_count = await FgRequest.filter( + request_type=RequestType.GROUP, handle_type__isnull=True + ).count() + data = { + "friend_count": f_count, + "group_count": g_count, + } + return Result.ok(data, "拿到了新鲜出炉的数据!") + except Exception as e: + logger.error("调用API错误", "/get_request_count", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.get( + "/get_request_list", + dependencies=[authentication()], + response_model=Result[ReqResult], + response_class=JSONResponse, + description="获取请求列表", +) +async def _() -> Result[ReqResult]: + try: + return Result.ok(await ApiDataSource.get_request_list(), "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_request_list 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/clear_request", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="清空请求列表", +) +async def _(cr: ClearRequest) -> Result: + await FgRequest.filter( + handle_type__isnull=True, request_type=cr.request_type + ).update(handle_type=RequestHandleType.IGNORE) + return Result.ok(info="成功清除了数据!") + + +@router.post( + "/refuse_request", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="拒绝请求", +) +async def _(param: HandleRequest) -> Result: + try: + bot = nonebot.get_bot(param.bot_id) + try: + await FgRequest.refused(bot, param.id) + except ActionFailed: + await FgRequest.expire(param.id) + return Result.warning_("请求失败,可能该请求已失效或请求数据错误...") + except NotFoundError: + return Result.warning_("未找到此Id请求...") + return Result.ok(info="成功处理了请求!") + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/refuse_request 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post( + "/delete_request", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="忽略请求", +) +async def _(param: HandleRequest) -> Result: + await FgRequest.ignore(param.id) + return Result.ok(info="成功处理了请求!") + + +@router.post( + "/approve_request", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="同意请求", +) +async def _(param: HandleRequest) -> Result: + try: + bot = nonebot.get_bot(param.bot_id) + if not (req := await FgRequest.get_or_none(id=param.id)): + return Result.warning_("未找到此Id请求...") + if req.request_type == RequestType.GROUP: + if group := await GroupConsole.get_group(group_id=req.group_id): + group.group_flag = 1 + await group.save(update_fields=["group_flag"]) + else: + await GroupConsole.update_or_create( + group_id=req.group_id, + defaults={"group_flag": 1}, + ) + try: + await FgRequest.approve(bot, param.id) + return Result.ok(info="成功处理了请求!") + except ActionFailed: + await FgRequest.expire(param.id) + return Result.warning_("请求失败,可能该请求已失效或请求数据错误...") + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/approve_request 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post( + "/leave_group", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="退群", +) +async def _(param: LeaveGroup) -> Result: + try: + bot = nonebot.get_bot(param.bot_id) + platform = PlatformUtils.get_platform(bot) + if platform != "qq": + return Result.warning_("该平台不支持退群操作...") + group_list, _ = await PlatformUtils.get_group_list(bot) + if param.group_id not in [g.group_id for g in group_list]: + return Result.warning_("Bot未在该群聊中...") + await bot.set_group_leave(group_id=param.group_id) + return Result.ok(info="成功处理了请求!") + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/leave_group 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post( + "/delete_friend", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="删除好友", +) +async def _(param: DeleteFriend) -> Result: + try: + bot = nonebot.get_bot(param.bot_id) + platform = PlatformUtils.get_platform(bot) + if platform != "qq": + return Result.warning_("该平台不支持删除好友操作...") + friend_list, _ = await PlatformUtils.get_friend_list(bot) + if param.user_id not in [f.user_id for f in friend_list]: + return Result.warning_("Bot未有其好友...") + await bot.delete_friend(user_id=param.user_id) + return Result.ok(info="成功处理了请求!") + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/delete_friend 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.get( + "/get_friend_detail", + dependencies=[authentication()], + response_model=Result[UserDetail], + response_class=JSONResponse, + description="获取好友详情", +) +async def _(bot_id: str, user_id: str) -> Result[UserDetail]: + try: + result = await ApiDataSource.get_friend_detail(bot_id, user_id) + return ( + Result.ok(result, "拿到信息啦!") + if result + else Result.warning_("未找到该好友...") + ) + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/get_friend_detail 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_group_detail", + dependencies=[authentication()], + response_model=Result[GroupDetail], + response_class=JSONResponse, + description="获取群组详情", +) +async def _(group_id: str) -> Result[GroupDetail]: + try: + return Result.ok(await ApiDataSource.get_group_detail(group_id), "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_group_detail 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/send_message", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="发送消息", +) +async def _(param: SendMessageParam) -> Result: + try: + bot = nonebot.get_bot(param.bot_id) + await PlatformUtils.send_message( + bot, param.user_id, param.group_id, param.message + ) + return Result.ok("发送成功!") + except (ValueError, KeyError): + return Result.warning_("指定Bot未连接...") + except Exception as e: + logger.error(f"{router.prefix}/send_message 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") diff --git a/zhenxun/plugins/web_ui/api/tabs/manage/chat.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/chat.py similarity index 61% rename from zhenxun/plugins/web_ui/api/tabs/manage/chat.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/manage/chat.py index e0171569..d20149fb 100644 --- a/zhenxun/plugins/web_ui/api/tabs/manage/chat.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/chat.py @@ -1,20 +1,15 @@ -import re -from typing import Literal - -import nonebot from fastapi import APIRouter +import nonebot from nonebot import on_message from nonebot.adapters.onebot.v11 import MessageEvent -from nonebot_plugin_alconna import At, Emoji, Hyper, Image, Text, UniMessage, UniMsg -from nonebot_plugin_session import EventSession +from nonebot_plugin_alconna import At, Hyper, Image, Text, UniMsg +from nonebot_plugin_uninfo import Uninfo from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_console import GroupConsole from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.utils.depends import UserName -from ....config import AVA_URL, GROUP_AVA_URL +from ....config import AVA_URL from .model import Message, MessageItem driver = nonebot.get_driver() @@ -27,12 +22,13 @@ ID_LIST = [] ws_router = APIRouter() -matcher = on_message(block=False, priority=1) + +matcher = on_message(block=False, priority=1, rule=lambda: bool(ws_conn)) @driver.on_shutdown async def _(): - if ws_conn: + if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED: await ws_conn.close() @@ -40,11 +36,11 @@ async def _(): async def _(websocket: WebSocket): global ws_conn await websocket.accept() - if not ws_conn: + if not ws_conn or ws_conn.client_state != WebSocketState.CONNECTED: ws_conn = websocket try: while websocket.client_state == WebSocketState.CONNECTED: - recv = await websocket.receive() + await websocket.receive() except WebSocketDisconnect: ws_conn = None @@ -55,7 +51,7 @@ async def message_handle( ): messages = [] for m in message: - if isinstance(m, (Text, str)): + if isinstance(m, Text | str): messages.append(MessageItem(type="text", msg=str(m))) elif isinstance(m, Image): if m.url: @@ -70,42 +66,38 @@ async def message_handle( ID2NAME[group_id] = {} if m.target in ID2NAME[group_id]: uname = ID2NAME[group_id][m.target] - else: - if group_user := await GroupInfoUser.get_or_none( - user_id=m.target, group_id=group_id - ): - uname = group_user.user_name - if m.target not in ID2NAME[group_id]: - ID2NAME[group_id][m.target] = uname + elif group_user := await GroupInfoUser.get_or_none( + user_id=m.target, group_id=group_id + ): + uname = group_user.user_name + if m.target not in ID2NAME[group_id]: + ID2NAME[group_id][m.target] = uname messages.append(MessageItem(type="at", msg=f"@{uname}")) - # elif isinstance(m, Emoji): - # messages.append(MessageItem(type="text", msg=f"[emoji]")) elif isinstance(m, Hyper): - messages.append(MessageItem(type="text", msg=f"[分享消息]")) + messages.append(MessageItem(type="text", msg="[分享消息]")) return messages @matcher.handle() async def _( - message: UniMsg, event: MessageEvent, session: EventSession, uname: str = UserName() + message: UniMsg, event: MessageEvent, session: Uninfo, uname: str = UserName() ): global ws_conn, ID2NAME, ID_LIST - uid = session.id1 - gid = session.id3 or session.id2 - if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED and uid: + if ws_conn and ws_conn.client_state == WebSocketState.CONNECTED: msg_id = event.message_id if msg_id in ID_LIST: return ID_LIST.append(msg_id) if len(ID_LIST) > 50: ID_LIST = ID_LIST[40:] + gid = session.group.id if session.group else None messages = await message_handle(message, gid) data = Message( - object_id=gid or uid, - user_id=uid, + object_id=gid or session.user.id, + user_id=session.user.id, group_id=gid, message=messages, name=uname, - ava_url=AVA_URL.format(uid), + ava_url=AVA_URL.format(session.user.id), ) - await ws_conn.send_json(data.dict()) + await ws_conn.send_json(data.to_dict()) diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/manage/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/data_source.py new file mode 100644 index 00000000..39de7736 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/data_source.py @@ -0,0 +1,274 @@ +import nonebot +from tortoise.functions import Count + +from zhenxun.models.ban_console import BanConsole +from zhenxun.models.chat_history import ChatHistory +from zhenxun.models.fg_request import FgRequest +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.statistics import Statistics +from zhenxun.models.task_info import TaskInfo +from zhenxun.utils.common_utils import CommonUtils +from zhenxun.utils.enum import RequestType +from zhenxun.utils.platform import PlatformUtils + +from ....config import AVA_URL, GROUP_AVA_URL +from .model import ( + FriendRequestResult, + GroupDetail, + GroupRequestResult, + Plugin, + ReqResult, + Task, + UpdateGroup, + UserDetail, +) + + +class ApiDataSource: + @classmethod + async def update_group(cls, group: UpdateGroup): + """更新群组数据 + + 参数: + group: UpdateGroup + """ + db_group = await GroupConsole.get_group(group.group_id) or GroupConsole( + group_id=group.group_id + ) + task_list = await TaskInfo.all().values_list("module", flat=True) + db_group.level = group.level + db_group.status = group.status + if group.close_plugins: + db_group.block_plugin = CommonUtils.convert_module_format( + group.close_plugins + ) + else: + db_group.block_plugin = "" + if group.task: + if block_task := [t for t in task_list if t not in group.task]: + db_group.block_task = CommonUtils.convert_module_format(block_task) # type: ignore + else: + db_group.block_task = CommonUtils.convert_module_format(task_list) # type: ignore + await db_group.save() + + @classmethod + async def get_request_list(cls) -> ReqResult: + """获取好友与群组请求列表 + + 返回: + ReqResult: 数据内容 + """ + req_result = ReqResult() + data_list = await FgRequest.filter(handle_type__isnull=True).all() + for req in data_list: + if req.request_type == RequestType.FRIEND: + req_result.friend.append( + FriendRequestResult( + oid=req.id, + bot_id=req.bot_id, + id=req.user_id, + flag=req.flag, + nickname=req.nickname, + comment=req.comment, + ava_url=AVA_URL.format(req.user_id), + type=str(req.request_type).lower(), + ) + ) + else: + req_result.group.append( + GroupRequestResult( + oid=req.id, + bot_id=req.bot_id, + id=req.user_id, + flag=req.flag, + nickname=req.nickname, + comment=req.comment, + ava_url=GROUP_AVA_URL.format(req.group_id, req.group_id), + type=str(req.request_type).lower(), + invite_group=req.group_id, + group_name=None, + ) + ) + req_result.friend.reverse() + req_result.group.reverse() + return req_result + + @classmethod + async def get_friend_detail(cls, bot_id: str, user_id: str) -> UserDetail | None: + """获取好友详情 + + 参数: + bot_id: bot id + user_id: 用户id + + 返回: + UserDetail | None: 详情数据 + """ + bot = nonebot.get_bot(bot_id) + friend_list, _ = await PlatformUtils.get_friend_list(bot) + fd = [x for x in friend_list if x.user_id == user_id] + if not fd: + return None + like_plugin_list = ( + await Statistics.filter(user_id=user_id) + .annotate(count=Count("id")) + .group_by("plugin_name") + .order_by("-count") + .limit(5) + .values_list("plugin_name", "count") + ) + like_plugin = {} + module_list = [x[0] for x in like_plugin_list] + plugins = await PluginInfo.filter(module__in=module_list).all() + module2name = {p.module: p.name for p in plugins} + for data in like_plugin_list: + name = module2name.get(data[0]) or data[0] + like_plugin[name] = data[1] + user = fd[0] + return UserDetail( + user_id=user_id, + ava_url=AVA_URL.format(user_id), + nickname=user.user_name, + remark="", + is_ban=await BanConsole.is_ban(user_id), + chat_count=await ChatHistory.filter(user_id=user_id).count(), + call_count=await Statistics.filter(user_id=user_id).count(), + like_plugin=like_plugin, + ) + + @classmethod + async def __get_group_detail_like_plugin(cls, group_id: str) -> dict[str, int]: + """获取群组喜爱的插件 + + 参数: + group_id: 群组id + + 返回: + dict[str, int]: 插件与调用次数 + """ + like_plugin_list = ( + await Statistics.filter(group_id=group_id) + .annotate(count=Count("id")) + .group_by("plugin_name") + .order_by("-count") + .limit(5) + .values_list("plugin_name", "count") + ) + like_plugin = {} + plugins = await PluginInfo.get_plugins() + module2name = {p.module: p.name for p in plugins} + for data in like_plugin_list: + name = module2name.get(data[0]) or data[0] + like_plugin[name] = data[1] + return like_plugin + + @classmethod + async def __get_group_detail_disable_plugin( + cls, group: GroupConsole + ) -> list[Plugin]: + """获取群组禁用插件 + + 参数: + group: GroupConsole + + 返回: + list[Plugin]: 禁用插件数据列表 + """ + disable_plugins: list[Plugin] = [] + plugins = await PluginInfo.get_plugins() + module2name = {p.module: p.name for p in plugins} + if group.block_plugin: + for module in CommonUtils.convert_module_format(group.block_plugin): + if module: + plugin = Plugin( + module=module, + plugin_name=module, + is_super_block=False, + ) + plugin.plugin_name = module2name.get(module) or module + disable_plugins.append(plugin) + exists_modules = [p.module for p in disable_plugins] + if group.superuser_block_plugin: + for module in CommonUtils.convert_module_format( + group.superuser_block_plugin + ): + if module and module not in exists_modules: + plugin = Plugin( + module=module, + plugin_name=module, + is_super_block=True, + ) + plugin.plugin_name = module2name.get(module) or module + disable_plugins.append(plugin) + return disable_plugins + + @classmethod + async def __get_group_detail_task(cls, group: GroupConsole) -> list[Task]: + """获取群组被动技能状态 + + 参数: + group: GroupConsole + + 返回: + list[Task]: 群组被动列表 + """ + all_task = await TaskInfo.annotate().values_list("module", "name") + task_module2name = {x[0]: x[1] for x in all_task} + task_list = [] + if group.block_task or group.superuser_block_plugin: + sbp = CommonUtils.convert_module_format(group.superuser_block_task) + tasks = CommonUtils.convert_module_format(group.block_task) + task_list.extend( + Task( + name=task[0], + zh_name=task_module2name.get(task[0]) or task[0], + status=task[0] not in tasks and task[0] not in sbp, + is_super_block=task[0] in sbp, + ) + for task in all_task + ) + else: + task_list.extend( + Task( + name=task[0], + zh_name=task_module2name.get(task[0]) or task[0], + status=True, + is_super_block=False, + ) + for task in all_task + ) + return task_list + + @classmethod + async def get_group_detail(cls, group_id: str) -> GroupDetail | None: + """获取群组详情 + + 参数: + group_id: 群组id + + 返回: + GroupDetail | None: 群组详情数据 + """ + group = await GroupConsole.get_or_none(group_id=group_id) + if not group: + return None + like_plugin = await cls.__get_group_detail_like_plugin(group_id) + disable_plugins: list[Plugin] = await cls.__get_group_detail_disable_plugin( + group + ) + task_list = await cls.__get_group_detail_task(group) + return GroupDetail( + group_id=group_id, + ava_url=GROUP_AVA_URL.format(group_id, group_id), + name=group.group_name, + member_count=group.member_count, + max_member_count=group.max_member_count, + chat_count=await ChatHistory.filter(group_id=group_id).count(), + call_count=await Statistics.filter(group_id=group_id).count(), + like_plugin=like_plugin, + level=group.level, + status=group.status, + close_plugins=disable_plugins, + task=task_list, + ) diff --git a/zhenxun/plugins/web_ui/api/tabs/manage/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/model.py similarity index 91% rename from zhenxun/plugins/web_ui/api/tabs/manage/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/manage/model.py index 64b64e8e..7149cee1 100644 --- a/zhenxun/plugins/web_ui/api/tabs/manage/model.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/model.py @@ -1,6 +1,5 @@ -from typing import Literal - -from pydantic import BaseModel +from nonebot.compat import model_dump +from pydantic import BaseModel, Field from zhenxun.utils.enum import RequestType @@ -31,6 +30,8 @@ class Task(BaseModel): """被动中文名称""" status: bool """状态""" + is_super_block: bool + """是否超级用户禁用""" class Plugin(BaseModel): @@ -171,9 +172,9 @@ class ReqResult(BaseModel): 好友/群组请求列表 """ - friend: list[FriendRequestResult] = [] + friend: list[FriendRequestResult] = Field(default_factory=list) """好友请求列表""" - group: list[GroupRequestResult] = [] + group: list[GroupRequestResult] = Field(default_factory=list) """群组请求列表""" @@ -232,7 +233,6 @@ class GroupDetail(BaseModel): class MessageItem(BaseModel): - type: str """消息类型""" msg: str @@ -257,8 +257,11 @@ class Message(BaseModel): ava_url: str """用户头像""" + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) -class SendMessage(BaseModel): + +class SendMessageParam(BaseModel): """ 发送消息 """ diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py new file mode 100644 index 00000000..e011e67f --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -0,0 +1,154 @@ +from fastapi import APIRouter, Query +from fastapi.responses import JSONResponse + +from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo +from zhenxun.services.log import logger +from zhenxun.utils.enum import BlockType, PluginType + +from ....base_model import Result +from ....utils import authentication +from .data_source import ApiDataSource +from .model import ( + PluginCount, + PluginDetail, + PluginInfo, + PluginSwitch, + UpdatePlugin, +) + +router = APIRouter(prefix="/plugin") + + +@router.get( + "/get_plugin_list", + dependencies=[authentication()], + response_model=Result[list[PluginInfo]], + response_class=JSONResponse, + description="获取插件列表", # type: ignore +) +async def _( + plugin_type: list[PluginType] = Query(None), menu_type: str | None = None +) -> Result[list[PluginInfo]]: + try: + return Result.ok( + await ApiDataSource.get_plugin_list(plugin_type, menu_type), "拿到信息啦!" + ) + except Exception as e: + logger.error(f"{router.prefix}/get_plugin_list 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.get( + "/get_plugin_count", + dependencies=[authentication()], + response_model=Result[PluginCount], + response_class=JSONResponse, + description="获取插件数量", # type: ignore +) +async def _() -> Result[PluginCount]: + try: + plugin_count = PluginCount() + plugin_count.normal = await DbPluginInfo.filter( + plugin_type=PluginType.NORMAL, load_status=True + ).count() + plugin_count.admin = await DbPluginInfo.filter( + plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN], + load_status=True, + ).count() + plugin_count.superuser = await DbPluginInfo.filter( + plugin_type__in=[PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN], + load_status=True, + ).count() + plugin_count.other = await DbPluginInfo.filter( + plugin_type__in=[PluginType.HIDDEN, PluginType.DEPENDANT], load_status=True + ).count() + return Result.ok(plugin_count, "拿到信息啦!") + except Exception as e: + logger.error(f"{router.prefix}/get_plugin_count 调用错误", "WebUi", e=e) + return Result.fail(f"发生了一点错误捏 {type(e)}: {e}") + + +@router.post( + "/update_plugin", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="更新插件参数", +) +async def _(param: UpdatePlugin) -> Result: + try: + await ApiDataSource.update_plugin(param) + return Result.ok(info="已经帮你写好啦!") + except (ValueError, KeyError): + return Result.fail("插件数据不存在...") + except Exception as e: + logger.error(f"{router.prefix}/update_plugin 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.post( + "/change_switch", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="开关插件", +) +async def _(param: PluginSwitch) -> Result: + try: + db_plugin = await DbPluginInfo.get_plugin(module=param.module) + if not db_plugin: + return Result.fail("插件不存在...") + if not param.status: + db_plugin.block_type = BlockType.ALL + db_plugin.status = False + else: + db_plugin.block_type = None + db_plugin.status = True + await db_plugin.save() + return Result.ok(info="成功改变了开关状态!") + except Exception as e: + logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.get( + "/get_plugin_menu_type", + dependencies=[authentication()], + response_model=Result[list[str]], + response_class=JSONResponse, + description="获取插件类型", +) +async def _() -> Result[list[str]]: + try: + menu_type_list = [] + result = ( + await DbPluginInfo.filter(load_status=True) + .annotate() + .values_list("menu_type", flat=True) + ) + for r in result: + if r not in menu_type_list and r: + menu_type_list.append(r) + return Result.ok(menu_type_list) + except Exception as e: + logger.error(f"{router.prefix}/get_plugin_menu_type 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") + + +@router.get( + "/get_plugin", + dependencies=[authentication()], + response_model=Result[PluginDetail], + response_class=JSONResponse, + description="获取插件详情", +) +async def _(module: str) -> Result[PluginDetail]: + try: + return Result.ok( + await ApiDataSource.get_plugin_detail(module), "已经帮你写好啦!" + ) + except (ValueError, KeyError): + return Result.fail("插件数据不存在...") + except Exception as e: + logger.error(f"{router.prefix}/get_plugin 调用错误", "WebUi", e=e) + return Result.fail(f"{type(e)}: {e}") diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py new file mode 100644 index 00000000..ee0992d6 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py @@ -0,0 +1,152 @@ +import re + +import cattrs +from fastapi import Query + +from zhenxun.configs.config import Config +from zhenxun.configs.utils import ConfigGroup +from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo +from zhenxun.utils.enum import BlockType, PluginType + +from .model import PluginConfig, PluginDetail, PluginInfo, UpdatePlugin + + +class ApiDataSource: + @classmethod + async def get_plugin_list( + cls, plugin_type: list[PluginType] = Query(None), menu_type: str | None = None + ) -> list[PluginInfo]: + """获取插件列表 + + 参数: + plugin_type: 插件类型. + menu_type: 菜单类型. + + 返回: + list[PluginInfo]: 插件数据列表 + """ + plugin_list: list[PluginInfo] = [] + query = DbPluginInfo + if plugin_type: + query = query.filter(plugin_type__in=plugin_type, load_status=True) + if menu_type: + query = query.filter(menu_type=menu_type, load_status=True) + plugins = await query.all() + for plugin in plugins: + plugin_info = PluginInfo( + module=plugin.module, + plugin_name=plugin.name, + default_status=plugin.default_status, + limit_superuser=plugin.limit_superuser, + cost_gold=plugin.cost_gold, + menu_type=plugin.menu_type, + version=plugin.version or "0", + level=plugin.level, + status=plugin.status, + author=plugin.author, + ) + plugin_list.append(plugin_info) + return plugin_list + + @classmethod + async def update_plugin(cls, param: UpdatePlugin) -> DbPluginInfo: + """更新插件数据 + + 参数: + param: UpdatePlugin + + 返回: + DbPluginInfo | None: 插件数据 + """ + db_plugin = await DbPluginInfo.get_plugin(module=param.module) + if not db_plugin: + raise ValueError("插件不存在") + db_plugin.default_status = param.default_status + db_plugin.limit_superuser = param.limit_superuser + db_plugin.cost_gold = param.cost_gold + db_plugin.level = param.level + db_plugin.menu_type = param.menu_type + db_plugin.block_type = param.block_type + db_plugin.status = param.block_type != BlockType.ALL + await db_plugin.save() + # 配置项 + if param.configs and (configs := Config.get(param.module)): + for key in param.configs: + if c := configs.configs.get(key): + value = param.configs[key] + if c.type and value is not None: + value = cattrs.structure(value, c.type) + Config.set_config(param.module, key, value) + Config.save(save_simple_data=True) + return db_plugin + + @classmethod + def __build_plugin_config( + cls, module: str, cfg: str, config: ConfigGroup + ) -> PluginConfig: + """获取插件配置项 + + 参数: + module: 模块名 + cfg: cfg + config: ConfigGroup + + 返回: + lPluginConfig: 配置数据 + """ + type_str = "" + type_inner = None + if r := re.search(r"", str(config.configs[cfg].type)): + type_str = r[1] + elif r := re.search(r"typing\.(.*)\[(.*)\]", str(config.configs[cfg].type)): + type_str = r[1] + if type_str: + type_str = type_str.lower() + type_inner = r[2] + if type_inner: + type_inner = [x.strip() for x in type_inner.split(",")] + return PluginConfig( + module=module, + key=cfg, + value=config.configs[cfg].value, + help=config.configs[cfg].help, + default_value=config.configs[cfg].default_value, + type=type_str, + type_inner=type_inner, # type: ignore + ) + + @classmethod + async def get_plugin_detail(cls, module: str) -> PluginDetail: + """获取插件详情 + + 参数: + module: 模块名 + + 异常: + ValueError: 插件不存在 + + 返回: + PluginDetail: 插件详情数据 + """ + db_plugin = await DbPluginInfo.get_plugin(module=module) + if not db_plugin: + raise ValueError("插件不存在") + config_list = [] + if config := Config.get(module): + config_list.extend( + cls.__build_plugin_config(module, cfg, config) for cfg in config.configs + ) + return PluginDetail( + module=module, + plugin_name=db_plugin.name, + default_status=db_plugin.default_status, + limit_superuser=db_plugin.limit_superuser, + cost_gold=db_plugin.cost_gold, + menu_type=db_plugin.menu_type, + version=db_plugin.version or "0", + level=db_plugin.level, + status=db_plugin.status, + author=db_plugin.author, + config_list=config_list, + block_type=db_plugin.block_type, + ) diff --git a/zhenxun/plugins/web_ui/api/tabs/plugin_manage/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py similarity index 97% rename from zhenxun/plugins/web_ui/api/tabs/plugin_manage/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py index e2952038..662814c9 100644 --- a/zhenxun/plugins/web_ui/api/tabs/plugin_manage/model.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py @@ -123,3 +123,8 @@ class PluginDetail(PluginInfo): """ config_list: list[PluginConfig] + + +class PluginIr(BaseModel): + id: int + """插件id""" diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py new file mode 100644 index 00000000..acff6356 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/store.py @@ -0,0 +1,92 @@ +from fastapi import APIRouter +from fastapi.responses import JSONResponse +from nonebot import require + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.services.log import logger + +from ....base_model import Result +from ....utils import authentication +from .model import PluginIr + +router = APIRouter(prefix="/store") + + +@router.get( + "/get_plugin_store", + dependencies=[authentication()], + response_model=Result[dict], + response_class=JSONResponse, + description="获取插件商店插件信息", # type: ignore +) +async def _() -> Result[dict]: + try: + require("plugin_store") + from zhenxun.builtin_plugins.plugin_store import ShopManage + + data = await ShopManage.get_data() + plugin_list = [ + {**data[name].to_dict(), "name": name, "id": idx} + for idx, name in enumerate(data) + ] + modules = await PluginInfo.filter(load_status=True).values_list( + "module", flat=True + ) + return Result.ok({"install_module": modules, "plugin_list": plugin_list}) + except Exception as e: + logger.error("获取插件商店插件信息失败", "WebUi", e=e) + return Result.fail(f"获取插件商店插件信息失败: {type(e)}: {e}") + + +@router.post( + "/install_plugin", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="安装插件", # type: ignore +) +async def _(param: PluginIr) -> Result: + try: + require("plugin_store") + from zhenxun.builtin_plugins.plugin_store import ShopManage + + result = await ShopManage.add_plugin(param.id) # type: ignore + return Result.ok(info=result) + except Exception as e: + return Result.fail(f"安装插件失败: {type(e)}: {e}") + + +@router.post( + "/update_plugin", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="更新插件", # type: ignore +) +async def _(param: PluginIr) -> Result: + try: + require("plugin_store") + from zhenxun.builtin_plugins.plugin_store import ShopManage + + result = await ShopManage.update_plugin(param.id) # type: ignore + return Result.ok(info=result) + except Exception as e: + return Result.fail(f"更新插件失败: {type(e)}: {e}") + + +@router.post( + "/remove_plugin", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="移除插件", # type: ignore +) +async def _(param: PluginIr) -> Result: + try: + require("plugin_store") + from zhenxun.builtin_plugins.plugin_store import ShopManage + + result = await ShopManage.remove_plugin(param.id) # type: ignore + return Result.ok(info=result) + except Exception as e: + return Result.fail(f"移除插件失败: {type(e)}: {e}") diff --git a/zhenxun/plugins/web_ui/api/tabs/system/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py similarity index 54% rename from zhenxun/plugins/web_ui/api/tabs/system/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py index 55c56764..aa92306a 100644 --- a/zhenxun/plugins/web_ui/api/tabs/system/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py @@ -1,13 +1,14 @@ import os -import shutil from pathlib import Path -from typing import List, Optional +import shutil +import aiofiles from fastapi import APIRouter +from fastapi.responses import JSONResponse from zhenxun.utils._build_image import BuildImage -from ....base_model import Result +from ....base_model import Result, SystemFolderSize from ....utils import authentication, get_system_disk from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile @@ -17,18 +18,18 @@ IMAGE_TYPE = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"] @router.get( - "/get_dir_list", dependencies=[authentication()], description="获取文件列表" + "/get_dir_list", + dependencies=[authentication()], + response_model=Result[list[DirFile]], + response_class=JSONResponse, + description="获取文件列表", ) -async def _(path: Optional[str] = None) -> Result: +async def _(path: str | None = None) -> Result[list[DirFile]]: base_path = Path(path) if path else Path() data_list = [] for file in os.listdir(base_path): file_path = base_path / file - is_image = False - for t in IMAGE_TYPE: - if file.endswith(f".{t}"): - is_image = True - break + is_image = any(file.endswith(f".{t}") for t in IMAGE_TYPE) data_list.append( DirFile( is_file=not file_path.is_dir(), @@ -41,13 +42,23 @@ async def _(path: Optional[str] = None) -> Result: @router.get( - "/get_resources_size", dependencies=[authentication()], description="获取文件列表" + "/get_resources_size", + dependencies=[authentication()], + response_model=Result[list[SystemFolderSize]], + response_class=JSONResponse, + description="获取文件列表", ) -async def _(full_path: Optional[str] = None) -> Result: +async def _(full_path: str | None = None) -> Result[list[SystemFolderSize]]: return Result.ok(await get_system_disk(full_path)) -@router.post("/delete_file", dependencies=[authentication()], description="删除文件") +@router.post( + "/delete_file", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="删除文件", +) async def _(param: DeleteFile) -> Result: path = Path(param.full_path) if not path or not path.exists(): @@ -56,11 +67,15 @@ async def _(param: DeleteFile) -> Result: path.unlink() return Result.ok("删除成功!") except Exception as e: - return Result.warning_("删除失败: " + str(e)) + return Result.warning_(f"删除失败: {e!s}") @router.post( - "/delete_folder", dependencies=[authentication()], description="删除文件夹" + "/delete_folder", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="删除文件夹", ) async def _(param: DeleteFile) -> Result: path = Path(param.full_path) @@ -70,10 +85,16 @@ async def _(param: DeleteFile) -> Result: shutil.rmtree(path.absolute()) return Result.ok("删除成功!") except Exception as e: - return Result.warning_("删除失败: " + str(e)) + return Result.warning_(f"删除失败: {e!s}") -@router.post("/rename_file", dependencies=[authentication()], description="重命名文件") +@router.post( + "/rename_file", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="重命名文件", +) async def _(param: RenameFile) -> Result: path = ( (Path(param.parent) / param.old_name) if param.parent else Path(param.old_name) @@ -84,11 +105,15 @@ async def _(param: RenameFile) -> Result: path.rename(path.parent / param.name) return Result.ok("重命名成功!") except Exception as e: - return Result.warning_("重命名失败: " + str(e)) + return Result.warning_(f"重命名失败: {e!s}") @router.post( - "/rename_folder", dependencies=[authentication()], description="重命名文件夹" + "/rename_folder", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="重命名文件夹", ) async def _(param: RenameFile) -> Result: path = ( @@ -101,10 +126,16 @@ async def _(param: RenameFile) -> Result: shutil.move(path.absolute(), new_path.absolute()) return Result.ok("重命名成功!") except Exception as e: - return Result.warning_("重命名失败: " + str(e)) + return Result.warning_(f"重命名失败: {e!s}") -@router.post("/add_file", dependencies=[authentication()], description="新建文件") +@router.post( + "/add_file", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="新建文件", +) async def _(param: AddFile) -> Result: path = (Path(param.parent) / param.name) if param.parent else Path(param.name) if path.exists(): @@ -113,10 +144,16 @@ async def _(param: AddFile) -> Result: path.open("w") return Result.ok("新建文件成功!") except Exception as e: - return Result.warning_("新建文件失败: " + str(e)) + return Result.warning_(f"新建文件失败: {e!s}") -@router.post("/add_folder", dependencies=[authentication()], description="新建文件夹") +@router.post( + "/add_folder", + dependencies=[authentication()], + response_model=Result, + response_class=JSONResponse, + description="新建文件夹", +) async def _(param: AddFile) -> Result: path = (Path(param.parent) / param.name) if param.parent else Path(param.name) if path.exists(): @@ -125,10 +162,16 @@ async def _(param: AddFile) -> Result: path.mkdir() return Result.ok("新建文件夹成功!") except Exception as e: - return Result.warning_("新建文件夹失败: " + str(e)) + return Result.warning_(f"新建文件夹失败: {e!s}") -@router.get("/read_file", dependencies=[authentication()], description="读取文件") +@router.get( + "/read_file", + dependencies=[authentication()], + response_model=Result[str], + response_class=JSONResponse, + description="读取文件", +) async def _(full_path: str) -> Result: path = Path(full_path) if not path.exists(): @@ -137,26 +180,38 @@ async def _(full_path: str) -> Result: text = path.read_text(encoding="utf-8") return Result.ok(text) except Exception as e: - return Result.warning_("读取文件失败: " + str(e)) + return Result.warning_(f"读取文件失败: {e!s}") -@router.post("/save_file", dependencies=[authentication()], description="读取文件") -async def _(param: SaveFile) -> Result: +@router.post( + "/save_file", + dependencies=[authentication()], + response_model=Result[str], + response_class=JSONResponse, + description="读取文件", +) +async def _(param: SaveFile) -> Result[str]: path = Path(param.full_path) try: - with path.open("w") as f: - f.write(param.content) + async with aiofiles.open(path, "w", encoding="utf-8") as f: + await f.write(param.content) return Result.ok("更新成功!") except Exception as e: - return Result.warning_("保存文件失败: " + str(e)) + return Result.warning_(f"保存文件失败: {e!s}") -@router.get("/get_image", dependencies=[authentication()], description="读取图片base64") -async def _(full_path: str) -> Result: +@router.get( + "/get_image", + dependencies=[authentication()], + response_model=Result[str], + response_class=JSONResponse, + description="读取图片base64", +) +async def _(full_path: str) -> Result[str]: path = Path(full_path) if not path.exists(): return Result.warning_("文件不存在...") try: return Result.ok(BuildImage.open(path).pic2bs4()) except Exception as e: - return Result.warning_("获取图片失败: " + str(e)) + return Result.warning_(f"获取图片失败: {e!s}") diff --git a/zhenxun/plugins/web_ui/api/tabs/system/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/system/model.py similarity index 83% rename from zhenxun/plugins/web_ui/api/tabs/system/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/system/model.py index 7cabc86a..3c2357f2 100644 --- a/zhenxun/plugins/web_ui/api/tabs/system/model.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/system/model.py @@ -1,6 +1,3 @@ -from datetime import datetime -from typing import Literal, Optional - from pydantic import BaseModel @@ -15,7 +12,7 @@ class DirFile(BaseModel): """是否为图片""" name: str """文件夹或文件名称""" - parent: Optional[str] = None + parent: str | None = None """父级""" @@ -33,7 +30,7 @@ class RenameFile(BaseModel): 删除文件 """ - parent: Optional[str] + parent: str | None """父路径""" old_name: str """旧名称""" @@ -46,7 +43,7 @@ class AddFile(BaseModel): 新建文件 """ - parent: Optional[str] + parent: str | None = None """父路径""" name: str """新名称""" diff --git a/zhenxun/plugins/web_ui/auth/__init__.py b/zhenxun/builtin_plugins/web_ui/auth/__init__.py similarity index 89% rename from zhenxun/plugins/web_ui/auth/__init__.py rename to zhenxun/builtin_plugins/web_ui/auth/__init__.py index d5a4ead7..974d6de2 100644 --- a/zhenxun/plugins/web_ui/auth/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/auth/__init__.py @@ -1,9 +1,10 @@ -import json from datetime import timedelta +import json -import nonebot +import aiofiles from fastapi import APIRouter, Depends from fastapi.security import OAuth2PasswordRequestForm +import nonebot from zhenxun.configs.config import Config @@ -40,8 +41,8 @@ async def login_get_token(form_data: OAuth2PasswordRequestForm = Depends()): token_data["token"].append(access_token) if len(token_data["token"]) > 3: token_data["token"] = token_data["token"][1:] - with open(token_file, "w", encoding="utf8") as f: - json.dump(token_data, f, ensure_ascii=False, indent=4) + async with aiofiles.open(token_file, "w", encoding="utf8") as f: + await f.write(json.dumps(token_data, ensure_ascii=False, indent=4)) return Result.ok( {"access_token": access_token, "token_type": "bearer"}, "欢迎回家, 欧尼酱!" ) diff --git a/zhenxun/plugins/web_ui/base_model.py b/zhenxun/builtin_plugins/web_ui/base_model.py similarity index 69% rename from zhenxun/plugins/web_ui/base_model.py rename to zhenxun/builtin_plugins/web_ui/base_model.py index 67bb280f..77b3e9e3 100644 --- a/zhenxun/plugins/web_ui/base_model.py +++ b/zhenxun/builtin_plugins/web_ui/base_model.py @@ -1,11 +1,18 @@ from datetime import datetime -from typing import Any, Generic, Optional, TypeVar +from typing import Any, Generic, TypeVar -from pydantic import BaseModel, validator -from typing_extensions import Self +from nonebot.compat import PYDANTIC_V2 +from pydantic import BaseModel T = TypeVar("T") +RT = TypeVar("RT") + +if PYDANTIC_V2: + from pydantic import field_validator as validator_decorator +else: + from pydantic import validator as validator_decorator + class User(BaseModel): username: str @@ -17,7 +24,7 @@ class Token(BaseModel): token_type: str -class Result(BaseModel): +class Result(BaseModel, Generic[RT]): """ 总体返回 """ @@ -28,21 +35,23 @@ class Result(BaseModel): """code""" info: str = "操作成功" """info""" - warning: Optional[str] = None + warning: str | None = None """警告信息""" - data: Any = None + data: RT | None = None """返回数据""" @classmethod - def warning_(cls, info: str, code: int = 200) -> Self: + def warning_(cls, info: str, code: int = 200) -> "Result[RT]": return cls(suc=True, warning=info, code=code) @classmethod - def fail(cls, info: str = "异常错误", code: int = 500) -> Self: + def fail(cls, info: str = "异常错误", code: int = 500) -> "Result[RT]": return cls(suc=False, info=info, code=code) @classmethod - def ok(cls, data: Any = None, info: str = "操作成功", code: int = 200) -> Self: + def ok( + cls, data: Any = None, info: str = "操作成功", code: int = 200 + ) -> "Result[RT]": return cls(suc=True, info=info, code=code, data=data) @@ -55,16 +64,16 @@ class QueryModel(BaseModel, Generic[T]): """页数""" size: int """每页数量""" - data: T + data: T | None = None """携带数据""" - @validator("index") + @validator_decorator("index") def index_validator(cls, index): if index < 1: raise ValueError("查询下标小于1...") return index - @validator("size") + @validator_decorator("size") def size_validator(cls, size): if size < 1: raise ValueError("每页数量小于1...") @@ -102,7 +111,7 @@ class SystemFolderSize(BaseModel): """名称""" size: float """大小""" - full_path: Optional[str] + full_path: str | None """完整路径""" is_dir: bool """是否为文件夹""" diff --git a/zhenxun/plugins/web_ui/config.py b/zhenxun/builtin_plugins/web_ui/config.py similarity index 64% rename from zhenxun/plugins/web_ui/config.py rename to zhenxun/builtin_plugins/web_ui/config.py index 0f16949a..bddcb062 100644 --- a/zhenxun/plugins/web_ui/config.py +++ b/zhenxun/builtin_plugins/web_ui/config.py @@ -1,8 +1,18 @@ -import nonebot from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel +import nonebot from strenum import StrEnum +from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH + +WEBUI_STRING = "web_ui" +PUBLIC_STRING = "public" + +WEBUI_DATA_PATH = DATA_PATH / WEBUI_STRING +PUBLIC_PATH = WEBUI_DATA_PATH / PUBLIC_STRING +TMP_PATH = TEMP_PATH / WEBUI_STRING + +WEBUI_DIST_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot_webui/tree/dist" + app = nonebot.get_app() origins = ["*"] diff --git a/zhenxun/builtin_plugins/web_ui/public/__init__.py b/zhenxun/builtin_plugins/web_ui/public/__init__.py new file mode 100644 index 00000000..53d4914e --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/public/__init__.py @@ -0,0 +1,43 @@ +from fastapi import APIRouter, FastAPI +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles + +from zhenxun.services.log import logger + +from ..config import PUBLIC_PATH +from .data_source import COMMAND_NAME, update_webui_assets + +router = APIRouter() + + +@router.get("/") +async def index(): + return FileResponse(PUBLIC_PATH / "index.html") + + +@router.get("/favicon.ico") +async def favicon(): + return FileResponse(PUBLIC_PATH / "favicon.ico") + + +@router.get("/79edfa81f3308a9f.jfif") +async def _(): + return FileResponse(PUBLIC_PATH / "79edfa81f3308a9f.jfif") + + +async def init_public(app: FastAPI): + try: + if not PUBLIC_PATH.exists(): + folders = await update_webui_assets() + else: + folders = [x.name for x in PUBLIC_PATH.iterdir() if x.is_dir()] + app.include_router(router) + for pathname in folders: + logger.debug(f"挂载文件夹: {pathname}") + app.mount( + f"/{pathname}", + StaticFiles(directory=PUBLIC_PATH / pathname, check_dir=True), + name=f"public_{pathname}", + ) + except Exception as e: + logger.error("初始化 WebUI资源 失败", COMMAND_NAME, e=e) diff --git a/zhenxun/builtin_plugins/web_ui/public/data_source.py b/zhenxun/builtin_plugins/web_ui/public/data_source.py new file mode 100644 index 00000000..9f5a657e --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/public/data_source.py @@ -0,0 +1,43 @@ +from pathlib import Path +import shutil +import zipfile + +from nonebot.utils import run_sync + +from zhenxun.services.log import logger +from zhenxun.utils.github_utils import GithubUtils +from zhenxun.utils.http_utils import AsyncHttpx + +from ..config import PUBLIC_PATH, TMP_PATH, WEBUI_DIST_GITHUB_URL + +COMMAND_NAME = "WebUI资源管理" + + +async def update_webui_assets(): + webui_assets_path = TMP_PATH / "webui_assets.zip" + download_url = await GithubUtils.parse_github_url( + WEBUI_DIST_GITHUB_URL + ).get_archive_download_urls() + if await AsyncHttpx.download_file( + download_url, webui_assets_path, follow_redirects=True + ): + logger.info("下载 webui_assets 成功...", COMMAND_NAME) + return await _file_handle(webui_assets_path) + raise Exception("下载 webui_assets 失败", COMMAND_NAME) + + +@run_sync +def _file_handle(webui_assets_path: Path): + logger.debug("开始解压 webui_assets...", COMMAND_NAME) + if webui_assets_path.exists(): + tf = zipfile.ZipFile(webui_assets_path) + tf.extractall(TMP_PATH) + logger.debug("解压 webui_assets 成功...", COMMAND_NAME) + else: + raise Exception("解压 webui_assets 失败,文件不存在...", COMMAND_NAME) + download_file_path = next(f for f in TMP_PATH.iterdir() if f.is_dir()) + shutil.rmtree(PUBLIC_PATH, ignore_errors=True) + shutil.copytree(download_file_path / "dist", PUBLIC_PATH, dirs_exist_ok=True) + logger.debug("复制 webui_assets 成功...", COMMAND_NAME) + shutil.rmtree(TMP_PATH, ignore_errors=True) + return [x.name for x in PUBLIC_PATH.iterdir() if x.is_dir()] diff --git a/zhenxun/plugins/web_ui/utils.py b/zhenxun/builtin_plugins/web_ui/utils.py similarity index 82% rename from zhenxun/plugins/web_ui/utils.py rename to zhenxun/builtin_plugins/web_ui/utils.py index f39f36ac..df2fdd35 100644 --- a/zhenxun/plugins/web_ui/utils.py +++ b/zhenxun/builtin_plugins/web_ui/utils.py @@ -1,20 +1,20 @@ +import contextlib +from datetime import datetime, timedelta, timezone import os -from datetime import datetime, timedelta from pathlib import Path -import psutil -import ujson as json from fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from nonebot.utils import run_sync +import psutil +import ujson as json from zhenxun.configs.config import Config from zhenxun.configs.path_config import DATA_PATH from .base_model import SystemFolderSize, SystemStatus, User -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 @@ -24,10 +24,8 @@ token_file = DATA_PATH / "web_ui" / "token.json" token_file.parent.mkdir(parents=True, exist_ok=True) token_data = {"token": []} if token_file.exists(): - try: - token_data = json.load(open(token_file, "r", encoding="utf8")) - except json.JSONDecodeError: - pass + with contextlib.suppress(json.JSONDecodeError): + token_data = json.load(open(token_file, encoding="utf8")) def get_user(uname: str) -> User | None: @@ -52,10 +50,10 @@ def create_token(user: User, expires_delta: timedelta | None = None): user: 用户信息 expires_delta: 过期时间. """ - expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) + expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=15)) return jwt.encode( claims={"sub": user.username, "exp": expire}, - key=SECRET_KEY, + key=Config.get_config("web-ui", "secret"), algorithm=ALGORITHM, ) @@ -71,8 +69,10 @@ def authentication(): # if token not in token_data["token"]: def inner(token: str = Depends(oauth2_scheme)): try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username, expire = payload.get("sub"), payload.get("exp") + payload = jwt.decode( + token, Config.get_config("web-ui", "secret"), algorithms=[ALGORITHM] + ) + username, _ = payload.get("sub"), payload.get("exp") user = get_user(username) # type: ignore if user is None: raise JWTError @@ -90,10 +90,10 @@ def _get_dir_size(dir_path: Path) -> float: 参数: dir_path: 文件夹路径 """ - size = 0 - for root, dirs, files in os.walk(dir_path): - size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) - return size + return sum( + sum(os.path.getsize(os.path.join(root, name)) for name in files) + for root, dirs, files in os.walk(dir_path) + ) @run_sync diff --git a/zhenxun/builtin_plugins/withdraw.py b/zhenxun/builtin_plugins/withdraw.py new file mode 100644 index 00000000..ef10d592 --- /dev/null +++ b/zhenxun/builtin_plugins/withdraw.py @@ -0,0 +1,69 @@ +from nonebot.adapters import Bot, Event +from nonebot.plugin import PluginMetadata +from nonebot.rule import Rule +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna +from nonebot_plugin_alconna.uniseg.tools import reply_fetch +from nonebot_plugin_uninfo import Uninfo + +from zhenxun.configs.utils import Command, PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.manager.message_manager import MessageManager +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils + +__plugin_meta__ = PluginMetadata( + name="消息撤回", + description="撤回自己触发的消息撤回,不允许撤回其他人触发消息的撤回哦", + usage=""" + 引用消息 撤回 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + menu_type="其他", + commands=[Command(command="[引用消息] 撤回")], + ).to_dict(), +) + + +def reply_check() -> Rule: + """ + 检查是否存在回复消息 + + 返回: + Rule: Rule + """ + + async def _rule(bot: Bot, event: Event, session: Uninfo): + if event.get_type() == "message": + return ( + bool(await reply_fetch(event, bot)) + and PlatformUtils.get_platform(session) == "qq" + ) + return False + + return Rule(_rule) + + +_matcher = on_alconna(Alconna("撤回"), priority=5, block=True, rule=reply_check()) + + +@_matcher.handle() +async def _(bot: Bot, event: Event, session: Uninfo, arparma: Arparma): + if reply := await reply_fetch(event, bot): + if session.user.id in bot.config.superusers: + try: + await bot.delete_msg(message_id=reply.id) + logger.info("撤回消息", arparma.header_result, session=session) + except Exception: + await MessageUtils.build_message("撤回失败,可能消息已过期...").send() + elif MessageManager.check(session.user.id, reply.id): + try: + await bot.delete_msg(message_id=reply.id) + logger.info("撤回消息", arparma.header_result, session=session) + except Exception: + await MessageUtils.build_message("撤回失败,可能消息已过期...").send() + else: + await MessageUtils.build_message( + "权限不足,不是你触发的消息不要胡乱撤回哦..." + ).send() diff --git a/zhenxun/configs/config.py b/zhenxun/configs/config.py index e1140a88..83937201 100644 --- a/zhenxun/configs/config.py +++ b/zhenxun/configs/config.py @@ -1,35 +1,58 @@ -import platform from pathlib import Path +import nonebot +from pydantic import BaseModel, Field + from .utils import ConfigsManager -if platform.system() == "Linux": - import os - - hostip = ( - os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'") - .read() - .replace("\n", "") - ) +__all__ = ["BotConfig", "Config"] -# 回复消息名称 -NICKNAME: str = "小真寻" +class BotSetting(BaseModel): + self_nickname: str = "" + """回复时NICKNAME""" + system_proxy: str | None = None + """系统代理""" + db_url: str = "" + """数据库链接""" + platform_superusers: dict[str, list[str]] = Field(default_factory=dict) + """平台超级用户""" + qbot_id_data: dict[str, str] = Field(default_factory=dict) + """官bot id:账号id""" -# 数据库(必要) -# 如果填写了bind就不需要再填写后面的字段了#) -# 示例:"bind": "postgres://user:password@127.0.0.1:5432/database" -bind: str = "" # 数据库连接链接 -sql_name: str = "postgres" -user: str = "" # 数据用户名 -password: str = "" # 数据库密码 -address: str = "" # 数据库地址 -port: str = "" # 数据库端口 -database: str = "" # 数据库名称 + def get_qbot_uid(self, qbot_id: str) -> str | None: + """获取官bot账号id -# 代理,例如 "http://127.0.0.1:7890" -# 如果是WLS 可以 f"http://{hostip}:7890" 使用寄主机的代理 -SYSTEM_PROXY: str | None = None # 全局代理 + 参数: + qbot_id: 官bot id + + 返回: + str: 账号id + """ + return self.qbot_id_data.get(qbot_id) + + def get_superuser(self, platform: str) -> list[str]: + """获取超级用户 + + 参数: + platform: 对应平台 + + 返回: + list[str]: 超级用户id + """ + if self.platform_superusers: + return self.platform_superusers.get(platform, []) + return [] + + def get_sql_type(self) -> str: + """获取数据库类型 + + 返回: + str: 数据库类型, postgres, mysql, sqlite + """ + return self.db_url.split(":", 1)[0] if self.db_url else "" Config = ConfigsManager(Path() / "data" / "configs" / "plugins2config.yaml") + +BotConfig = nonebot.get_plugin_config(BotSetting) diff --git a/zhenxun/configs/path_config.py b/zhenxun/configs/path_config.py index 1a22cb02..e100ca2d 100644 --- a/zhenxun/configs/path_config.py +++ b/zhenxun/configs/path_config.py @@ -18,7 +18,6 @@ TEMP_PATH = Path() / "resources" / "temp" TEMPLATE_PATH = Path() / "resources" / "template" - IMAGE_PATH.mkdir(parents=True, exist_ok=True) RECORD_PATH.mkdir(parents=True, exist_ok=True) TEXT_PATH.mkdir(parents=True, exist_ok=True) @@ -26,8 +25,3 @@ LOG_PATH.mkdir(parents=True, exist_ok=True) FONT_PATH.mkdir(parents=True, exist_ok=True) DATA_PATH.mkdir(parents=True, exist_ok=True) TEMP_PATH.mkdir(parents=True, exist_ok=True) - - - - - diff --git a/zhenxun/configs/utils/__init__.py b/zhenxun/configs/utils/__init__.py index 4ba9c47c..72b4af6c 100644 --- a/zhenxun/configs/utils/__init__.py +++ b/zhenxun/configs/utils/__init__.py @@ -1,9 +1,12 @@ +from collections.abc import Callable import copy +from datetime import datetime from pathlib import Path -from typing import Any, Callable, Dict, Set, Type +from typing import Any, Literal import cattrs -from pydantic import BaseModel +from nonebot.compat import model_dump +from pydantic import BaseModel, Field from ruamel.yaml import YAML from ruamel.yaml.scanner import ScannerError @@ -16,6 +19,32 @@ _yaml.indent = 2 _yaml.allow_unicode = True +class Example(BaseModel): + """ + 示例 + """ + + exec: str + """执行命令""" + description: str = "" + """命令描述""" + + +class Command(BaseModel): + """ + 具体参数说明 + """ + + command: str + """命令名称""" + params: list[str] = Field(default_factory=list) + """参数""" + description: str = "" + """描述""" + examples: list[Example] = Field(default_factory=list) + """示例列表""" + + class RegisterConfig(BaseModel): """ 注册配置项 @@ -53,6 +82,9 @@ class ConfigModel(BaseModel): arg_parser: Callable | None = None """参数解析""" + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) + class ConfigGroup(BaseModel): """ @@ -63,7 +95,7 @@ class ConfigGroup(BaseModel): """模块名""" name: str | None = None """插件名""" - configs: Dict[str, ConfigModel] = {} + configs: dict[str, ConfigModel] = Field(default_factory=dict) """配置项列表""" def get(self, c: str, default: Any = None) -> Any: @@ -75,6 +107,9 @@ class ConfigGroup(BaseModel): return cfg.default_value return default + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) + class BaseBlock(BaseModel): """ @@ -92,6 +127,9 @@ class BaseBlock(BaseModel): _type: PluginLimitType = PluginLimitType.BLOCK """类型""" + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) + class PluginCdBlock(BaseBlock): """ @@ -128,6 +166,31 @@ class PluginSetting(BaseModel): """是否限制超级用户""" cost_gold: int = 0 """调用插件花费金币""" + impression: float = 0.0 + """调用插件好感度限制""" + + +class SchedulerModel(BaseModel): + trigger: Literal["date", "interval", "cron"] + """trigger""" + day: int | None = None + """天数""" + hour: int | None = None + """小时""" + minute: int | None = None + """分钟""" + second: int | None = None + """秒""" + run_date: datetime | None = None + """运行日期""" + id: str | None = None + """id""" + max_instances: int | None = None + """最大运行实例""" + args: list | None = None + """参数""" + kwargs: dict | None = None + """参数""" class Task(BaseBlock): @@ -137,8 +200,18 @@ class Task(BaseBlock): """被动技能名称""" status: bool = True """全局开关状态""" - run_time: str | None = None - """运行时间""" + create_status: bool = False + """初次加载默认开关状态""" + default_status: bool = True + """进群时默认状态""" + scheduler: SchedulerModel | None = None + """定时任务配置""" + run_func: Callable | None = None + """运行函数""" + check: Callable | None = None + """检查函数""" + check_args: list = Field(default_factory=list) + """检查函数参数""" class PluginExtraData(BaseModel): @@ -162,14 +235,23 @@ class PluginExtraData(BaseModel): """插件基本配置""" limits: list[BaseBlock | PluginCdBlock | PluginCountBlock] | None = None """插件限制""" + commands: list[Command] = Field(default_factory=list) + """命令列表,用于说明帮助""" + ignore_prompt: bool = False + """是否忽略阻断提示""" tasks: list[Task] | None = None """技能被动""" superuser_help: str | None = None """超级用户帮助""" - aliases: Set[str] = set() + aliases: set[str] = Field(default_factory=set) """额外名称""" sql_list: list[str] | None = None """常用sql""" + is_show: bool = True + """是否显示在菜单中""" + + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) class NoSuchConfig(Exception): @@ -182,9 +264,10 @@ class ConfigsManager: """ def __init__(self, file: Path): - self._data: Dict[str, ConfigGroup] = {} + self._data: dict[str, ConfigGroup] = {} self._simple_data: dict = {} self._simple_file = DATA_PATH / "config.yaml" + self.add_module = [] _yaml = YAML() if file: file.parent.mkdir(exist_ok=True, parents=True) @@ -192,14 +275,14 @@ class ConfigsManager: self.load_data() if self._simple_file.exists(): try: - with open(self._simple_file, "r", encoding="utf8") as f: + with self._simple_file.open(encoding="utf8") as f: self._simple_data = _yaml.load(f) except ScannerError as e: raise ScannerError( f"{e}\n**********************************************\n" f"****** 可能为config.yaml配置文件填写不规范 ******\n" f"**********************************************" - ) + ) from e def set_name(self, module: str, name: str): """设置插件配置中文名出 @@ -224,7 +307,7 @@ class ConfigsManager: *, help: str | None = None, default_value: Any = None, - type: Type | None = None, + type: type | None = None, arg_parser: Callable | None = None, _override: bool = False, ): @@ -247,6 +330,7 @@ class ConfigsManager: if not module or not key: raise ValueError("add_plugin_config: module和key不能为为空") + self.add_module.append(f"{module}:{key}".lower()) if module in self._data and (config := self._data[module].configs.get(key)): config.help = help config.arg_parser = arg_parser @@ -255,13 +339,6 @@ class ConfigsManager: config.value = value config.default_value = default_value else: - _module = None - if ":" in module: - module_split = module.split(":") - if len(module_split) < 2: - raise ValueError(f"module: {module} 填写错误") - _module = module_split[-1] - module = module_split[0] key = key.upper() if not self._data.get(module): self._data[module] = ConfigGroup(module=module) @@ -278,7 +355,6 @@ class ConfigsManager: key: str, value: Any, auto_save: bool = False, - save_simple_data: bool = True, ): """设置配置值 @@ -287,15 +363,16 @@ class ConfigsManager: key: 配置名称 value: 值 auto_save: 自动保存. - save_simple_data: 保存至config.yaml. """ + key = key.upper() if module in self._data: - data = self._data[module].configs.get(key) - if data and data != value: + if self._data[module].configs.get(key): self._data[module].configs[key].value = value - self._simple_data[module][key] = value + else: + self.add_plugin_config(module, key, value) + self._simple_data[module][key] = value if auto_save: - self.save(save_simple_data=save_simple_data) + self.save(save_simple_data=True) def get_config(self, module: str, key: str, default: Any = None) -> Any: """获取指定配置值 @@ -312,45 +389,46 @@ class ConfigsManager: Any: 配置值 """ logger.debug( - f"尝试获取配置 MODULE: [{module}] | KEY: [{key}]" + f"尝试获取配置MODULE: [{module}] | KEY: [{key}]" ) key = key.upper() value = None if module in self._data.keys(): - config = self._data[module].configs.get(key) - if not config: - config = self._data[module].configs.get(key) + config = self._data[module].configs.get(key) or self._data[ + module + ].configs.get(key) if not config: raise NoSuchConfig( f"未查询到配置项 MODULE: [ {module} ] | KEY: [ {key} ]" ) - if config.arg_parser: - value = config.arg_parser(value or config.default_value) - else: - try: - if config.value is not None: - value = ( - cattrs.structure(config.value, config.type) - if config.type - else config.value - ) - else: - if config.default_value is not None: - value = ( - cattrs.structure(config.default_value, config.type) - if config.type - else config.default_value - ) - except Exception as e: - logger.warning( - f"配置项类型转换 MODULE: [{module}] | KEY: [{key}]", - e=e, + try: + if config.arg_parser: + value = config.arg_parser(value or config.default_value) + elif config.value is not None: + # try: + value = ( + cattrs.structure(config.value, config.type) + if config.type + else config.value ) - value = config.value or config.default_value + elif config.default_value is not None: + value = ( + cattrs.structure(config.default_value, config.type) + if config.type + else config.default_value + ) + except Exception as e: + logger.warning( + f"配置项类型转换 MODULE: [{module}]" + " | KEY: [{key}]", + e=e, + ) + value = config.value or config.default_value if value is None: value = default logger.debug( - f"获取配置 MODULE: [{module}] | KEY: [{key}] -> [{value}]" + f"获取配置 MODULE: [{module}] | " + f" KEY: [{key}] -> [{value}]" ) return value @@ -374,13 +452,6 @@ class ConfigsManager: """ if save_simple_data: with open(self._simple_file, "w", encoding="utf8") as f: - # yaml.dump( - # self._simple_data, - # f, - # indent=2, - # Dumper=yaml.RoundTripDumper, - # allow_unicode=True, - # ) _yaml.dump(self._simple_data, f) path = path or self.file data = {} @@ -392,16 +463,12 @@ class ConfigsManager: del value["arg_parser"] data[module][config] = value with open(path, "w", encoding="utf8") as f: - # yaml.dump( - # data, f, indent=2, Dumper=yaml.RoundTripDumper, allow_unicode=True - # ) _yaml.dump(data, f) def reload(self): """重新加载配置文件""" - _yaml = YAML() if self._simple_file.exists(): - with open(self._simple_file, "r", encoding="utf8") as f: + with open(self._simple_file, encoding="utf8") as f: self._simple_data = _yaml.load(f) for key in self._simple_data.keys(): for k in self._simple_data[key].keys(): @@ -414,31 +481,31 @@ class ConfigsManager: 异常: ValueError: 配置文件为空! """ - if self.file.exists(): - with open(self.file, "r", encoding="utf8") as f: - temp_data = _yaml.load(f) - if not temp_data: - self.file.unlink() - raise ValueError( - "配置文件为空!\n" - "***********************************************************\n" - "****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n" - "***********************************************************" - ) - count = 0 - for module in temp_data: - config_group = ConfigGroup(module=module) - for config in temp_data[module]: - config_group.configs[config] = ConfigModel( - **temp_data[module][config] - ) - count += 1 - self._data[module] = config_group - logger.info( - f"加载配置完成,共加载 {len(temp_data)} 个配置组及对应 {count} 个配置项" + if not self.file.exists(): + return + with open(self.file, encoding="utf8") as f: + temp_data = _yaml.load(f) + if not temp_data: + self.file.unlink() + raise ValueError( + "配置文件为空!\n" + "***********************************************************\n" + "****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n" + "***********************************************************" ) + count = 0 + for module in temp_data: + config_group = ConfigGroup(module=module) + for config in temp_data[module]: + config_group.configs[config] = ConfigModel(**temp_data[module][config]) + count += 1 + self._data[module] = config_group + logger.info( + f"加载配置完成,共加载 {len(temp_data)} 个配置组及对应" + f" {count} 个配置项" + ) - def get_data(self) -> Dict[str, ConfigGroup]: + def get_data(self) -> dict[str, ConfigGroup]: return copy.deepcopy(self._data) def is_empty(self) -> bool: diff --git a/zhenxun/models/bag_user.py b/zhenxun/models/bag_user.py index 711de8f7..bcee7da4 100644 --- a/zhenxun/models/bag_user.py +++ b/zhenxun/models/bag_user.py @@ -1,5 +1,3 @@ -from typing import Dict - from tortoise import fields from zhenxun.services.db_context import Model @@ -8,7 +6,6 @@ from .goods_info import GoodsInfo class BagUser(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255) @@ -25,10 +22,10 @@ class BagUser(Model): """今日获取金币""" spend_today_gold = fields.IntField(default=0) """今日获取金币""" - property: Dict[str, int] = fields.JSONField(default={}) # type: ignore + property: dict[str, int] = fields.JSONField(default={}) # type: ignore """道具""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "bag_users" table_description = "用户道具数据表" unique_together = ("user_id", "group_id") @@ -50,7 +47,7 @@ class BagUser(Model): @classmethod async def get_property( cls, user_id: str, group_id: str, only_active: bool = False - ) -> Dict[str, int]: + ) -> dict[str, int]: """获取当前道具 参数: @@ -153,8 +150,10 @@ class BagUser(Model): @classmethod async def _run_script(cls): return [ - "ALTER TABLE bag_users DROP props;", # 删除 props 字段 - "ALTER TABLE bag_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id + # 删除 props 字段 + "ALTER TABLE bag_users DROP props;", + # 将user_qq改为user_id + "ALTER TABLE bag_users RENAME COLUMN user_qq TO user_id;", "ALTER TABLE bag_users ALTER COLUMN user_id TYPE character varying(255);", # 将user_id字段类型改为character varying(255) "ALTER TABLE bag_users ALTER COLUMN group_id TYPE character varying(255);", diff --git a/zhenxun/models/ban_console.py b/zhenxun/models/ban_console.py index 0ab0fc69..39907ff0 100644 --- a/zhenxun/models/ban_console.py +++ b/zhenxun/models/ban_console.py @@ -1,7 +1,7 @@ import time +from typing_extensions import Self from tortoise import fields -from typing_extensions import Self from zhenxun.services.db_context import Model from zhenxun.services.log import logger @@ -9,7 +9,6 @@ from zhenxun.utils.exception import UserAndGroupIsNone class BanConsole(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, null=True) @@ -25,9 +24,9 @@ class BanConsole(Model): operator = fields.CharField(255) """使用Ban命令的用户""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "ban_console" - table_description = ".ban/b了 封禁人员/群组数据表" + table_description = "封禁人员/群组数据表" @classmethod async def _get_data(cls, user_id: str | None, group_id: str | None) -> Self | None: @@ -45,16 +44,14 @@ class BanConsole(Model): """ if not user_id and not group_id: raise UserAndGroupIsNone() - user = None if user_id: - if group_id: - user = await cls.get_or_none(user_id=user_id, group_id=group_id) - else: - user = await cls.get_or_none(user_id=user_id, group_id__isnull=True) + return ( + await cls.get_or_none(user_id=user_id, group_id=group_id) + if group_id + else await cls.get_or_none(user_id=user_id, group_id__isnull=True) + ) else: - if group_id: - user = await cls.get_or_none(user_id__isnull=True, group_id=group_id) - return user + return await cls.get_or_none(user_id="", group_id=group_id) @classmethod async def check_ban_level( @@ -91,7 +88,7 @@ class BanConsole(Model): 返回: int: ban剩余时长,-1时为永久ban,0表示未被ban """ - logger.debug(f"获取用户ban时长", target=f"{group_id}:{user_id}") + logger.debug("获取用户ban时长", target=f"{group_id}:{user_id}") user = await cls._get_data(user_id, group_id) if not user and user_id: user = await cls._get_data(user_id, None) @@ -99,9 +96,7 @@ class BanConsole(Model): if user.duration == -1: return -1 _time = time.time() - (user.ban_time + user.duration) - if _time > 0: - return 0 - return int(time.time() - user.ban_time - user.duration) + return 0 if _time > 0 else int(time.time() - user.ban_time - user.duration) return 0 @classmethod @@ -114,7 +109,7 @@ class BanConsole(Model): 返回: bool: 是否被ban """ - logger.debug(f"检测是否被ban", target=f"{group_id}:{user_id}") + logger.debug("检测是否被ban", target=f"{group_id}:{user_id}") if await cls.check_ban_time(user_id, group_id): return True else: @@ -143,8 +138,8 @@ class BanConsole(Model): f"封禁用户/群组,等级:{ban_level},时长: {duration}", target=f"{group_id}:{user_id}", ) - user = await cls._get_data(user_id, group_id) - if user: + target = await cls._get_data(user_id, group_id) + if target: await cls.unban(user_id, group_id) await cls.create( user_id=user_id, diff --git a/zhenxun/models/bot_connect_log.py b/zhenxun/models/bot_connect_log.py new file mode 100644 index 00000000..ad96186d --- /dev/null +++ b/zhenxun/models/bot_connect_log.py @@ -0,0 +1,22 @@ +from tortoise import fields + +from zhenxun.services.db_context import Model + + +class BotConnectLog(Model): + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + bot_id = fields.CharField(255, description="Bot id") + """Bot id""" + platform = fields.CharField(255, null=True, description="平台") + """平台""" + connect_time = fields.DatetimeField(description="连接时间") + """日期""" + type = fields.IntField(null=True, description="1: 连接, 0: 断开") + """1: 连接, 0: 断开""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" + + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] + table = "bot_connect_log" + table_description = "bot连接表" diff --git a/zhenxun/models/bot_console.py b/zhenxun/models/bot_console.py new file mode 100644 index 00000000..30e981ef --- /dev/null +++ b/zhenxun/models/bot_console.py @@ -0,0 +1,438 @@ +from typing import Literal, overload + +from tortoise import fields + +from zhenxun.services.db_context import Model + + +class BotConsole(Model): + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + bot_id = fields.CharField(255, unique=True, description="bot_id") + """bot_id""" + status = fields.BooleanField(default=True, description="Bot状态") + """Bot状态""" + create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") + """创建时间""" + platform = fields.CharField(255, null=True, description="平台") + """平台""" + block_plugins = fields.TextField(default="", description="禁用插件") + """禁用插件""" + block_tasks = fields.TextField(default="", description="禁用被动技能") + """禁用被动技能""" + available_plugins = fields.TextField(default="", description="可用插件") + """可用插件""" + available_tasks = fields.TextField(default="", description="可用被动技能") + """可用被动技能""" + + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] + table = "bot_console" + table_description = "Bot数据表" + + @staticmethod + def format(name: str) -> str: + return f"<{name}," + + @overload + @classmethod + async def get_bot_status(cls) -> list[tuple[str, bool]]: ... + + @overload + @classmethod + async def get_bot_status(cls, bot_id: str) -> bool: ... + + @classmethod + async def get_bot_status( + cls, bot_id: str | None = None + ) -> list[tuple[str, bool]] | bool: + """ + 获取bot状态 + + 参数: + bot_id (str, optional): bot_id. Defaults to None. + + 返回: + list[tuple[str, bool]] | bool: bot状态 + """ + if not bot_id: + return await cls.all().values_list("bot_id", "status") + result = await cls.get_or_none(bot_id=bot_id) + return result.status if result else False + + @overload + @classmethod + async def get_tasks(cls) -> list[tuple[str, list[str]]]: ... + + @overload + @classmethod + async def get_tasks(cls, bot_id: str) -> list[str]: ... + + @overload + @classmethod + async def get_tasks(cls, *, status: bool) -> dict[str, list[str]]: ... + + @overload + @classmethod + async def get_tasks(cls, bot_id: str, status: bool = True) -> list[str]: ... + + @classmethod + async def get_tasks(cls, bot_id: str | None = None, status: bool | None = True): + """ + 获取bot被动技能 + + 参数: + bot_id (str | None, optional): bot_id. Defaults to None. + status (bool | None, optional): 被动状态. Defaults to True. + + 返回: + list[tuple[str, str]] | str: 被动技能 + """ + if not bot_id: + task_field: Literal["available_tasks", "block_tasks"] = ( + "available_tasks" if status else "block_tasks" + ) + data_list = await cls.all().values_list("bot_id", task_field) + return {k: cls.convert_module_format(v) for k, v in data_list} + result = await cls.get_or_none(bot_id=bot_id) + if result: + tasks = result.available_tasks if status else result.block_tasks + return cls.convert_module_format(tasks) + return [] + + @overload + @classmethod + async def get_plugins(cls) -> dict[str, list[str]]: ... + + @overload + @classmethod + async def get_plugins(cls, bot_id: str) -> list[str]: ... + + @overload + @classmethod + async def get_plugins(cls, *, status: bool) -> dict[str, list[str]]: ... + + @overload + @classmethod + async def get_plugins(cls, bot_id: str, status: bool = True) -> list[str]: ... + + @classmethod + async def get_plugins(cls, bot_id: str | None = None, status: bool = True): + """ + 获取bot插件 + + 参数: + bot_id (str | None, optional): bot_id. Defaults to None. + status (bool, optional): 插件状态. Defaults to True. + + 返回: + list[tuple[str, str]] | str: 插件 + """ + if not bot_id: + plugin_field = "available_plugins" if status else "block_plugins" + data_list = await cls.all().values_list("bot_id", plugin_field) + return {k: cls.convert_module_format(v) for k, v in data_list} + + result = await cls.get_or_none(bot_id=bot_id) + if result: + plugins = result.available_plugins if status else result.block_plugins + return cls.convert_module_format(plugins) + return [] + + @classmethod + async def set_bot_status(cls, status: bool, bot_id: str | None = None) -> None: + """ + 设置bot状态 + + 参数: + status (bool): 状态 + bot_id (str, optional): bot_id. Defaults to None. + + Raises: + ValueError: 未找到 bot_id + """ + if bot_id: + affected_rows = await cls.filter(bot_id=bot_id).update(status=status) + if not affected_rows: + raise ValueError(f"未找到 bot_id: {bot_id}") + else: + await cls.all().update(status=status) + + @overload + @classmethod + def convert_module_format(cls, data: str) -> list[str]: ... + + @overload + @classmethod + def convert_module_format(cls, data: list[str]) -> str: ... + + @classmethod + def convert_module_format(cls, data: str | list[str]) -> str | list[str]: + """ + 在 ` None: + """ + 在 from_field 和 to_field 之间移动指定的 data + + 参数: + bot_id (str): 目标 bot 的 ID + from_field (str): 源字段名称 + to_field (str): 目标字段名称 + data (str): 要插入的内容 + + Raises: + ValueError: 如果 data 不在 from_field 和 to_field 中 + """ + bot_data, _ = await cls.get_or_create(bot_id=bot_id) + formatted_data = cls.format(data) + + from_list: str = getattr(bot_data, from_field) + to_list: str = getattr(bot_data, to_field) + + if formatted_data not in (from_list + to_list): + raise ValueError(f"{data} 不在源字段和目标字段中") + + if formatted_data in from_list: + from_list = from_list.replace(formatted_data, "", 1) + if formatted_data not in to_list: + to_list += formatted_data + + setattr(bot_data, from_field, from_list) + setattr(bot_data, to_field, to_list) + + await bot_data.save(update_fields=[from_field, to_field]) + + @classmethod + async def disable_plugin(cls, bot_id: str | None, plugin_name: str) -> None: + """ + 禁用插件 + + 参数: + bot_id (str | None): bot_id + plugin_name (str): 插件名称 + """ + if bot_id: + await cls._toggle_field( + bot_id, + "available_plugins", + "block_plugins", + plugin_name, + ) + else: + bot_list = await cls.all() + for bot in bot_list: + await cls._toggle_field( + bot.bot_id, + "available_plugins", + "block_plugins", + plugin_name, + ) + + @classmethod + async def enable_plugin(cls, bot_id: str | None, plugin_name: str) -> None: + """ + 启用插件 + + 参数: + bot_id (str | None): bot_id + plugin_name (str): 插件名称 + """ + if bot_id: + await cls._toggle_field( + bot_id, + "block_plugins", + "available_plugins", + plugin_name, + ) + else: + bot_list = await cls.all() + for bot in bot_list: + await cls._toggle_field( + bot.bot_id, + "block_plugins", + "available_plugins", + plugin_name, + ) + + @classmethod + async def disable_task(cls, bot_id: str | None, task_name: str) -> None: + """ + 禁用被动技能 + + 参数: + bot_id (str | None): bot_id + task_name (str): 被动技能名称 + """ + if bot_id: + await cls._toggle_field( + bot_id, + "available_tasks", + "block_tasks", + task_name, + ) + else: + bot_list = await cls.all() + for bot in bot_list: + await cls._toggle_field( + bot.bot_id, + "available_tasks", + "block_tasks", + task_name, + ) + + @classmethod + async def enable_task(cls, bot_id: str | None, task_name: str) -> None: + """ + 启用被动技能 + + 参数: + bot_id (str | None): bot_id + task_name (str): 被动技能名称 + """ + if bot_id: + await cls._toggle_field( + bot_id, + "block_tasks", + "available_tasks", + task_name, + ) + else: + bot_list = await cls.all() + for bot in bot_list: + await cls._toggle_field( + bot.bot_id, + "block_tasks", + "available_tasks", + task_name, + ) + + @classmethod + async def disable_all( + cls, + bot_id: str, + feat: Literal["plugins", "tasks"], + ) -> None: + """ + 禁用全部插件或被动技能 + + 参数: + bot_id (str): bot_id + feat (Literal["plugins", "tasks"]): 插件或被动技能 + """ + bot_data, _ = await cls.get_or_create(bot_id=bot_id) + if feat == "plugins": + available_plugins = cls.convert_module_format(bot_data.available_plugins) + block_plugins = cls.convert_module_format(bot_data.block_plugins) + bot_data.block_plugins = cls.convert_module_format( + available_plugins + block_plugins + ) + bot_data.available_plugins = "" + elif feat == "tasks": + available_tasks = cls.convert_module_format(bot_data.available_tasks) + block_tasks = cls.convert_module_format(bot_data.block_tasks) + bot_data.block_tasks = cls.convert_module_format( + available_tasks + block_tasks + ) + bot_data.available_tasks = "" + await bot_data.save( + update_fields=[ + "available_tasks", + "block_tasks", + "available_plugins", + "block_plugins", + ] + ) + + @classmethod + async def enable_all( + cls, + bot_id: str, + feat: Literal["plugins", "tasks"], + ) -> None: + """ + 启用全部插件或被动技能 + + 参数: + bot_id (str): bot_id + feat (Literal["plugins", "tasks"]): 插件或被动技能 + """ + bot_data, _ = await cls.get_or_create(bot_id=bot_id) + if feat == "plugins": + available_plugins = cls.convert_module_format(bot_data.available_plugins) + block_plugins = cls.convert_module_format(bot_data.block_plugins) + bot_data.available_plugins = cls.convert_module_format( + available_plugins + block_plugins + ) + bot_data.block_plugins = "" + elif feat == "tasks": + available_tasks = cls.convert_module_format(bot_data.available_tasks) + block_tasks = cls.convert_module_format(bot_data.block_tasks) + bot_data.available_tasks = cls.convert_module_format( + available_tasks + block_tasks + ) + bot_data.block_tasks = "" + await bot_data.save( + update_fields=[ + "available_tasks", + "block_tasks", + "available_plugins", + "block_plugins", + ] + ) + + @classmethod + async def is_block_plugin(cls, bot_id: str, plugin_name: str) -> bool: + """ + 检查插件是否被禁用 + + 参数: + bot_id (str): bot_id + plugin_name (str): 插件某款 + + 返回: + bool: 是否被禁用 + """ + bot_data, _ = await cls.get_or_create(bot_id=bot_id) + return cls.format(plugin_name) in bot_data.block_plugins + + @classmethod + async def is_block_task(cls, bot_id: str, task_name: str) -> bool: + """ + 检查被动技能是否被禁用 + + 参数: + bot_id (str): bot_id + task_name (str): 被动技能名称 + + 返回: + bool: 是否被禁用 + """ + bot_data, _ = await cls.get_or_create(bot_id=bot_id) + return cls.format(task_name) in bot_data.block_tasks + + @classmethod + async def _run_script(cls): + return [ + "ALTER TABLE bot_console RENAME COLUMN block_plugin TO block_plugins;", + "ALTER TABLE bot_console RENAME COLUMN block_task TO block_tasks;", + "ALTER TABLE bot_console ADD available_plugins text default '';", + "ALTER TABLE bot_console ADD available_tasks text default '';", + ] diff --git a/zhenxun/models/chat_history.py b/zhenxun/models/chat_history.py index 02425987..7284db1e 100644 --- a/zhenxun/models/chat_history.py +++ b/zhenxun/models/chat_history.py @@ -1,15 +1,14 @@ from datetime import datetime, timedelta -from typing import Literal, Tuple +from typing import Literal +from typing_extensions import Self from tortoise import fields from tortoise.functions import Count -from typing_extensions import Self from zhenxun.services.db_context import Model class ChatHistory(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255) @@ -27,7 +26,7 @@ class ChatHistory(Model): platform = fields.CharField(255, null=True) """平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "chat_history" table_description = "聊天记录数据表" @@ -53,7 +52,7 @@ class ChatHistory(Model): query = query.filter(create_time__range=date_scope) return list( await query.annotate(count=Count("user_id")) - .order_by(o + "count") + .order_by(f"{o}count") .group_by("user_id") .limit(limit) .values_list("user_id", "count") @@ -74,9 +73,7 @@ class ChatHistory(Model): ) else: message = await cls.all().order_by("create_time").first() - if message: - return message.create_time - return None + return message.create_time if message else None @classmethod async def get_message( @@ -85,7 +82,7 @@ class ChatHistory(Model): gid: str, type_: Literal["user", "group"], msg_type: Literal["private", "group"] | None = None, - days: int | Tuple[datetime, datetime] | None = None, + days: int | tuple[datetime, datetime] | None = None, ) -> list[Self]: """获取消息查询query @@ -118,13 +115,20 @@ class ChatHistory(Model): @classmethod async def _run_script(cls): return [ - "alter table chat_history alter group_id drop not null;", # 允许 group_id 为空 - "alter table chat_history alter text drop not null;", # 允许 text 为空 - "alter table chat_history alter plain_text drop not null;", # 允许 plain_text 为空 - "ALTER TABLE chat_history RENAME COLUMN user_qq TO user_id;", # 将user_id改为user_id - "ALTER TABLE chat_history ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE chat_history ALTER COLUMN group_id TYPE character varying(255);", - "ALTER TABLE chat_history ADD bot_id VARCHAR(255);", # 添加bot_id字段 + # 允许 group_id 为空 + "alter table chat_history alter group_id drop not null;", + # 允许 text 为空 + "alter table chat_history alter text drop not null;", + # 允许 plain_text 为空 + "alter table chat_history alter plain_text drop not null;", + # 将user_id改为user_id + "ALTER TABLE chat_history RENAME COLUMN user_qq TO user_id;", + "ALTER TABLE chat_history " + "ALTER COLUMN user_id TYPE character varying(255);", + "ALTER TABLE chat_history " + "ALTER COLUMN group_id TYPE character varying(255);", + # 添加bot_id字段 + "ALTER TABLE chat_history ADD bot_id VARCHAR(255);", "ALTER TABLE chat_history ALTER COLUMN bot_id TYPE character varying(255);", "ALTER TABLE chat_history ADD COLUMN platform character varying(255);", ] diff --git a/zhenxun/models/fg_request.py b/zhenxun/models/fg_request.py index 84f2e4c8..4aee1d73 100644 --- a/zhenxun/models/fg_request.py +++ b/zhenxun/models/fg_request.py @@ -1,6 +1,9 @@ +from typing_extensions import Self + from nonebot.adapters import Bot from tortoise import fields +from zhenxun.models.group_console import GroupConsole from zhenxun.services.db_context import Model from zhenxun.utils.enum import RequestHandleType, RequestType from zhenxun.utils.exception import NotFoundError @@ -32,12 +35,12 @@ class FgRequest(Model): ) """处理类型""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "fg_request" table_description = "好友群组请求" @classmethod - async def approve(cls, bot: Bot, id: int): + async def approve(cls, bot: Bot, id: int) -> Self: """同意请求 参数: @@ -47,10 +50,10 @@ class FgRequest(Model): 异常: NotFoundError: 未发现请求 """ - await cls._handle_request(bot, id, RequestHandleType.APPROVE) + return await cls._handle_request(bot, id, RequestHandleType.APPROVE) @classmethod - async def refused(cls, bot: Bot, id: int): + async def refused(cls, bot: Bot, id: int) -> Self: """拒绝请求 参数: @@ -60,10 +63,10 @@ class FgRequest(Model): 异常: NotFoundError: 未发现请求 """ - await cls._handle_request(bot, id, RequestHandleType.REFUSED) + return await cls._handle_request(bot, id, RequestHandleType.REFUSED) @classmethod - async def ignore(cls, id: int): + async def ignore(cls, id: int) -> Self: """忽略请求 参数: @@ -72,14 +75,13 @@ class FgRequest(Model): 异常: NotFoundError: 未发现请求 """ - await cls._handle_request(None, id, RequestHandleType.IGNORE) + return await cls._handle_request(None, id, RequestHandleType.IGNORE) @classmethod async def expire(cls, id: int): """忽略请求 参数: - bot: Bot id: 请求id 异常: @@ -93,7 +95,7 @@ class FgRequest(Model): bot: Bot | None, id: int, handle_type: RequestHandleType, - ): + ) -> Self: """处理请求 参数: @@ -118,8 +120,12 @@ class FgRequest(Model): flag=req.flag, approve=handle_type == RequestHandleType.APPROVE ) else: + await GroupConsole.update_or_create( + group_id=req.group_id, defaults={"group_flag": 1} + ) await bot.set_group_add_request( flag=req.flag, sub_type="invite", approve=handle_type == RequestHandleType.APPROVE, ) + return req diff --git a/zhenxun/models/friend_user.py b/zhenxun/models/friend_user.py index 597ce4f1..c6d6f119 100644 --- a/zhenxun/models/friend_user.py +++ b/zhenxun/models/friend_user.py @@ -1,5 +1,3 @@ -from typing import Union - from tortoise import fields from zhenxun.configs.config import Config @@ -18,7 +16,7 @@ class FriendUser(Model): platform = fields.CharField(255, null=True, description="平台") """平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "friend_users" table_description = "好友信息数据表" @@ -78,6 +76,8 @@ class FriendUser(Model): @classmethod def _run_script(cls): return [ - "ALTER TABLE friend_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE friend_users ADD COLUMN platform character varying(255) default 'qq';", + "ALTER TABLE friend_users " + "ALTER COLUMN user_id TYPE character varying(255);", + "ALTER TABLE friend_users " + "ADD COLUMN platform character varying(255) default 'qq';", ] diff --git a/zhenxun/models/goods_info.py b/zhenxun/models/goods_info.py index f776500b..07efa6f4 100644 --- a/zhenxun/models/goods_info.py +++ b/zhenxun/models/goods_info.py @@ -1,8 +1,7 @@ +from typing_extensions import Self import uuid -from typing import Dict from tortoise import fields -from typing_extensions import Self from zhenxun.services.db_context import Model @@ -28,10 +27,12 @@ class GoodsInfo(Model): """每日限购""" is_passive = fields.BooleanField(default=False) """是否为被动道具""" + partition = fields.CharField(255, null=True) + """分区名称""" icon = fields.TextField(null=True) """图标路径""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "goods_info" table_description = "商品数据表" @@ -45,6 +46,7 @@ class GoodsInfo(Model): goods_limit_time: int = 0, daily_limit: int = 0, is_passive: bool = False, + partition: str | None = None, icon: str | None = None, ) -> str: """添加商品 @@ -57,6 +59,7 @@ class GoodsInfo(Model): goods_limit_time: 商品限时 daily_limit: 每日购买限制 is_passive: 是否为被动道具 + partition: 分区名称 icon: 图标 """ if not await cls.exists(goods_name=goods_name): @@ -70,6 +73,7 @@ class GoodsInfo(Model): goods_limit_time=goods_limit_time, daily_limit=daily_limit, is_passive=is_passive, + partition=partition, icon=icon, ) return str(uuid_) @@ -147,7 +151,7 @@ class GoodsInfo(Model): goods_lst = [] for _ in range(len(query)): min_id = min(id_lst) - goods_lst.append([x for x in query if x.id == min_id][0]) + goods_lst.append(next(x for x in query if x.id == min_id)) id_lst.remove(min_id) return goods_lst @@ -158,5 +162,7 @@ class GoodsInfo(Model): "ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;", "ALTER TABLE goods_info ADD is_passive boolean DEFAULT False;", "ALTER TABLE goods_info ADD icon VARCHAR(255);", - "ALTER TABLE goods_info DROP daily_purchase_limit;", # 删除 daily_purchase_limit 字段 + # 删除 daily_purchase_limit 字段 + "ALTER TABLE goods_info DROP daily_purchase_limit;", + "ALTER TABLE goods_info ADD partition VARCHAR(255);", ] diff --git a/zhenxun/models/group_console.py b/zhenxun/models/group_console.py index 88959e8b..70a3d901 100644 --- a/zhenxun/models/group_console.py +++ b/zhenxun/models/group_console.py @@ -1,11 +1,16 @@ -from tortoise import fields +from typing import Any, overload from typing_extensions import Self +from tortoise import fields +from tortoise.backends.base.client import BaseDBAsyncClient + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.models.task_info import TaskInfo from zhenxun.services.db_context import Model +from zhenxun.utils.enum import PluginType class GroupConsole(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" group_id = fields.CharField(255, description="群组id") @@ -30,18 +35,128 @@ class GroupConsole(Model): """群认证标记""" block_plugin = fields.TextField(default="", description="禁用插件") """禁用插件""" - block_task = fields.TextField(default="", description="禁用插件") - """禁用插件""" + superuser_block_plugin = fields.TextField( + default="", description="超级用户禁用插件" + ) + """超级用户禁用插件""" + block_task = fields.TextField(default="", description="禁用被动技能") + """禁用被动技能""" + superuser_block_task = fields.TextField(default="", description="超级用户禁用被动") + """超级用户禁用被动""" platform = fields.CharField(255, default="qq", description="所属平台") """所属平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "group_console" table_description = "群组信息表" unique_together = ("group_id", "channel_id") + @staticmethod + def format(name: str) -> str: + return f"<{name}," + + @overload @classmethod - async def get_group(cls, group_id: str, channel_id: str | None = None) -> Self: + def convert_module_format(cls, data: str) -> list[str]: ... + + @overload + @classmethod + def convert_module_format(cls, data: list[str]) -> str: ... + + @classmethod + def convert_module_format(cls, data: str | list[str]) -> str | list[str]: + """ + 在 ` Self: + """覆盖create方法""" + group = await super().create(using_db=using_db, **kwargs) + if modules := await TaskInfo.filter(default_status=False).values_list( + "module", flat=True + ): + group.block_task = cls.convert_module_format(modules) # type: ignore + if modules := await PluginInfo.filter( + plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], + default_status=False, + ).values_list("module", flat=True): + group.block_plugin = cls.convert_module_format(modules) # type: ignore + await group.save( + using_db=using_db, update_fields=["block_plugin", "block_task"] + ) + return group + + @classmethod + async def get_or_create( + cls, + defaults: dict | None = None, + using_db: BaseDBAsyncClient | None = None, + **kwargs: Any, + ) -> tuple[Self, bool]: + """覆盖get_or_create方法""" + group, is_create = await super().get_or_create( + defaults=defaults, using_db=using_db, **kwargs + ) + if is_create and ( + modules := await TaskInfo.filter(default_status=False).values_list( + "module", flat=True + ) + ): + group.block_task = cls.convert_module_format(modules) # type: ignore + if modules := await PluginInfo.filter( + plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], + default_status=False, + ).values_list("module", flat=True): + group.block_plugin = cls.convert_module_format(modules) # type: ignore + await group.save( + using_db=using_db, update_fields=["block_plugin", "block_task"] + ) + return group, is_create + + @classmethod + async def update_or_create( + cls, + defaults: dict | None = None, + using_db: BaseDBAsyncClient | None = None, + **kwargs: Any, + ) -> tuple[Self, bool]: + """覆盖update_or_create方法""" + group, is_create = await super().update_or_create( + defaults=defaults, using_db=using_db, **kwargs + ) + if is_create and ( + modules := await TaskInfo.filter(default_status=False).values_list( + "module", flat=True + ) + ): + group.block_task = cls.convert_module_format(modules) # type: ignore + if modules := await PluginInfo.filter( + plugin_type__in=[PluginType.NORMAL, PluginType.DEPENDANT], + default_status=False, + ).values_list("module", flat=True): + group.block_plugin = cls.convert_module_format(modules) # type: ignore + await group.save( + using_db=using_db, update_fields=["block_plugin", "block_task"] + ) + return group, is_create + + @classmethod + async def get_group( + cls, group_id: str, channel_id: str | None = None + ) -> Self | None: """获取群组 参数: @@ -52,46 +167,110 @@ class GroupConsole(Model): Self: GroupConsole """ if channel_id: - return await cls.get(group_id=group_id, channel_id=channel_id) - return await cls.get(group_id=group_id, channel_id__isnull=True) + return await cls.get_or_none(group_id=group_id, channel_id=channel_id) + return await cls.get_or_none(group_id=group_id, channel_id__isnull=True) @classmethod - async def is_super_group(cls, group_id: str, channel_id: str | None = None) -> bool: + async def is_super_group(cls, group_id: str) -> bool: """是否超级用户指定群 参数: group_id: 群组id - channel_id: 频道id. 返回: bool: 是否超级用户指定群 """ - if group := await cls.get_or_none(group_id=group_id): - return group.is_super - return False + return group.is_super if (group := await cls.get_group(group_id)) else False @classmethod - async def is_super_block_plugin( - cls, group_id: str, module: str, channel_id: str | None = None - ) -> bool: + async def is_superuser_block_plugin(cls, group_id: str, module: str) -> bool: """查看群组是否超级用户禁用功能 参数: group_id: 群组id module: 模块名称 - channel_id: 频道id 返回: bool: 是否禁用被动 """ return await cls.exists( group_id=group_id, - channel_id=channel_id, - block_plugin__contains=f"super:{module},", + superuser_block_plugin__contains=f"<{module},", ) @classmethod - async def is_block_plugin( + async def is_block_plugin(cls, group_id: str, module: str) -> bool: + """查看群组是否禁用插件 + + 参数: + group_id: 群组id + plugin: 插件名称 + + 返回: + bool: 是否禁用插件 + """ + return await cls.exists( + group_id=group_id, block_plugin__contains=f"<{module}," + ) or await cls.exists( + group_id=group_id, superuser_block_plugin__contains=f"<{module}," + ) + + @classmethod + async def set_block_plugin( + cls, + group_id: str, + module: str, + is_superuser: bool = False, + platform: str | None = None, + ): + """禁用群组插件 + + 参数: + group_id: 群组id + task: 任务模块 + is_superuser: 是否为超级用户 + platform: 平台 + """ + group, _ = await cls.get_or_create( + group_id=group_id, defaults={"platform": platform} + ) + if is_superuser: + if f"<{module}," not in group.superuser_block_plugin: + group.superuser_block_plugin += f"<{module}," + elif f"<{module}," not in group.block_plugin: + group.block_plugin += f"<{module}," + await group.save(update_fields=["block_plugin", "superuser_block_plugin"]) + + @classmethod + async def set_unblock_plugin( + cls, + group_id: str, + module: str, + is_superuser: bool = False, + platform: str | None = None, + ): + """禁用群组插件 + + 参数: + group_id: 群组id + task: 任务模块 + is_superuser: 是否为超级用户 + platform: 平台 + """ + group, _ = await cls.get_or_create( + group_id=group_id, defaults={"platform": platform} + ) + if is_superuser: + if f"<{module}," in group.superuser_block_plugin: + group.superuser_block_plugin = group.superuser_block_plugin.replace( + f"<{module},", "" + ) + elif f"<{module}," in group.block_plugin: + group.block_plugin = group.block_plugin.replace(f"<{module},", "") + await group.save(update_fields=["block_plugin", "superuser_block_plugin"]) + + @classmethod + async def is_normal_block_plugin( cls, group_id: str, module: str, channel_id: str | None = None ) -> bool: """查看群组是否禁用功能 @@ -107,7 +286,23 @@ class GroupConsole(Model): return await cls.exists( group_id=group_id, channel_id=channel_id, - block_plugin__contains=f"{module},", + block_plugin__contains=f"<{module},", + ) + + @classmethod + async def is_superuser_block_task(cls, group_id: str, task: str) -> bool: + """查看群组是否超级用户禁用被动 + + 参数: + group_id: 群组id + task: 模块名称 + + 返回: + bool: 是否禁用被动 + """ + return await cls.exists( + group_id=group_id, + superuser_block_task__contains=f"<{task},", ) @classmethod @@ -128,12 +323,79 @@ class GroupConsole(Model): return await cls.exists( group_id=group_id, channel_id__isnull=True, - block_task__contains=f"{task},", + block_task__contains=f"<{task},", + ) or await cls.exists( + group_id=group_id, + channel_id__isnull=True, + superuser_block_task__contains=f"<{task},", ) return await cls.exists( - group_id=group_id, channel_id=channel_id, block_task__contains=f"{task}," + group_id=group_id, channel_id=channel_id, block_task__contains=f"<{task}," + ) or await cls.exists( + group_id=group_id, + channel_id__isnull=True, + superuser_block_task__contains=f"<{task},", ) + @classmethod + async def set_block_task( + cls, + group_id: str, + task: str, + is_superuser: bool = False, + platform: str | None = None, + ): + """禁用群组插件 + + 参数: + group_id: 群组id + task: 任务模块 + is_superuser: 是否为超级用户 + platform: 平台 + """ + group, _ = await cls.get_or_create( + group_id=group_id, defaults={"platform": platform} + ) + if is_superuser: + if f"<{task}," not in group.superuser_block_task: + group.superuser_block_task += f"<{task}," + elif f"<{task}," not in group.block_task: + group.block_task += f"<{task}," + await group.save(update_fields=["block_task", "superuser_block_task"]) + + @classmethod + async def set_unblock_task( + cls, + group_id: str, + task: str, + is_superuser: bool = False, + platform: str | None = None, + ): + """禁用群组插件 + + 参数: + group_id: 群组id + task: 任务模块 + is_superuser: 是否为超级用户 + platform: 平台 + """ + group, _ = await cls.get_or_create( + group_id=group_id, defaults={"platform": platform} + ) + if is_superuser: + if f"<{task}," in group.superuser_block_task: + group.superuser_block_task = group.superuser_block_task.replace( + f"<{task},", "" + ) + elif f"<{task}," in group.block_task: + group.block_task = group.block_task.replace(f"<{task},", "") + await group.save(update_fields=["block_task", "superuser_block_task"]) + @classmethod def _run_script(cls): - return [] + return [ + "ALTER TABLE group_console ADD superuser_block_plugin" + " character varying(255) NOT NULL DEFAULT '';", + "ALTER TABLE group_console ADD superuser_block_task" + " character varying(255) NOT NULL DEFAULT '';", + ] diff --git a/zhenxun/models/group_info.py b/zhenxun/models/group_info.py index 8183bf16..b60120a1 100644 --- a/zhenxun/models/group_info.py +++ b/zhenxun/models/group_info.py @@ -23,7 +23,7 @@ class GroupInfo(Model): platform = fields.CharField(255, default="qq", description="所属平台") """所属平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "group_info" table_description = "群聊信息表" @@ -40,12 +40,81 @@ class GroupInfo(Model): """ return await cls.exists(group_id=group_id, block_task__contains=f"{task},") + @classmethod + async def is_block_plugin(cls, group_id: str, module: str) -> bool: + """查看群组是否禁用插件 + + 参数: + group_id: 群组id + plugin: 插件名称 + + 返回: + bool: 是否禁用插件 + """ + return await cls.exists( + group_id=group_id, block_plugin__contains=f"{module}," + ) or await cls.exists( + group_id=group_id, superuser_block_plugin__contains=f"{module}," + ) + + @classmethod + async def set_block_plugin( + cls, + group_id: str, + module: str, + is_superuser: bool = False, + platform: str | None = None, + ): + """禁用群组插件 + + 参数: + group_id: 群组id + task: 任务模块 + """ + group, _ = await cls.get_or_create( + group_id=group_id, defaults={"platform": platform} + ) + if is_superuser: + if "module," not in group.superuser_block_plugin: # type: ignore + group.superuser_block_plugin += f"{module}," # type: ignore + elif "module," not in group.block_plugin: + group.block_plugin += f"{module}," + await group.save(update_fields=["block_plugin", "superuser_block_plugin"]) + + @classmethod + async def set_unblock_plugin( + cls, + group_id: str, + module: str, + is_superuser: bool = False, + platform: str | None = None, + ): + """禁用群组插件 + + 参数: + group_id: 群组id + task: 任务模块 + """ + group, _ = await cls.get_or_create( + group_id=group_id, defaults={"platform": platform} + ) + if is_superuser: + if "module," in group.superuser_block_plugin: # type: ignore + group.superuser_block_plugin = group.superuser_block_plugin.replace( # type: ignore + f"{module},", "" + ) + elif "module," in group.block_plugin: + group.block_plugin = group.block_plugin.replace(f"{module},", "") + await group.save(update_fields=["block_plugin", "superuser_block_plugin"]) + @classmethod def _run_script(cls): return [ - "ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;", # group_info表添加一个group_flag + "ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;", + # group_info表添加一个group_flag "ALTER TABLE group_info ALTER COLUMN group_id TYPE character varying(255);", "ALTER TABLE group_info ADD block_plugin Text NOT NULL DEFAULT '';", "ALTER TABLE group_info ADD block_task Text NOT NULL DEFAULT '';", - "ALTER TABLE group_info ADD platform character varying(255) NOT NULL DEFAULT 'qq';", + "ALTER TABLE group_info ADD platform character varying(255) NOT NULL" + " DEFAULT 'qq';", ] diff --git a/zhenxun/models/group_member_info.py b/zhenxun/models/group_member_info.py index dc9100ca..2bb3d46f 100644 --- a/zhenxun/models/group_member_info.py +++ b/zhenxun/models/group_member_info.py @@ -1,5 +1,3 @@ -from typing import Set - from tortoise import fields from zhenxun.configs.config import Config @@ -24,13 +22,13 @@ class GroupInfoUser(Model): platform = fields.CharField(255, null=True, description="平台") """平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "group_info_users" table_description = "群员信息数据表" unique_together = ("user_id", "group_id") @classmethod - async def get_all_uid(cls, group_id: str) -> Set[int]: + async def get_all_uid(cls, group_id: str) -> set[int]: """获取该群所有用户id 参数: @@ -101,11 +99,18 @@ class GroupInfoUser(Model): @classmethod async def _run_script(cls): return [ - "alter table group_info_users alter user_join_time drop not null;", # 允许 user_join_time 为空 - "ALTER TABLE group_info_users ALTER COLUMN user_join_time TYPE timestamp with time zone USING user_join_time::timestamp with time zone;", - "ALTER TABLE group_info_users RENAME COLUMN user_qq TO user_id;", # 将user_id改为user_id - "ALTER TABLE group_info_users ALTER COLUMN user_id TYPE character varying(255);", + # 允许 user_join_time 为空 + "alter table group_info_users alter user_join_time drop not null;", + "ALTER TABLE group_info_users " + "ALTER COLUMN user_join_time TYPE timestamp with time zone " + "USING user_join_time::timestamp with time zone;", + # 将user_id改为user_id + "ALTER TABLE group_info_users RENAME COLUMN user_qq TO user_id;", + "ALTER TABLE group_info_users " + "ALTER COLUMN user_id TYPE character varying(255);", # 将user_id字段类型改为character varying(255) - "ALTER TABLE group_info_users ALTER COLUMN group_id TYPE character varying(255);", - "ALTER TABLE group_info_users ADD COLUMN platform character varying(255) default 'qq';", + "ALTER TABLE group_info_users " + "ALTER COLUMN group_id TYPE character varying(255);", + "ALTER TABLE group_info_users " + "ADD COLUMN platform character varying(255) default 'qq';", ] diff --git a/zhenxun/models/level_user.py b/zhenxun/models/level_user.py index c2c4fc31..d60b52c2 100644 --- a/zhenxun/models/level_user.py +++ b/zhenxun/models/level_user.py @@ -1,6 +1,3 @@ -from datetime import datetime -from typing import Union - from tortoise import fields from zhenxun.services.db_context import Model @@ -18,7 +15,7 @@ class LevelUser(Model): group_flag = fields.IntField(default=0) """特殊标记,是否随群管理员变更而设置权限""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "level_users" table_description = "用户权限数据库" unique_together = ("user_id", "group_id") @@ -120,8 +117,11 @@ class LevelUser(Model): @classmethod async def _run_script(cls): return [ - "ALTER TABLE level_users RENAME COLUMN user_qq TO user_id;", # 将user_id改为user_id - "ALTER TABLE level_users ALTER COLUMN user_id TYPE character varying(255);", + # 将user_id改为user_id + "ALTER TABLE level_users RENAME COLUMN user_qq TO user_id;", + "ALTER TABLE level_users " + "ALTER COLUMN user_id TYPE character varying(255);", # 将user_id字段类型改为character varying(255) - "ALTER TABLE level_users ALTER COLUMN group_id TYPE character varying(255);", + "ALTER TABLE level_users " + "ALTER COLUMN group_id TYPE character varying(255);", ] diff --git a/zhenxun/models/plugin_info.py b/zhenxun/models/plugin_info.py index e5eb1b21..b6dfd2ce 100644 --- a/zhenxun/models/plugin_info.py +++ b/zhenxun/models/plugin_info.py @@ -1,10 +1,11 @@ +from typing_extensions import Self + from tortoise import fields +from zhenxun.models.plugin_limit import PluginLimit # noqa: F401 from zhenxun.services.db_context import Model from zhenxun.utils.enum import BlockType, PluginType -from .plugin_limit import PluginLimit - class PluginInfo(Model): id = fields.IntField(pk=True, generated=True, auto_increment=True) @@ -43,9 +44,50 @@ class PluginInfo(Model): """插件限制""" admin_level = fields.IntField(default=0, null=True, description="调用所需权限等级") """调用所需权限等级""" + ignore_prompt = fields.BooleanField(default=False, description="是否忽略提示") + """是否忽略阻断提示""" is_delete = fields.BooleanField(default=False, description="是否删除") """是否删除""" + parent = fields.CharField(max_length=255, null=True, description="父插件") + """父插件""" + is_show = fields.BooleanField(default=True, description="是否显示在帮助中") + """是否显示在帮助中""" + impression = fields.FloatField(default=0, description="插件好感度限制") + """插件好感度限制""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "plugin_info" table_description = "插件基本信息" + + @classmethod + async def get_plugin(cls, load_status: bool = True, **kwargs) -> Self | None: + """获取插件列表 + + 参数: + load_status: 加载状态. + + 返回: + Self | None: 插件 + """ + return await cls.get_or_none(load_status=load_status, **kwargs) + + @classmethod + async def get_plugins(cls, load_status: bool = True, **kwargs) -> list[Self]: + """获取插件列表 + + 参数: + load_status: 加载状态. + + 返回: + list[Self]: 插件列表 + """ + return await cls.filter(load_status=load_status, **kwargs).all() + + @classmethod + async def _run_script(cls): + return [ + "ALTER TABLE plugin_info ADD COLUMN parent character varying(255);", + "ALTER TABLE plugin_info ADD COLUMN is_show boolean DEFAULT true;", + "ALTER TABLE plugin_info ADD COLUMN ignore_prompt boolean DEFAULT false;", + "ALTER TABLE plugin_info ADD COLUMN impression float DEFAULT 0;", + ] diff --git a/zhenxun/models/plugin_limit.py b/zhenxun/models/plugin_limit.py index e6b185e7..bee905ff 100644 --- a/zhenxun/models/plugin_limit.py +++ b/zhenxun/models/plugin_limit.py @@ -35,6 +35,6 @@ class PluginLimit(Model): max_count = fields.IntField(null=True, description="最大调用次数") """最大调用次数""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "plugin_limit" table_description = "插件限制" diff --git a/zhenxun/models/sign_group_user.py b/zhenxun/models/sign_group_user.py index ca397270..adbd797c 100644 --- a/zhenxun/models/sign_group_user.py +++ b/zhenxun/models/sign_group_user.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List, Literal, Optional, Tuple, Union from tortoise import fields @@ -7,7 +6,6 @@ from zhenxun.services.db_context import Model class SignGroupUser(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255) @@ -26,7 +24,7 @@ class SignGroupUser(Model): """使用指定双倍概率""" # specify_probability = fields.DecimalField(10, 3, default=0) - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "sign_group_users" table_description = "群员签到数据表" unique_together = ("user_id", "group_id") @@ -49,8 +47,8 @@ class SignGroupUser(Model): @classmethod async def get_all_impression( - cls, group_id: Union[int, str] - ) -> Tuple[List[str], List[float], List[str]]: + cls, group_id: int | str + ) -> tuple[list[str], list[float], list[str]]: """ 说明: 获取该群所有用户 id 及对应 好感度 @@ -74,8 +72,11 @@ class SignGroupUser(Model): @classmethod async def _run_script(cls): return [ - "ALTER TABLE sign_group_users RENAME COLUMN user_qq TO user_id;", # 将user_id改为user_id - "ALTER TABLE sign_group_users ALTER COLUMN user_id TYPE character varying(255);", + # 将user_id改为user_id + "ALTER TABLE sign_group_users RENAME COLUMN user_qq TO user_id;", + "ALTER TABLE sign_group_users " + "ALTER COLUMN user_id TYPE character varying(255);", # 将user_id字段类型改为character varying(255) - "ALTER TABLE sign_group_users ALTER COLUMN group_id TYPE character varying(255);", + "ALTER TABLE sign_group_users " + "ALTER COLUMN group_id TYPE character varying(255);", ] diff --git a/zhenxun/models/sign_log.py b/zhenxun/models/sign_log.py index cbfca947..6424f94c 100644 --- a/zhenxun/models/sign_log.py +++ b/zhenxun/models/sign_log.py @@ -1,13 +1,9 @@ -from datetime import datetime -from typing import List, Literal, Optional, Tuple, Union - from tortoise import fields from zhenxun.services.db_context import Model class SignLog(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, description="用户id") @@ -21,6 +17,6 @@ class SignLog(Model): platform = fields.CharField(255, null=True, description="平台") """平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "sign_log" table_description = "用户签到记录表" diff --git a/zhenxun/models/sign_user.py b/zhenxun/models/sign_user.py index eb25b6cb..5a9fc234 100644 --- a/zhenxun/models/sign_user.py +++ b/zhenxun/models/sign_user.py @@ -1,6 +1,7 @@ -from tortoise import fields from typing_extensions import Self +from tortoise import fields + from zhenxun.services.db_context import Model from .sign_log import SignLog @@ -8,7 +9,6 @@ from .user_console import UserConsole class SignUser(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, unique=True, description="用户id") @@ -32,10 +32,28 @@ class SignUser(Model): platform = fields.CharField(255, null=True, description="平台") """平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "sign_users" table_description = "用户签到数据表" + @classmethod + async def get_user(cls, user_id: str, platform: str | None = None) -> "SignUser": + """获取签到用户 + + 参数: + user_id: 用户id + platform: 平台. + + 返回: + Self: SignUser + """ + user_console = await UserConsole.get_user(user_id, platform) + user, _ = await SignUser.get_or_create( + user_id=user_id, + defaults={"user_console": user_console, "platform": platform}, + ) + return user + @classmethod async def sign( cls, diff --git a/zhenxun/models/statistics.py b/zhenxun/models/statistics.py index 43576fcb..812a9d6d 100644 --- a/zhenxun/models/statistics.py +++ b/zhenxun/models/statistics.py @@ -4,7 +4,6 @@ from zhenxun.services.db_context import Model class Statistics(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255) @@ -15,15 +14,19 @@ class Statistics(Model): """插件名称""" create_time = fields.DatetimeField(auto_now=True) """添加日期""" + bot_id = fields.CharField(255, null=True) + """Bot Id""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "statistics" table_description = "插件调用统计数据库" @classmethod async def _run_script(cls): return [ - "ALTER TABLE statistics RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id + "ALTER TABLE statistics RENAME COLUMN user_qq TO user_id;", + # 将user_qq改为user_id "ALTER TABLE statistics ALTER COLUMN user_id TYPE character varying(255);", "ALTER TABLE statistics ALTER COLUMN group_id TYPE character varying(255);", + "ALTER TABLE statistics ADD bot_id Text DEFAULT '';", ] diff --git a/zhenxun/models/task_info.py b/zhenxun/models/task_info.py index 3ca1fb49..d80d7bac 100644 --- a/zhenxun/models/task_info.py +++ b/zhenxun/models/task_info.py @@ -2,9 +2,6 @@ from tortoise import fields from zhenxun.services.db_context import Model -from .ban_console import BanConsole -from .group_console import GroupConsole - class TaskInfo(Model): id = fields.IntField(pk=True, generated=True, auto_increment=True) @@ -15,41 +12,23 @@ class TaskInfo(Model): """被动技能名称""" status = fields.BooleanField(default=True, description="全局开关状态") """全局开关状态""" + load_status = fields.BooleanField(default=True, description="进群默认开关状态") + """加载状态""" + default_status = fields.BooleanField(default=True, description="进群默认开关状态") + """全局开关状态""" run_time = fields.CharField(255, null=True, description="运行时间") """运行时间""" run_count = fields.IntField(default=0, description="运行次数") """运行次数""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "task_info" table_description = "被动技能基本信息" @classmethod - async def is_block(cls, module: str, group_id: str | None) -> bool: - """判断被动技能是否可以发送 - - 参数: - module: 被动技能模块名 - group_id: 群组id - - 返回: - bool: 是否可以发送 - """ - if task := await cls.get_or_none(module=module): - """被动全局状态""" - if not task.status: - return True - if group_id: - if await GroupConsole.is_block_task(group_id, module): - """群组是否禁用被动""" - return True - if g := await GroupConsole.get_or_none( - group_id=group_id, channel_id__isnull=True - ): - """群组权限是否小于0""" - if g.level < 0: - return True - if await BanConsole.is_ban(None, group_id): - """群组是否被ban""" - return True - return False + async def _run_script(cls): + return [ + "ALTER TABLE task_info ADD default_status boolean DEFAULT true;", + "ALTER TABLE task_info ADD load_status boolean DEFAULT false;", + # 默认状态 + ] diff --git a/zhenxun/models/user_console.py b/zhenxun/models/user_console.py index 55fd021b..b590a802 100644 --- a/zhenxun/models/user_console.py +++ b/zhenxun/models/user_console.py @@ -1,5 +1,3 @@ -from typing import Dict - from tortoise import fields from zhenxun.models.goods_info import GoodsInfo @@ -11,7 +9,6 @@ from .user_gold_log import UserGoldLog class UserConsole(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, unique=True, description="用户id") @@ -22,14 +19,14 @@ class UserConsole(Model): """金币数量""" sign = fields.ReverseRelation["SignUser"] # type: ignore """好感度""" - props: Dict[str, int] = fields.JSONField(default={}) # type: ignore + props: dict[str, int] = fields.JSONField(default={}) # type: ignore """道具""" platform = fields.CharField(255, null=True, description="平台") """平台""" create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") """创建时间""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "user_console" table_description = "用户数据表" @@ -109,7 +106,8 @@ class UserConsole(Model): InsufficientGold: 金币不足 """ user, _ = await cls.get_or_create( - user_id=user_id, defaults={"platform": platform, "uid": cls.get_new_uid()} + user_id=user_id, + defaults={"platform": platform, "uid": await cls.get_new_uid()}, ) if user.gold < gold: raise InsufficientGold() @@ -176,6 +174,8 @@ class UserConsole(Model): if goods_uuid not in user.props or user.props[goods_uuid] < num: raise GoodsNotFound("未找到商品或道具数量不足...") user.props[goods_uuid] -= num + if user.props[goods_uuid] <= 0: + del user.props[goods_uuid] await user.save(update_fields=["props"]) @classmethod diff --git a/zhenxun/models/user_gold_log.py b/zhenxun/models/user_gold_log.py index 30e83242..399a0e2c 100644 --- a/zhenxun/models/user_gold_log.py +++ b/zhenxun/models/user_gold_log.py @@ -5,7 +5,6 @@ from zhenxun.utils.enum import GoldHandle class UserGoldLog(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, description="用户id") @@ -19,6 +18,6 @@ class UserGoldLog(Model): create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") """创建时间""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "user_gold_log" table_description = "用户金币记录表" diff --git a/zhenxun/models/user_props.py b/zhenxun/models/user_props.py index 3e3a5e2b..bcae2e63 100644 --- a/zhenxun/models/user_props.py +++ b/zhenxun/models/user_props.py @@ -1,25 +1,20 @@ -from typing import Dict - from tortoise import fields from zhenxun.services.db_context import Model -from .sign_user import SignUser - class UserProps(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, unique=True, description="用户id") """用户id""" name = fields.CharField(255, description="道具名称") """道具名称""" - property: Dict[str, int] = fields.JSONField(default={}) # type: ignore + property: dict[str, int] = fields.JSONField(default={}) # type: ignore """道具""" platform = fields.CharField(255, null=True) """平台""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "user_props" table_description = "用户道具表" diff --git a/zhenxun/models/user_props_log.py b/zhenxun/models/user_props_log.py index 16ae405c..9cae679d 100644 --- a/zhenxun/models/user_props_log.py +++ b/zhenxun/models/user_props_log.py @@ -1,15 +1,10 @@ -from typing import Dict - from tortoise import fields from zhenxun.services.db_context import Model from zhenxun.utils.enum import PropHandle -from .sign_user import SignUser - class UserPropsLog(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) """自增id""" user_id = fields.CharField(255, description="用户id") @@ -25,6 +20,6 @@ class UserPropsLog(Model): create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") """创建时间""" - class Meta: + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] table = "user_props_log" table_description = "用户道具记录表" diff --git a/zhenxun/plugins/image_management/send_image.py b/zhenxun/plugins/__init__.py similarity index 100% rename from zhenxun/plugins/image_management/send_image.py rename to zhenxun/plugins/__init__.py diff --git a/zhenxun/plugins/ai/__init__.py b/zhenxun/plugins/ai/__init__.py deleted file mode 100644 index 28e15354..00000000 --- a/zhenxun/plugins/ai/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import List - -from nonebot import on_message -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.services.log import logger -from zhenxun.utils.depends import UserName -from zhenxun.utils.message import MessageUtils - -from .data_source import get_chat_result, hello, no_result - -__plugin_meta__ = PluginMetadata( - name="AI", - description="屑Ai", - usage=f""" - 与{NICKNAME}普普通通的对话吧! - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - configs=[ - RegisterConfig( - module="alapi", - key="ALAPI_TOKEN", - value=None, - help="在 https://admin.alapi.cn/user/login 登录后获取token", - ), - RegisterConfig(key="TL_KEY", value=[], help="图灵Key", type=List[str]), - RegisterConfig( - key="ALAPI_AI_CHECK", - value=False, - help="是否检测青云客骂娘回复", - default_value=False, - type=bool, - ), - RegisterConfig( - key="TEXT_FILTER", - value=["鸡", "口交"], - help="文本过滤器,将敏感词更改为*", - type=List[str], - ), - ], - ).dict(), -) - - -ai = on_message(rule=to_me(), priority=998) - - -@ai.handle() -async def _(message: UniMsg, session: EventSession, uname: str = UserName()): - if not message or message.extract_plain_text() in [ - "你好啊", - "你好", - "在吗", - "在不在", - "您好", - "您好啊", - "你好", - "在", - ]: - await hello().finish() - if not session.id1: - await Text("用户id不存在...").finish() - gid = session.id3 or session.id2 - if gid: - nickname = await GroupInfoUser.get_user_nickname(session.id1, gid) - else: - nickname = await FriendUser.get_user_nickname(session.id1) - if not nickname: - nickname = uname - result = await get_chat_result(message, session.id1, nickname) - logger.info(f"问题:{message} ---- 回答:{result}", "ai", session=session) - if result: - result = str(result) - for t in Config.get_config("ai", "TEXT_FILTER"): - result = result.replace(t, "*") - await MessageUtils.build_message(result).finish() - else: - await no_result().finish() diff --git a/zhenxun/plugins/ai/data_source.py b/zhenxun/plugins/ai/data_source.py deleted file mode 100644 index 555f3885..00000000 --- a/zhenxun/plugins/ai/data_source.py +++ /dev/null @@ -1,241 +0,0 @@ -import os -import random -import re - -import ujson as json -from nonebot_plugin_alconna import UniMessage, UniMsg - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -from .utils import ai_message_manager - -url = "http://openapi.tuling123.com/openapi/api/v2" - -check_url = "https://v2.alapi.cn/api/censor/text" - -index = 0 - -anime_data = json.load(open(DATA_PATH / "anime.json", "r", encoding="utf8")) - - -async def get_chat_result( - message: UniMsg, user_id: str, nickname: str -) -> UniMessage | None: - """获取 AI 返回值,顺序: 特殊回复 -> 图灵 -> 青云客 - - 参数: - text: 问题 - img_url: 图片链接 - user_id: 用户id - nickname: 用户昵称 - - 返回 - str: 回答 - """ - global index - text = message.extract_plain_text() - ai_message_manager.add_message(user_id, text) - special_rst = await ai_message_manager.get_result(user_id, nickname) - if special_rst: - ai_message_manager.add_result(user_id, special_rst) - return MessageUtils.build_message(special_rst) - if index == 5: - index = 0 - if len(text) < 6 and random.random() < 0.6: - keys = anime_data.keys() - for key in keys: - if text.find(key) != -1: - return random.choice(anime_data[key]).replace("你", nickname) - rst = await tu_ling(text, "", user_id) - if not rst: - rst = await xie_ai(text) - if not rst: - return None - if nickname: - if len(nickname) < 5: - if random.random() < 0.5: - nickname = "~".join(nickname) + "~" - if random.random() < 0.2: - if nickname.find("大人") == -1: - nickname += "大~人~" - rst = str(rst).replace("小主人", nickname).replace("小朋友", nickname) - ai_message_manager.add_result(user_id, rst) - for t in Config.get_config("ai", "TEXT_FILTER"): - rst = rst.replace(t, "*") - return MessageUtils.build_message(rst) - - -# 图灵接口 -async def tu_ling(text: str, img_url: str, user_id: str) -> str | None: - """获取图灵接口的回复 - - 参数: - text: 问题 - img_url: 图片链接 - user_id: 用户id - - 返回 - str: 图灵回复 - """ - global index - TL_KEY = Config.get_config("ai", "TL_KEY") - req = None - if not TL_KEY: - return None - try: - if text: - req = { - "perception": { - "inputText": {"text": text}, - "selfInfo": { - "location": { - "city": "陨石坑", - "province": "火星", - "street": "第5坑位", - } - }, - }, - "userInfo": {"apiKey": TL_KEY[index], "userId": str(user_id)}, - } - elif img_url: - req = { - "reqType": 1, - "perception": { - "inputImage": {"url": img_url}, - "selfInfo": { - "location": { - "city": "陨石坑", - "province": "火星", - "street": "第5坑位", - } - }, - }, - "userInfo": {"apiKey": TL_KEY[index], "userId": str(user_id)}, - } - except IndexError: - index = 0 - return None - text = "" - response = await AsyncHttpx.post(url, json=req) - if response.status_code != 200: - return None - resp_payload = json.loads(response.text) - if int(resp_payload["intent"]["code"]) in [4003]: - return None - if resp_payload["results"]: - for result in resp_payload["results"]: - if result["resultType"] == "text": - text = result["values"]["text"] - if "请求次数超过" in text: - text = "" - return text - - -# 屑 AI -async def xie_ai(text: str) -> str: - """获取青云客回复 - - 参数: - text: 问题 - - 返回: - str: 青云可回复 - """ - res = await AsyncHttpx.get( - f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}" - ) - content = "" - try: - data = json.loads(res.text) - if data["result"] == 0: - content = data["content"] - if "菲菲" in content: - content = content.replace("菲菲", NICKNAME) - if "艳儿" in content: - content = content.replace("艳儿", NICKNAME) - if "公众号" in content: - content = "" - if "{br}" in content: - content = content.replace("{br}", "\n") - if "提示" in content: - content = content[: content.find("提示")] - if "淘宝" in content or "taobao.com" in content: - return "" - while True: - r = re.search("{face:(.*)}", content) - if r: - id_ = r.group(1) - content = content.replace("{" + f"face:{id_}" + "}", "") - else: - break - return ( - content - if not content and not Config.get_config("ai", "ALAPI_AI_CHECK") - else await check_text(content) - ) - except Exception as e: - logger.error(f"Ai xie_ai 发生错误", e=e) - return "" - - -def hello() -> UniMessage: - """一些打招呼的内容""" - result = random.choice( - ( - "哦豁?!", - "你好!Ov<", - f"库库库,呼唤{NICKNAME}做什么呢", - "我在呢!", - "呼呼,叫俺干嘛", - ) - ) - img = random.choice(os.listdir(IMAGE_PATH / "zai")) - return MessageUtils.build_message([IMAGE_PATH / "zai" / img, result]) - - -def no_result() -> UniMessage: - """ - 没有回答时的回复 - """ - return MessageUtils.build_message( - [ - random.choice( - [ - "你在说啥子?", - f"纯洁的{NICKNAME}没听懂", - "下次再告诉你(下次一定)", - "你觉得我听懂了吗?嗯?", - "我!不!知!道!", - ] - ), - IMAGE_PATH - / "noresult" - / random.choice(os.listdir(IMAGE_PATH / "noresult")), - ] - ) - - -async def check_text(text: str) -> str: - """ALAPI文本检测,主要针对青云客API,检测为恶俗文本改为无回复的回答 - - 参数: - text: 回复 - - 返回: - str: 检测文本 - """ - if not Config.get_config("alapi", "ALAPI_TOKEN"): - return text - params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} - try: - data = (await AsyncHttpx.get(check_url, timeout=2, params=params)).json() - if data["code"] == 200: - if data["data"]["conclusion_type"] == 2: - return "" - except Exception as e: - logger.error(f"检测违规文本错误...", e=e) - return text diff --git a/zhenxun/plugins/ai/utils.py b/zhenxun/plugins/ai/utils.py deleted file mode 100644 index 946bc234..00000000 --- a/zhenxun/plugins/ai/utils.py +++ /dev/null @@ -1,153 +0,0 @@ -import random -import time - -from zhenxun.configs.config import NICKNAME -from zhenxun.models.ban_console import BanConsole - - -class AiMessageManager: - def __init__(self): - self._data = {} - self._same_message = [ - "为什么要发一样的话?", - "请不要再重复对我说一句话了,不然我就要生气了!", - "别再发这句话了,我已经知道了...", - "你是只会说这一句话吗?", - "[*],你发我也发!", - "[uname],[*]", - f"救命!有笨蛋一直给{NICKNAME}发一样的话!", - "这句话你已经给我发了{}次了,再发就生气!", - ] - self._repeat_message = [ - f"请不要学{NICKNAME}说话", - f"为什么要一直学{NICKNAME}说话?", - "你再学!你再学我就生气了!", - f"呜呜,你是想欺负{NICKNAME}嘛..", - "[uname]不要再学我说话了!", - "再学我说话,我就把你拉进黑名单(生气", - "你再学![uname]是个笨蛋!", - "你已经学我说话{}次了!别再学了!", - ] - - def add_message(self, user_id: str, message: str): - """添加用户消息 - - 参数: - user_id: 用户id - message: 消息内容 - """ - if message: - if self._data.get(user_id) is None: - self._data[user_id] = { - "time": time.time(), - "message": [], - "result": [], - "repeat_count": 0, - } - if time.time() - self._data[user_id]["time"] > 60 * 10: - self._data[user_id]["message"].clear() - self._data[user_id]["time"] = time.time() - self._data[user_id]["message"].append(message.strip()) - - def add_result(self, user_id: str, message: str): - """添加回复用户的消息 - - 参数: - user_id: 用户id - message: 回复消息内容 - """ - if message: - if self._data.get(user_id) is None: - self._data[user_id] = { - "time": time.time(), - "message": [], - "result": [], - "repeat_count": 0, - } - if time.time() - self._data[user_id]["time"] > 60 * 10: - self._data[user_id]["result"].clear() - self._data[user_id]["repeat_count"] = 0 - self._data[user_id]["time"] = time.time() - self._data[user_id]["result"].append(message.strip()) - - async def get_result(self, user_id: str, nickname: str) -> str | None: - """特殊消息特殊回复 - - 参数: - user_id: 用户id - nickname: 用户昵称 - - 返回: - str | None: 回答 - """ - try: - if len(self._data[user_id]["message"]) < 2: - return None - except KeyError: - return None - msg = await self._get_user_repeat_message_result(user_id) - if not msg: - msg = await self._get_user_same_message_result(user_id) - if msg: - if "[uname]" in msg: - msg = msg.replace("[uname]", nickname) - if not msg.startswith("生气了!你好烦,闭嘴!") and "[*]" in msg: - msg = msg.replace("[*]", self._data[user_id]["message"][-1]) - return msg - - async def _get_user_same_message_result(self, user_id: str) -> str | None: - """重复消息回复 - - 参数: - user_id: 用户id - - 返回: - str | None: 回答 - """ - msg = self._data[user_id]["message"][-1] - cnt = 0 - _tmp = self._data[user_id]["message"][:-1] - _tmp.reverse() - for s in _tmp: - if s == msg: - cnt += 1 - else: - break - if cnt > 1: - if random.random() < 0.5 and cnt > 3: - rand = random.randint(60, 300) - await BanConsole.ban(user_id, None, 9, rand, None) - self._data[user_id]["message"].clear() - return f"生气了!你好烦,闭嘴!给我老实安静{rand}秒" - return random.choice(self._same_message).format(cnt) - return None - - async def _get_user_repeat_message_result(self, user_id: str) -> str | None: - """复读真寻的消息回复 - - 参数: - user_id: 用户id - - 返回: - str | None: 回答 - """ - msg = self._data[user_id]["message"][-1] - if self._data[user_id]["result"]: - rst = self._data[user_id]["result"][-1] - else: - return None - if msg == rst: - self._data[user_id]["repeat_count"] += 1 - cnt = self._data[user_id]["repeat_count"] - if cnt > 1: - if random.random() < 0.5 and cnt > 3: - rand = random.randint(60, 300) - await BanConsole.ban(user_id, None, 9, rand, None) - self._data[user_id]["result"].clear() - self._data[user_id]["repeat_count"] = 0 - return f"生气了!你好烦,闭嘴!给我老实安静{rand}秒" - return random.choice(self._repeat_message).format(cnt) - return None - - -ai_message_manager = AiMessageManager() diff --git a/zhenxun/plugins/alapi/__init__.py b/zhenxun/plugins/alapi/__init__.py deleted file mode 100644 index 3efe4113..00000000 --- a/zhenxun/plugins/alapi/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path - -import nonebot - -from zhenxun.configs.config import Config - -Config.add_plugin_config( - "alapi", - "ALAPI_TOKEN", - None, - help="在https://admin.alapi.cn/user/login登录后获取token", -) - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/alapi/_data_source.py b/zhenxun/plugins/alapi/_data_source.py deleted file mode 100644 index 61037ab9..00000000 --- a/zhenxun/plugins/alapi/_data_source.py +++ /dev/null @@ -1,29 +0,0 @@ -from zhenxun.configs.config import Config -from zhenxun.utils.http_utils import AsyncHttpx - - -async def get_data(url: str, params: dict | None = None) -> tuple[dict | str, int]: - """获取ALAPI数据 - - 参数: - url: 请求链接 - params: 参数 - - 返回: - tuple[dict | str, int]: 返回信息 - """ - if not params: - params = {} - params["token"] = Config.get_config("alapi", "ALAPI_TOKEN") - try: - data = (await AsyncHttpx.get(url, params=params, timeout=5)).json() - if data["code"] == 200: - if not data["data"]: - return "没有搜索到...", 997 - return data, 200 - else: - if data["code"] == 101: - return "缺失ALAPI TOKEN,请在配置文件中填写!", 999 - return f'发生了错误...code:{data["code"]}', 999 - except TimeoutError: - return "超时了....", 998 diff --git a/zhenxun/plugins/alapi/comments_163.py b/zhenxun/plugins/alapi/comments_163.py deleted file mode 100644 index d05a5aa9..00000000 --- a/zhenxun/plugins/alapi/comments_163.py +++ /dev/null @@ -1,57 +0,0 @@ -from nonebot import on_regex -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -comments_163_url = "https://v2.alapi.cn/api/comment" - -__plugin_meta__ = PluginMetadata( - name="网易云热评", - description="生了个人,我很抱歉", - usage=""" - 到点了,还是防不了下塔 - 指令: - 网易云热评/到点了/12点了 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("网易云热评"), - priority=5, - block=True, -) - -_matcher.shortcut( - "(到点了|12点了)", - command="网易云热评", - arguments=[], - prefix=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - data, code = await get_data(comments_163_url) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - data = data["data"] # type: ignore - comment = data["comment_content"] # type: ignore - song_name = data["title"] # type: ignore - await MessageUtils.build_message(f"{comment}\n\t——《{song_name}》").send( - reply_to=True - ) - logger.info( - f" 发送网易云热评: {comment} \n\t\t————{song_name}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/alapi/cover.py b/zhenxun/plugins/alapi/cover.py deleted file mode 100644 index e26bf79c..00000000 --- a/zhenxun/plugins/alapi/cover.py +++ /dev/null @@ -1,46 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Image, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -cover_url = "https://v2.alapi.cn/api/bilibili/cover" - -__plugin_meta__ = PluginMetadata( - name="b封面", - description="快捷的b站视频封面获取方式", - usage=""" - b封面 [链接/av/bv/cv/直播id] - 示例:b封面 av86863038 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", menu_type="一些工具" - ).dict(), -) - -_matcher = on_alconna( - Alconna("b封面", Args["url", str]), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma, url: str): - params = {"c": url} - data, code = await get_data(cover_url, params) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - data = data["data"] # type: ignore - title = data["title"] # type: ignore - img = data["cover"] # type: ignore - await MessageUtils.build_message([f"title:{title}\n", Image(url=img)]).send( - reply_to=True - ) - logger.info( - f" 获取b站封面: {title} url:{img}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/alapi/jitang.py b/zhenxun/plugins/alapi/jitang.py deleted file mode 100644 index 63dc93e2..00000000 --- a/zhenxun/plugins/alapi/jitang.py +++ /dev/null @@ -1,48 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -url = "https://v2.alapi.cn/api/soul" - -__plugin_meta__ = PluginMetadata( - name="鸡汤", - description="喏,亲手为你煮的鸡汤", - usage=""" - 不喝点什么感觉有点不舒服 - 指令: - 鸡汤 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("鸡汤"), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - try: - data, code = await get_data(url) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - await MessageUtils.build_message(data["data"]["content"]).send(reply_to=True) # type: ignore - logger.info( - f" 发送鸡汤:" + data["data"]["content"], # type:ignore - arparma.header_result, - session=session, - ) - except Exception as e: - await MessageUtils.build_message("鸡汤煮坏掉了...").send() - logger.error(f"鸡汤煮坏掉了", e=e) diff --git a/zhenxun/plugins/alapi/poetry.py b/zhenxun/plugins/alapi/poetry.py deleted file mode 100644 index 4d359498..00000000 --- a/zhenxun/plugins/alapi/poetry.py +++ /dev/null @@ -1,57 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -__plugin_meta__ = PluginMetadata( - name="古诗", - description="为什么突然文艺起来了!", - usage=""" - 平白无故念首诗 - 示例:念诗/来首诗/念首诗 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("念诗"), - priority=5, - block=True, -) - -_matcher.shortcut( - "(来首诗|念首诗)", - command="念诗", - arguments=[], - prefix=True, -) - - -poetry_url = "https://v2.alapi.cn/api/shici" - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - data, code = await get_data(poetry_url) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - data = data["data"] # type: ignore - content = data["content"] # type: ignore - title = data["origin"] # type: ignore - author = data["author"] # type: ignore - await MessageUtils.build_message(f"{content}\n\t——{author}《{title}》").send( - reply_to=True - ) - logger.info( - f" 发送古诗: f'{content}\n\t--{author}《{title}》'", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/black_word/__init__.py b/zhenxun/plugins/black_word/__init__.py deleted file mode 100644 index eb35e275..00000000 --- a/zhenxun/plugins/black_word/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/black_word/black_watch.py b/zhenxun/plugins/black_word/black_watch.py deleted file mode 100644 index 133352a6..00000000 --- a/zhenxun/plugins/black_word/black_watch.py +++ /dev/null @@ -1,62 +0,0 @@ -from nonebot.adapters import Bot, Event -from nonebot.matcher import Matcher -from nonebot.message import run_preprocessor -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.ban_console import BanConsole -from zhenxun.models.group_console import GroupConsole -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType - -from .utils import black_word_manager - -__plugin_meta__ = PluginMetadata( - name="敏感词文本监听", - description="敏感词文本监听", - usage="".strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - ).dict(), -) - -base_config = Config.get("black_word") - - -# 黑名单词汇检测 -@run_preprocessor -async def _( - bot: Bot, message: UniMsg, matcher: Matcher, event: Event, session: EventSession -): - gid = session.id3 or session.id2 - if session.id1: - if ( - event.is_tome() - and matcher.plugin_name == "black_word" - and not await BanConsole.is_ban(session.id1, gid) - ): - msg = message.extract_plain_text() - if session.id1 in bot.config.superusers: - return logger.debug( - f"超级用户跳过黑名单词汇检查 Message: {msg}", target=session.id1 - ) - if gid: - """屏蔽群权限-1的群""" - group, _ = await GroupConsole.get_or_create( - group_id=gid, channel_id__isnull=True - ) - if group.level < 0: - return - if await BanConsole.is_ban(None, gid): - """屏蔽群被ban的群""" - return - if await black_word_manager.check(bot, session, msg) and base_config.get( - "CONTAIN_BLACK_STOP_PROPAGATION" - ): - matcher.stop_propagation() diff --git a/zhenxun/plugins/black_word/black_word.py b/zhenxun/plugins/black_word/black_word.py deleted file mode 100644 index 8b819c6a..00000000 --- a/zhenxun/plugins/black_word/black_word.py +++ /dev/null @@ -1,235 +0,0 @@ -from datetime import datetime -from typing import List - -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from .data_source import set_user_punish, show_black_text_image - -__plugin_meta__ = PluginMetadata( - name="敏感词检测", - description="请注意你的发言!", - usage=""" - 惩罚机制: 检测内容提示 - 设置惩罚 [uid] [id] [level]: 设置惩罚内容, 此id需要通过`记录名单 -u:uid`来获取 - 记录名单: 查看检测记录名单 - 记录名单: - -u [uid] 指定用户记录名单 - -g [gid] 指定群组记录名单 - -d [date] 指定日期 - -dt ['=', '>', '<'] 大于小于等于指定日期 - - 示例: - 设置惩罚 123123123 0 1 - 记录名单 -u 123123123 - 记录名单 -g 333333 - 记录名单 -d 2022-11-11 - 记录名单 -d 2022-11-11 -dt > - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.SUPERUSER, - menu_type="其他", - configs=[ - RegisterConfig( - key="CYCLE_DAYS", - value=30, - help="黑名单词汇记录周期", - default_value=30, - type=int, - ), - RegisterConfig( - key="TOLERATE_COUNT", - value=[5, 1, 1, 1, 1], - help="各个级别惩罚的容忍次数, 依次为: 1, 2, 3, 4, 5", - default_value=[5, 1, 1, 1, 1], - type=List[int], - ), - RegisterConfig( - key="AUTO_PUNISH", - value=True, - help="是否启动自动惩罚机制", - default_value=True, - type=bool, - ), - RegisterConfig( - key="BAN_4_DURATION", - value=360, - help="Ban时长(分钟),四级惩罚,可以为指定数字或指定列表区间(随机),例如 [30, 360]", - default_value=360, - type=int, - ), - RegisterConfig( - key="BAN_3_DURATION", - value=7, - help="Ban时长(天),三级惩罚,可以为指定数字或指定列表区间(随机),例如 [7, 30]", - default_value=7, - type=int, - ), - RegisterConfig( - key="WARNING_RESULT", - value=f"请注意对{NICKNAME}的发言内容", - help="口头警告内容", - default_value=None, - ), - RegisterConfig( - key="AUTO_ADD_PUNISH_LEVEL", - value=360, - help="自动提级机制,当周期内处罚次数大于某一特定值就提升惩罚等级", - default_value=360, - type=int, - ), - RegisterConfig( - key="ADD_PUNISH_LEVEL_TO_COUNT", - value=3, - help="在CYCLE_DAYS周期内触发指定惩罚次数后提升惩罚等级", - default_value=3, - type=int, - ), - RegisterConfig( - key="ALAPI_CHECK_FLAG", - value=False, - help="当未检测到已收录的敏感词时,开启ALAPI文本检测并将疑似文本发送给超级用户", - default_value=False, - type=bool, - ), - RegisterConfig( - key="CONTAIN_BLACK_STOP_PROPAGATION", - value=True, - help="当文本包含任意敏感词时,停止向下级插件传递,即不触发ai", - default_value=True, - type=bool, - ), - ], - ).dict(), -) - - -_punish_matcher = on_alconna( - Alconna("设置惩罚", Args["uid", str]["id", int]["punish_level", int]), - priority=1, - permission=SUPERUSER, - block=True, -) - - -_show_matcher = on_alconna( - Alconna( - "记录名单", - Option("-u|--uid", Args["uid", str]), - Option("-g|--group", Args["gid", str]), - Option("-d|--date", Args["date", str]), - Option("-dt|--type", Args["date_type", ["=", ">", "<"]], default="="), - ), - priority=1, - permission=SUPERUSER, - block=True, -) - -_show_punish_matcher = on_alconna( - Alconna("惩罚机制"), aliases={"敏感词检测"}, priority=1, block=True -) - - -@_show_matcher.handle() -async def _( - bot: Bot, uid: Match[str], gid: Match[str], date: Match[str], date_type: Match[str] -): - user_id = None - group_id = None - date_ = None - date_str = None - date_type_ = "=" - if uid.available: - user_id = uid.result - if gid.available: - group_id = gid.result - if date.available: - date_str = date.result - if date_type.available: - date_type_ = date_type.result - if date_str: - try: - date_ = datetime.strptime(date_str, "%Y-%m-%d") - except ValueError: - await MessageUtils.build_message("日期格式错误,需要:年-月-日").finish() - result = await show_black_text_image( - user_id, - group_id, - date_, - date_type_, - ) - await MessageUtils.build_message(result).send() - - -@_show_punish_matcher.handle() -async def _(): - text = f""" - ** 惩罚机制 ** - - 惩罚前包含容忍机制,在指定周期内会容忍偶尔少次数的敏感词只会进行警告提醒 - - 多次触发同级惩罚会使惩罚等级提高,即惩罚自动提级机制 - - 目前公开的惩罚等级: - - 1级:永久ban - - 2级:删除好友 - - 3级:ban指定/随机天数 - - 4级:ban指定/随机时长 - - 5级:警告 - - 备注: - - 该功能为测试阶段,如果你有被误封情况,请联系管理员,会从数据库中提取出你的数据进行审核后判断 - - 目前该功能暂不完善,部分情况会由管理员鉴定,请注意对真寻的发言 - - 关于敏感词: - - 记住不要骂{NICKNAME}就对了! - """.strip() - max_width = 0 - for m in text.split("\n"): - max_width = len(m) * 20 if len(m) * 20 > max_width else max_width - max_height = len(text.split("\n")) * 24 - A = BuildImage( - max_width, max_height, font="CJGaoDeGuo.otf", font_size=24, color="#E3DBD1" - ) - await A.text((10, 10), text) - await MessageUtils.build_message(A).send() - - -@_punish_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - uid: str, - id: int, - punish_level: int, -): - result = await set_user_punish( - bot, uid, session.id2 or session.id3, id, punish_level - ) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"设置惩罚 uid:{uid} id_:{id} punish_level:{punish_level} --> {result}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/black_word/data_source.py b/zhenxun/plugins/black_word/data_source.py deleted file mode 100644 index e985facc..00000000 --- a/zhenxun/plugins/black_word/data_source.py +++ /dev/null @@ -1,103 +0,0 @@ -from datetime import datetime - -from nonebot.adapters import Bot - -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.utils.image_utils import BuildImage, ImageTemplate - -from .model import BlackWord -from .utils import Config, _get_punish - - -async def show_black_text_image( - user_id: str | None, - group_id: str | None, - date: datetime | None, - data_type: str = "=", -) -> BuildImage: - """展示记录名单 - - 参数: - bot: bot - user: 用户id - group_id: 群组id - date: 日期 - data_type: 日期搜索类型 - - 返回: - BuildImage: 数据图片 - """ - data_list = await BlackWord.get_black_data(user_id, group_id, date, data_type) - column_name = [ - "ID", - "昵称", - "UID", - "GID", - "文本", - "检测内容", - "检测等级", - "惩罚", - "平台", - "记录日期", - ] - column_list = [] - uid_list = [u for u in data_list] - uid2name = { - u.user_id: u.user_name for u in await FriendUser.filter(user_id__in=uid_list) - } - for i, data in enumerate(data_list): - uname = uid2name.get(data.user_id) - if not uname: - if u := await GroupInfoUser.get_or_none( - user_id=data.user_id, group_id=data.group_id - ): - uname = u.user_name - if len(data.plant_text) > 30: - data.plant_text = data.plant_text[:30] + "..." - column_list.append( - [ - i, - uname or data.user_id, - data.user_id, - data.group_id, - data.plant_text, - data.black_word, - data.punish_level, - data.punish, - data.platform, - data.create_time, - ] - ) - A = await ImageTemplate.table_page( - "记录名单", "一个都不放过!", column_name, column_list - ) - return A - - -async def set_user_punish( - bot: Bot, user_id: str, group_id: str | None, id_: int, punish_level: int -) -> str: - """设置惩罚 - - 参数: - user_id: 用户id - group_id: 群组id或频道id - id_: 记录下标 - punish_level: 惩罚等级 - - 返回: - str: 结果 - """ - result = await _get_punish(bot, punish_level, user_id, group_id) - punish = { - 1: "永久ban", - 2: "删除好友", - 3: f"ban {result} 天", - 4: f"ban {result} 分钟", - 5: "口头警告", - } - if await BlackWord.set_user_punish(user_id, punish[punish_level], id_=id_): - return f"已对 USER {user_id} 进行 {punish[punish_level]} 处罚。" - else: - return "操作失败,可能未找到用户,id或敏感词" diff --git a/zhenxun/plugins/black_word/model.py b/zhenxun/plugins/black_word/model.py deleted file mode 100644 index ef81c0ba..00000000 --- a/zhenxun/plugins/black_word/model.py +++ /dev/null @@ -1,154 +0,0 @@ -from datetime import datetime, timedelta -from email.policy import default - -import pytz -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class BlackWord(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255, null=True) - """群聊id""" - plant_text = fields.TextField() - """检测文本""" - black_word = fields.TextField() - """黑名单词语""" - punish = fields.TextField(default="") - """惩罚内容""" - punish_level = fields.IntField() - """惩罚等级""" - create_time = fields.DatetimeField(auto_now_add=True) - """创建时间""" - platform = fields.CharField(255, null=True) - """平台""" - - class Meta: - table = "black_word" - table_description = "惩罚机制数据表" - - @classmethod - async def set_user_punish( - cls, - user_id: str, - punish: str, - black_word: str | None = None, - id_: int | None = None, - ) -> bool: - """设置处罚 - - 参数: - user_id: 用户id - punish: 处罚 - black_word: 黑名单词汇 - id_: 记录下标 - """ - user = None - if (not black_word and id_ is None) or not punish: - return False - if black_word: - user = ( - await cls.filter(user_id=user_id, black_word=black_word, punish="") - .order_by("id") - .first() - ) - elif id_ is not None: - user_list = await cls.filter(user_id=user_id).order_by("id").all() - if len(user_list) == 0 or (id_ < 0 or id_ > len(user_list)): - return False - user = user_list[id_] - if not user: - return False - user.punish = f"{user.punish}{punish} " - await user.save(update_fields=["punish"]) - return True - - @classmethod - async def get_user_count( - cls, user_id: str, days: int = 7, punish_level: int | None = None - ) -> int: - """获取用户规定周期内的犯事次数 - - 参数: - user_id: 用户id - days: 周期天数 - punish_level: 惩罚等级 - """ - query = cls.filter( - user_id=user_id, - create_time__gte=datetime.now() - timedelta(days=days), - punish_level__not_in=[-1], - ) - if punish_level is not None: - query = query.filter(punish_level=punish_level) - return await query.count() - - @classmethod - async def get_user_punish_level(cls, user_id: str, days: int = 7) -> int | None: - """获取用户最近一次的惩罚记录等级 - - 参数: - user_id: 用户id - days: 周期天数 - """ - if ( - user := await cls.filter( - user_id=user_id, - create_time__gte=datetime.now() - timedelta(days=days), - ) - .order_by("id") - .first() - ): - return user.punish_level - return None - - @classmethod - async def get_black_data( - cls, - user_id: str | None, - group_id: str | None, - date: datetime | None, - date_type: str = "=", - ) -> list["BlackWord"]: - """通过指定条件查询数据 - - 参数: - user_id: 用户id - group_id: 群号 - date: 日期 - date_type: 日期查询类型 - """ - query = cls - if user_id: - query = query.filter(user_id=user_id) - if group_id: - query = query.filter(group_id=group_id) - if date: - if date_type == "=": - query = query.filter( - create_time__range=[date, date + timedelta(days=1)] - ) - elif date_type == ">": - query = query.filter(create_time__gte=date) - elif date_type == "<": - query = query.filter(create_time__lte=date) - data_list = await query.all().order_by("id") - for data in data_list: - data.create_time = data.create_time.astimezone( - pytz.timezone("Asia/Shanghai") - ) - return data_list # type: ignore - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE black_word RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE black_word ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE black_word ALTER COLUMN group_id TYPE character varying(255);", - "ALTER TABLE black_word ADD COLUMN platform character varying(255);", - ] diff --git a/zhenxun/plugins/black_word/utils.py b/zhenxun/plugins/black_word/utils.py deleted file mode 100644 index 53526bd0..00000000 --- a/zhenxun/plugins/black_word/utils.py +++ /dev/null @@ -1,374 +0,0 @@ -import random -from pathlib import Path - -import ujson as json -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import ActionFailed -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.models.ban_console import BanConsole -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.utils import cn2py - -from .model import BlackWord - - -class BlackWordManager: - """ - 敏感词管理( 拒绝恶意 - """ - - def __init__(self, word_file: Path, py_file: Path): - self._word_list = { - "1": [], - "2": [], - "3": [], - "4": ["sb", "nmsl", "mdzz", "2b", "jb", "操", "废物", "憨憨", "cnm", "rnm"], - "5": [], - } - self._py_list = { - "1": [], - "2": [], - "3": [], - "4": [ - "shabi", - "wocaonima", - "sima", - "sabi", - "zhizhang", - "naocan", - "caonima", - "rinima", - "simadongxi", - "simawanyi", - "hanbi", - "hanpi", - "laji", - "fw", - ], - "5": [], - } - word_file.parent.mkdir(parents=True, exist_ok=True) - if word_file.exists(): - # 清空默认配置 - with open(word_file, "r", encoding="utf8") as f: - self._word_list = json.load(f) - else: - with open(word_file, "w", encoding="utf8") as f: - json.dump( - self._word_list, - f, - ensure_ascii=False, - indent=4, - ) - if py_file.exists(): - # 清空默认配置 - with open(py_file, "r", encoding="utf8") as f: - self._py_list = json.load(f) - else: - with open(py_file, "w", encoding="utf8") as f: - json.dump( - self._py_list, - f, - ensure_ascii=False, - indent=4, - ) - - async def check( - self, bot: Bot, session: EventSession, message: str - ) -> str | bool | None: - """检查是否包含黑名单词汇 - - 参数: - bot: Bot - session: EventSession - message: 消息 - """ - logger.debug( - f"检查文本是否含有黑名单词汇: {message}", "敏感词检测", session=session - ) - if session.id1: - if data := self._check(message): - if data[0]: - await _add_user_black_word( - bot, - session.id1, - session.id2 or session.id3, - data[0], - message, - int(data[1]), - ) - return True - if Config.get_config( - "black_word", "ALAPI_CHECK_FLAG" - ) and not await check_text(message): - await send_msg( - bot, - "", - None, - f"用户 {session.id1} 群组 {session.id3 or session.id2} ALAPI 疑似检测:{message}", - ) - return False - - def _check(self, message: str) -> tuple[str | None, int]: - """检测文本是否违规 - - 参数: - message: 检测消息 - """ - # 移除空格 - message = message.replace(" ", "") - py_msg = cn2py(message).lower() - # 完全匹配 - for x in [self._word_list, self._py_list]: - for level in x: - if message in x[level] or py_msg in x[level]: - return message if message in x[level] else py_msg, int(level) - # 模糊匹配 - for x in [self._word_list, self._py_list]: - for level in x: - for m in x[level]: - if m in message or m in py_msg: - return m, -1 - return None, 0 - - -async def _add_user_black_word( - bot: Bot, - user_id: str, - group_id: str | None, - black_word: str, - message: str, - punish_level: int, -): - """添加敏感词数据 - - 参数: - bot: Bot - user_id: 用户id - group_id: 群组id或频道id - black_word: 触发的黑名单词汇 - message: 原始文本 - punish_level: 惩罚等级 - """ - cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7 - user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level) - add_punish_level_to_count = Config.get_config( - "black_word", "ADD_PUNISH_LEVEL_TO_COUNT" - ) - # 周期内超过次数直接提升惩罚 - if ( - Config.get_config("black_word", "AUTO_ADD_PUNISH_LEVEL") - and add_punish_level_to_count - ): - punish_level -= 1 - await BlackWord.create( - user_id=user_id, - group_id=group_id, - plant_text=message, - black_word=black_word, - punish_level=punish_level, - platform=PlatformUtils.get_platform(bot), - ) - logger.info( - f"已将 USER {user_id} GROUP {group_id} 添加至黑名单词汇记录 Black_word:{black_word} Plant_text:{message}" - ) - # 自动惩罚 - if Config.get_config("black_word", "AUTO_PUNISH") and punish_level != -1: - await _punish_handle(bot, user_id, group_id, punish_level, black_word) - - -async def _punish_handle( - bot: Bot, - user_id: str, - group_id: str | None, - punish_level: int, - black_word: str, -): - """惩罚措施,级别越低惩罚越严 - - 参数: - bot: Bot - user_id: 用户id - group_id: 群组id或频道id - black_word: 触发的黑名单词汇 - channel_id: 频道id - """ - logger.info(f"BlackWord USER {user_id} 触发 {punish_level} 级惩罚...") - # 周期天数 - cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7 - # 用户周期内触发punish_level级惩罚的次数 - user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level) - # 获取最近一次的惩罚等级,将在此基础上增加 - punish_level = ( - await BlackWord.get_user_punish_level(user_id, cycle_days) or punish_level - ) - # 容忍次数:List[int] - tolerate_count = Config.get_config("black_word", "TOLERATE_COUNT") - if not tolerate_count or len(tolerate_count) < 5: - tolerate_count = [5, 2, 2, 2, 2] - if punish_level == 1 and user_count > tolerate_count[punish_level - 1]: - # 永久ban - await _get_punish(bot, 1, user_id, group_id) - await BlackWord.set_user_punish(user_id, "永久ban 删除好友", black_word) - elif punish_level == 2 and user_count > tolerate_count[punish_level - 1]: - # 删除好友 - await _get_punish(bot, 2, user_id, group_id) - await BlackWord.set_user_punish(user_id, "删除好友", black_word) - elif punish_level == 3 and user_count > tolerate_count[punish_level - 1]: - # 永久ban - ban_day = await _get_punish(bot, 3, user_id, group_id) - await BlackWord.set_user_punish(user_id, f"ban {ban_day} 天", black_word) - elif punish_level == 4 and user_count > tolerate_count[punish_level - 1]: - # ban指定时长 - ban_time = await _get_punish(bot, 4, user_id, group_id) - await BlackWord.set_user_punish(user_id, f"ban {ban_time} 分钟", black_word) - elif punish_level == 5 and user_count > tolerate_count[punish_level - 1]: - # 口头警告 - warning_result = await _get_punish(bot, 5, user_id, group_id) - await BlackWord.set_user_punish( - user_id, f"口头警告:{warning_result}", black_word - ) - else: - await BlackWord.set_user_punish(user_id, f"提示!", black_word) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker:该条发言已被记录,目前你在{cycle_days}天内的发表{punish_level}级" - f"言论记录次数为:{user_count}次,请注意你的发言\n" - f"* 如果你不清楚惩罚机制,请发送“惩罚机制” *", - ) - - -async def _get_punish( - bot: Bot, - id_: int, - user_id: str, - group_id: str | None = None, -) -> int | str | None: - """通过id_获取惩罚 - - 参数: - bot: Bot - id_: id - user_id: 用户id - group_id: 群组id或频道id - """ - # 忽略的群聊 - # _ignore_group = Config.get_config("black_word", "IGNORE_GROUP") - # 处罚 id 4 ban 时间:int,List[int] - ban_3_duration = Config.get_config("black_word", "BAN_3_DURATION") or 7 - # 处罚 id 4 ban 时间:int,List[int] - ban_4_duration = Config.get_config("black_word", "BAN_4_DURATION") or 360 - # 口头警告内容 - warning_result = Config.get_config("black_word", "WARNING_RESULT") - if user := await GroupInfoUser.get_or_none(user_id=user_id, group_id=group_id): - uname = user.user_name - else: - uname = user_id - # 永久ban - if id_ == 1: - if str(user_id) not in bot.config.superusers: - await BanConsole.ban(user_id, group_id, 10, -1, None) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 永久ban USER {uname}({user_id})", - ) - logger.info(f"BlackWord 永久封禁 USER {user_id}...") - # 删除好友(有的话 - elif id_ == 2: - if str(user_id) not in bot.config.superusers: - try: - await bot.delete_friend(user_id=user_id) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 删除好友 USER {uname}({user_id})", - ) - logger.info(f"BlackWord 删除好友 {user_id}...") - except ActionFailed: - pass - # 封禁用户指定时间,默认7天 - elif id_ == 3: - if isinstance(ban_3_duration, list): - ban_3_duration = random.randint(ban_3_duration[0], ban_3_duration[1]) - await BanConsole.ban(user_id, group_id, 9, ban_4_duration * 60 * 24) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_3_duration} 天处罚。", - ) - logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_3_duration} 天...") - return ban_3_duration - # 封禁用户指定时间,默认360分钟 - elif id_ == 4: - if isinstance(ban_4_duration, list): - ban_4_duration = random.randint(ban_4_duration[0], ban_4_duration[1]) - await BanConsole.ban(user_id, group_id, 9, ban_4_duration * 60) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_4_duration} 分钟处罚。", - ) - logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_4_duration} 分钟...") - return ban_4_duration - # 口头警告 - elif id_ == 5: - await PlatformUtils.send_message(bot, user_id, group_id, warning_result) - logger.info(f"BlackWord 口头警告 USER {user_id}") - return warning_result - return None - - -async def send_msg(bot: Bot, user_id: str, group_id: str | None, message: str): - """发送消息 - - 参数: - bot: Bot - user_id: user_id - group_id: group_id - message: message - """ - if not user_id: - platform = PlatformUtils.get_platform(bot) - user_id = bot.config.platform_superusers[platform][0] - await PlatformUtils.send_message(bot, user_id, group_id, message) - - -async def check_text(text: str) -> bool: - """ALAPI文本检测,检测输入违规 - - 参数: - text: 回复 - """ - if not Config.get_config("alapi", "ALAPI_TOKEN"): - return True - params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} - try: - data = ( - await AsyncHttpx.get( - "https://v2.alapi.cn/api/censor/text", timeout=4, params=params - ) - ).json() - if data["code"] == 200: - return data["data"]["conclusion_type"] == 2 - except Exception as e: - logger.error(f"检测违规文本错误...", e=e) - return True - - -black_word_manager = BlackWordManager( - DATA_PATH / "black_word" / "black_word.json", - DATA_PATH / "black_word" / "black_py.json", -) diff --git a/zhenxun/plugins/bt/__init__.py b/zhenxun/plugins/bt/__init__.py deleted file mode 100644 index 3aff4f8b..00000000 --- a/zhenxun/plugins/bt/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from httpx import ConnectTimeout -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_private - -from .data_source import get_bt_info - -__plugin_meta__ = PluginMetadata( - name="磁力搜索", - description="bt(磁力搜索)[仅支持私聊,懂的都懂]", - usage=""" - * 拒绝反冲斗士! * - 指令: - bt [关键词] ?[页数] - 示例: bt 钢铁侠 - 示例: bt 钢铁侠 3 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - configs=[ - RegisterConfig( - key="BT_MAX_NUM", - value=10, - help="单次BT搜索返回最大消息数量", - default_value=10, - type=int, - ), - ], - ).dict(), -) - - -_matcher = on_alconna( - Alconna("bt", Args["keyword", str]["page?", int]), - rule=ensure_private, - priority=5, - block=True, -) - - -@_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - keyword: str, - page: Match[int], -): - send_flag = False - try: - async for title, type_, create_time, file_size, link in get_bt_info( - keyword, page.result if page.available else 1 - ): - await MessageUtils.build_message( - f"标题:{title}\n" - f"类型:{type_}\n" - f"创建时间:{create_time}\n" - f"文件大小:{file_size}\n" - f"种子:{link}" - ).send() - send_flag = True - except (TimeoutError, ConnectTimeout): - await MessageUtils.build_message(f"搜索 {keyword} 超时...").finish() - except Exception as e: - logger.error(f"bt 错误", arparma.header_result, session=session, e=e) - await MessageUtils.build_message(f"bt 其他未知错误..").finish() - if not send_flag: - await MessageUtils.build_message(f"{keyword} 未搜索到...").send() - logger.info( - f"BT搜索 {keyword} 第 {page} 页", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/bt/data_source.py b/zhenxun/plugins/bt/data_source.py deleted file mode 100644 index ad02a5d5..00000000 --- a/zhenxun/plugins/bt/data_source.py +++ /dev/null @@ -1,54 +0,0 @@ -from bs4 import BeautifulSoup - -from zhenxun.configs.config import Config -from zhenxun.utils.http_utils import AsyncHttpx, AsyncPlaywright - -url = "http://www.eclzz.ink" - - -async def get_bt_info(keyword: str, page: int): - """获取资源信息 - - 参数: - keyword: 关键词 - page: 页数 - """ - global url - text = (await AsyncHttpx.get(f"{url}/s/{keyword}_rel_{page}.html", timeout=30)).text - if "301 Moved Permanently" in text: - async with AsyncPlaywright.new_page() as _page: - await _page.goto(url) - url = _page.url - text = ( - await AsyncHttpx.get(f"{url}/s/{keyword}_rel_{page}.html", timeout=30) - ).text - if "大约0条结果" in text: - return - soup = BeautifulSoup(text, "lxml") - item_lst = soup.find_all("div", {"class": "search-item"}) - bt_max_num = Config.get_config("bt", "BT_MAX_NUM") or 10 - bt_max_num = bt_max_num if bt_max_num < len(item_lst) else len(item_lst) - for item in item_lst[:bt_max_num]: - divs = item.find_all("div") - title = ( - str(divs[0].find("a").text).replace("", "").replace("", "").strip() - ) - spans = divs[2].find_all("span") - type_ = spans[0].text - create_time = spans[1].find("b").text - file_size = spans[2].find("b").text - link = await get_download_link(divs[0].find("a")["href"]) - yield title, type_, create_time, file_size, link - - -async def get_download_link(_url: str) -> str | None: - """获取资源下载地址 - - 参数: - _url: 链接 - """ - text = (await AsyncHttpx.get(f"{url}{_url}")).text - soup = BeautifulSoup(text, "lxml") - if fd := soup.find("a", {"id": "down-url"}): - return fd["href"] # type: ignore - return None diff --git a/zhenxun/plugins/check/__init__.py b/zhenxun/plugins/check/__init__.py deleted file mode 100644 index ee63b50d..00000000 --- a/zhenxun/plugins/check/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from .data_source import Check - -__plugin_meta__ = PluginMetadata( - name="服务器自我检查", - description="查看服务器当前状态", - usage=""" - 查看服务器当前状态 - 指令: - 自检 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -check = Check() - - -_matcher = on_alconna( - Alconna("自检"), rule=to_me(), permission=SUPERUSER, block=True, priority=1 -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - image = await check.show() - await MessageUtils.build_message(image).send() - logger.info("自检", arparma.header_result, session=session) diff --git a/zhenxun/plugins/check/data_source.py b/zhenxun/plugins/check/data_source.py deleted file mode 100644 index 78fbe7ba..00000000 --- a/zhenxun/plugins/check/data_source.py +++ /dev/null @@ -1,83 +0,0 @@ -import asyncio -import time -from datetime import datetime - -import psutil - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage - - -class Check: - def __init__(self): - self.cpu = None - self.memory = None - self.disk = None - self.user = None - self.baidu = 200 - self.google = 200 - - async def check_all(self): - await self.check_network() - await asyncio.sleep(0.1) - self.check_system() - self.check_user() - - def check_system(self): - self.cpu = psutil.cpu_percent() - self.memory = psutil.virtual_memory().percent - self.disk = psutil.disk_usage("/").percent - - async def check_network(self): - try: - await AsyncHttpx.get("https://www.baidu.com/", timeout=5) - except Exception as e: - logger.warning(f"访问BaiDu失败... {type(e)}: {e}") - self.baidu = 404 - try: - await AsyncHttpx.get("https://www.google.com/", timeout=5) - except Exception as e: - logger.warning(f"访问Google失败... {type(e)}: {e}") - self.google = 404 - - def check_user(self): - result = "" - for user in psutil.users(): - result += f'[{user.name}] {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user.started))}\n' - self.user = result[:-1] - - async def show(self) -> BuildImage: - await self.check_all() - font = BuildImage.load_font(font_size=24) - result = ( - f'[Time] {str(datetime.now()).split(".")[0]}\n' - f"-----System-----\n" - f"[CPU] {self.cpu}%\n" - f"[Memory] {self.memory}%\n" - f"[Disk] {self.disk}%\n" - f"-----Network-----\n" - f"[BaiDu] {self.baidu}\n" - f"[Google] {self.google}\n" - ) - if self.user: - result += "-----User-----\n" + self.user - width = 0 - height = 0 - for x in result.split("\n"): - w, h = BuildImage.get_text_size(x, font) - if w > width: - width = w - height += 30 - A = BuildImage(width + 50, height + 10, font_size=24) - await A.transparent(1) - await A.text((10, 10), result) - max_width = max(width, height) - bk = BuildImage( - max_width + 100, - max_width + 100, - background=IMAGE_PATH / "background" / "check" / "0.jpg", - ) - await bk.paste(A, center_type="center") - return bk diff --git a/zhenxun/plugins/coser.py b/zhenxun/plugins/coser.py deleted file mode 100644 index 90062d51..00000000 --- a/zhenxun/plugins/coser.py +++ /dev/null @@ -1,90 +0,0 @@ -import time -from typing import Tuple - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.withdraw_manage import WithdrawManager - -__plugin_meta__ = PluginMetadata( - name="coser", - description="三次元也不戳,嘿嘿嘿", - usage=""" - ?N连cos/coser - 示例: cos - 示例: 5连cos (单次请求张数小于9) - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - configs=[ - RegisterConfig( - key="WITHDRAW_COS_MESSAGE", - value=(0, 1), - help="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], - ), - ], - ).dict(), -) - -_matcher = on_alconna(Alconna("get-cos", Args["num", int, 1]), priority=5, block=True) - -_matcher.shortcut( - r"cos", - command="get-cos", - arguments=["1"], - prefix=True, -) - -_matcher.shortcut( - r"(?P\d)(张|个|条|连)cos", - command="get-cos", - arguments=["{num}"], - prefix=True, -) - - -# 纯cos,较慢:https://picture.yinux.workers.dev -# 比较杂,有福利姬,较快:https://api.jrsgslb.cn/cos/url.php?return=img -url = "https://picture.yinux.workers.dev" - - -@_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - num: int, -): - withdraw_time = Config.get_config("coser", "WITHDRAW_COS_MESSAGE") - for _ in range(num): - path = TEMP_PATH / f"cos_cc{int(time.time())}.jpeg" - try: - await AsyncHttpx.download_file(url, path) - receipt = await MessageUtils.build_message(path).send() - message_id = receipt.msg_ids[0]["message_id"] - if message_id and WithdrawManager.check(session, withdraw_time): - WithdrawManager.append( - bot, - message_id, - withdraw_time[0], - ) - logger.info(f"发送cos", arparma.header_result, session=session) - except Exception as e: - await MessageUtils.build_message("你cos给我看!").send() - logger.error( - f"cos错误", - arparma.header_result, - session=session, - e=e, - ) diff --git a/zhenxun/plugins/dialogue/__init__.py b/zhenxun/plugins/dialogue/__init__.py deleted file mode 100644 index 5524cf9a..00000000 --- a/zhenxun/plugins/dialogue/__init__.py +++ /dev/null @@ -1,161 +0,0 @@ -import nonebot -from nonebot import on_command -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Target, Text, UniMsg -from nonebot_plugin_session import EventSession -from nonebot_plugin_userinfo import EventUserInfo, UserInfo - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.group_console import GroupConsole -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from ._data_source import DialogueManage - -__plugin_meta__ = PluginMetadata( - name="联系管理员", - description="跨越空间与时间跟管理员对话", - usage=""" - 滴滴滴- ?[文本] ?[图片] - 示例:滴滴滴- 我喜欢你 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="联系管理员", - superuser_help=""" - /t: 查看当前存储的消息 - /t [user_id] [group_id] [文本]: 在group回复指定用户 - /t [user_id] [文本]: 私聊用户 - /t -1 [group_id] [文本]: 在group内发送消息 - /t [id] [文本]: 回复指定id的对话,id在 /t 中获取 - 示例:/t 73747222 32848432 你好啊 - 示例:/t 73747222 你好不好 - 示例:/t -1 32848432 我不太好 - 示例:/t 0 我收到你的话了 - """.strip(), - ).dict(), -) - -config = nonebot.get_driver().config - - -_dialogue_matcher = on_command("滴滴滴-", priority=5, block=True) -_reply_matcher = on_command("/t", priority=1, permission=SUPERUSER, block=True) - - -@_dialogue_matcher.handle() -async def _( - bot: Bot, - message: UniMsg, - session: EventSession, - user_info: UserInfo = EventUserInfo(), -): - if session.id1: - message[0] = Text(str(message[0]).replace("滴滴滴-", "", 1)) - platform = PlatformUtils.get_platform(bot) - try: - superuser_id = config.platform_superusers["qq"][0] - if platform == "dodo": - superuser_id = config.platform_superusers["dodo"][0] - if platform == "kaiheila": - superuser_id = config.platform_superusers["kaiheila"][0] - if platform == "discord": - superuser_id = config.platform_superusers["discord"][0] - except IndexError: - await MessageUtils.build_message("管理员失联啦...").finish() - if not superuser_id: - await MessageUtils.build_message("管理员失联啦...").finish() - uname = user_info.user_displayname or user_info.user_name - group_name = "" - gid = session.id3 or session.id2 - if gid: - if g := await GroupConsole.get(group_id=gid): - group_name = g.group_name - logger.info( - f"发送消息至{platform}管理员: {message}", "滴滴滴-", session=session - ) - message.insert(0, Text("消息:\n")) - if gid: - message.insert(0, Text(f"群组: {group_name}({gid})\n")) - message.insert(0, Text(f"昵称: {uname}({session.id1})\n")) - message.insert(0, Text(f"Id: {DialogueManage._index}\n")) - message.insert(0, Text("*****一份交流报告*****\n")) - DialogueManage.add(uname, session.id1, gid, group_name, message, platform) - await message.send(bot=bot, target=Target(superuser_id, private=True)) - await MessageUtils.build_message("已成功发送给管理员啦!").send(reply_to=True) - else: - await MessageUtils.build_message("用户id为空...").send() - - -@_reply_matcher.handle() -async def _( - bot: Bot, - message: UniMsg, - session: EventSession, -): - message[0] = Text(str(message[0]).replace("/t", "", 1).strip()) - if session.id1: - msg = message.extract_plain_text() - if not msg: - platform = PlatformUtils.get_platform(bot) - data = DialogueManage._data - if not data: - await MessageUtils.build_message("暂无待回复消息...").finish() - if platform: - data = [data[d] for d in data if data[d].platform == platform] - for d in data: - await d.message.send( - bot=bot, target=Target(session.id1, private=True) - ) - else: - msg = msg.split() - group_id = "" - user_id = "" - if msg[0].replace("-", "", 1).isdigit(): - if len(msg[0]) < 4: - _id = int(msg[0]) - if _id >= 0: - if model := DialogueManage.get(_id): - user_id = model.user_id - group_id = model.group_id - else: - return MessageUtils.build_message("未获取此id数据").finish() - message[0] = Text(" ".join(str(message[0]).split(" ")[1:])) - else: - user_id = 0 - if msg[1].isdigit(): - group_id = msg[1] - message[0] = Text(" ".join(str(message[0]).split(" ")[2:])) - else: - await MessageUtils.build_message("群组id错误...").finish( - at_sender=True - ) - DialogueManage.remove(_id) - else: - user_id = msg[0] - if msg[1].isdigit() and len(msg[1]) > 5: - group_id = msg[1] - message[0] = Text(" ".join(str(message[0]).split(" ")[2:])) - else: - group_id = 0 - message[0] = Text(" ".join(str(message[0]).split(" ")[1:])) - else: - await MessageUtils.build_message("参数错误...").finish(at_sender=True) - if group_id: - if user_id: - message.insert(0, alcAt("user", user_id)) - message.insert(1, Text("\n管理员回复\n=======\n")) - await message.send(Target(group_id), bot) - await MessageUtils.build_message("消息发送成功!").finish(at_sender=True) - elif user_id: - await message.send(Target(user_id, private=True), bot) - await MessageUtils.build_message("消息发送成功!").finish(at_sender=True) - else: - await MessageUtils.build_message("群组id与用户id为空...").send() - else: - await MessageUtils.build_message("用户id为空...").send() diff --git a/zhenxun/plugins/dialogue/_data_source.py b/zhenxun/plugins/dialogue/_data_source.py deleted file mode 100644 index 440c8176..00000000 --- a/zhenxun/plugins/dialogue/_data_source.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Dict - -from nonebot_plugin_alconna import UniMsg -from pydantic import BaseModel - - -class DialogueData(BaseModel): - - name: str - """用户名称""" - user_id: str - """用户id""" - group_id: str | None - """群组id""" - group_name: str | None - """群组名称""" - message: UniMsg - """UniMsg""" - platform: str | None - """平台""" - - -class DialogueManage: - - _data: Dict[int, DialogueData] = {} - _index = 0 - - @classmethod - def add( - cls, - name: str, - uid: str, - gid: str | None, - group_name: str | None, - message: UniMsg, - platform: str | None, - ): - cls._data[cls._index] = DialogueData( - name=name, - user_id=uid, - group_id=gid, - group_name=group_name, - message=message, - platform=platform, - ) - cls._index += 1 - - @classmethod - def remove(cls, index: int): - if index in cls._data: - del cls._data[index] - - @classmethod - def get(cls, k: int): - return cls._data.get(k) diff --git a/zhenxun/plugins/draw_card/__init__.py b/zhenxun/plugins/draw_card/__init__.py deleted file mode 100644 index 2aeeb30e..00000000 --- a/zhenxun/plugins/draw_card/__init__.py +++ /dev/null @@ -1,293 +0,0 @@ -import asyncio -import traceback -from dataclasses import dataclass -from typing import Any - -import nonebot -from cn2an import cn2an -from nonebot import on_keyword, on_message, on_regex -from nonebot.log import logger -from nonebot.matcher import Matcher -from nonebot.params import RegexGroup -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.typing import T_Handler -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.utils.message import MessageUtils - -from .handles.azur_handle import AzurHandle -from .handles.ba_handle import BaHandle -from .handles.base_handle import BaseHandle -from .handles.fgo_handle import FgoHandle -from .handles.genshin_handle import GenshinHandle -from .handles.guardian_handle import GuardianHandle -from .handles.onmyoji_handle import OnmyojiHandle -from .handles.pcr_handle import PcrHandle -from .handles.pretty_handle import PrettyHandle -from .handles.prts_handle import PrtsHandle -from .rule import rule - -__plugin_meta__ = PluginMetadata( - name="游戏抽卡", - description="就算是模拟抽卡也不能改变自己是个非酋", - usage=""" - usage: - 模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师,碧蓝档案进行抽卡 - 指令: - 原神[1-180]抽: 原神常驻池 - 原神角色[1-180]抽: 原神角色UP池子 - 原神角色2池[1-180]抽: 原神角色UP池子 - 原神武器[1-180]抽: 原神武器UP池子 - 重置原神抽卡: 清空当前卡池的抽卡次数[即从0开始计算UP概率] - 方舟[1-300]抽: 方舟卡池,当有当期UP时指向UP池 - 赛马娘[1-200]抽: 赛马娘卡池,当有当期UP时指向UP池 - 坎公骑冠剑[1-300]抽: 坎公骑冠剑卡池,当有当期UP时指向UP池 - pcr/公主连接[1-300]抽: 公主连接卡池 - 碧蓝航线/碧蓝[重型/轻型/特型/活动][1-300]抽: 碧蓝航线重型/轻型/特型/活动卡池 - fgo[1-300]抽: fgo卡池 (已失效) - 阴阳师[1-300]抽: 阴阳师卡池 - ba/碧蓝档案[1-200]抽:碧蓝档案卡池 - * 以上指令可以通过 XX一井 来指定最大抽取数量 * - * 示例:原神一井 * - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="抽卡相关", - superuser_help=""" - 更新方舟信息 - 重载方舟卡池 - 更新原神信息 - 重载原神卡池 - 更新赛马娘信息 - 重载赛马娘卡池 - 更新坎公骑冠剑信息 - 更新碧蓝航线信息 - 更新fgo信息 - 更新阴阳师信息 - """, - ).dict(), -) - -_hidden = on_message(rule=lambda: False) - - -@dataclass -class Game: - keywords: set[str] - handle: BaseHandle - flag: bool - config_name: str - max_count: int = 300 # 一次最大抽卡数 - reload_time: int | None = None # 重载UP池时间(小时) - has_other_pool: bool = False - - -games = ( - Game( - {"azur", "碧蓝航线"}, - AzurHandle(), - Config.get_config("draw_card", "AZUR_FLAG", True), - "AZUR_FLAG", - ), - Game( - {"fgo", "命运冠位指定"}, - FgoHandle(), - Config.get_config("draw_card", "FGO_FLAG", True), - "FGO_FLAG", - ), - Game( - {"genshin", "原神"}, - GenshinHandle(), - Config.get_config("draw_card", "GENSHIN_FLAG", True), - "GENSHIN_FLAG", - max_count=180, - reload_time=18, - has_other_pool=True, - ), - Game( - {"guardian", "坎公骑冠剑"}, - GuardianHandle(), - Config.get_config("draw_card", "GUARDIAN_FLAG", True), - "GUARDIAN_FLAG", - reload_time=4, - ), - Game( - {"onmyoji", "阴阳师"}, - OnmyojiHandle(), - Config.get_config("draw_card", "ONMYOJI_FLAG", True), - "ONMYOJI_FLAG", - ), - Game( - {"pcr", "公主连结", "公主连接", "公主链接", "公主焊接"}, - PcrHandle(), - Config.get_config("draw_card", "PCR_FLAG", True), - "PCR_FLAG", - ), - Game( - {"pretty", "马娘", "赛马娘"}, - PrettyHandle(), - Config.get_config("draw_card", "PRETTY_FLAG", True), - "PRETTY_FLAG", - max_count=200, - reload_time=4, - ), - Game( - {"prts", "方舟", "明日方舟"}, - PrtsHandle(), - Config.get_config("draw_card", "PRTS_FLAG", True), - "PRTS_FLAG", - reload_time=4, - ), - Game( - {"ba", "碧蓝档案"}, - BaHandle(), - Config.get_config("draw_card", "BA_FLAG", True), - "BA_FLAG", - ), -) - - -def create_matchers(): - def draw_handler(game: Game) -> T_Handler: - async def handler( - session: EventSession, - args: tuple[Any, ...] = RegexGroup(), - ): - pool_name, pool_type_, num, unit = args - if num == "单": - num = 1 - else: - try: - num = int(cn2an(num, mode="smart")) - except ValueError: - await MessageUtils.build_message("必!须!是!数!字!").finish( - reply_to=True - ) - if unit == "井": - num *= game.max_count - if num < 1: - await MessageUtils.build_message("虚空抽卡???").finish(reply_to=True) - elif num > game.max_count: - await MessageUtils.build_message( - "一井都满不足不了你嘛!快爬开!" - ).finish(reply_to=True) - pool_name = ( - pool_name.replace("池", "") - .replace("武器", "arms") - .replace("角色", "char") - .replace("卡牌", "card") - .replace("卡", "card") - ) - try: - if pool_type_ in ["2池", "二池"]: - pool_name = pool_name + "1" - res = await game.handle.draw( - num, pool_name=pool_name, user_id=session.id1 - ) - logger.info( - f"游戏抽卡 类型: {list(game.keywords)[1]} 卡池: {pool_name} 数量: {num}", - "游戏抽卡", - session=session, - ) - except: - logger.warning(traceback.format_exc()) - await MessageUtils.build_message("出错了...").finish(reply_to=True) - await res.send() - - return handler - - def update_handler(game: Game) -> T_Handler: - async def handler(matcher: Matcher): - await game.handle.update_info() - await matcher.finish("更新完成!") - - return handler - - def reload_handler(game: Game) -> T_Handler: - async def handler(matcher: Matcher): - res = await game.handle.reload_pool() - if res: - await res.send() - - return handler - - def reset_handler(game: Game) -> T_Handler: - async def handler(matcher: Matcher, session: EventSession): - if not session.id1: - await MessageUtils.build_message("获取用户id失败...").finish() - if game.handle.reset_count(session.id1): - await MessageUtils.build_message("重置成功!").send() - - return handler - - def scheduled_job(game: Game) -> T_Handler: - async def handler(): - await game.handle.reload_pool() - - return handler - - for game in games: - pool_pattern = r"([^\s单0-9零一二三四五六七八九百十]{0,3})" - num_pattern = r"(单|[0-9零一二三四五六七八九百十]{1,3})" - unit_pattern = r"([抽|井|连])" - pool_type = "()" - if game.has_other_pool: - pool_type = r"([2二]池)?" - draw_regex = r".*?(?:{})\s*{}\s*{}\s*{}\s*{}".format( - "|".join(game.keywords), pool_pattern, pool_type, num_pattern, unit_pattern - ) - update_keywords = {f"更新{keyword}信息" for keyword in game.keywords} - reload_keywords = {f"重载{keyword}卡池" for keyword in game.keywords} - reset_keywords = {f"重置{keyword}抽卡" for keyword in game.keywords} - on_regex(draw_regex, priority=5, block=True, rule=rule(game)).append_handler( - draw_handler(game) - ) - on_keyword( - update_keywords, priority=1, block=True, permission=SUPERUSER - ).append_handler(update_handler(game)) - on_keyword( - reload_keywords, priority=1, block=True, permission=SUPERUSER - ).append_handler(reload_handler(game)) - on_keyword(reset_keywords, priority=5, block=True).append_handler( - reset_handler(game) - ) - if game.reload_time: - scheduler.add_job( - scheduled_job(game), trigger="cron", hour=game.reload_time, minute=1 - ) - - -create_matchers() - - -# 更新资源 -@scheduler.scheduled_job( - "cron", - hour=4, - minute=1, -) -async def _(): - tasks = [] - for game in games: - if game.flag: - tasks.append(asyncio.ensure_future(game.handle.update_info())) - await asyncio.gather(*tasks) - - -driver = nonebot.get_driver() - - -@driver.on_startup -async def _(): - tasks = [] - for game in games: - if game.flag: - game.handle.init_data() - if not game.handle.data_exists(): - tasks.append(asyncio.ensure_future(game.handle.update_info())) - await asyncio.gather(*tasks) diff --git a/zhenxun/plugins/draw_card/config.py b/zhenxun/plugins/draw_card/config.py deleted file mode 100644 index 0aff3ef8..00000000 --- a/zhenxun/plugins/draw_card/config.py +++ /dev/null @@ -1,203 +0,0 @@ -import nonebot -import ujson as json -from pydantic import BaseModel, Extra, ValidationError - -from zhenxun.configs.config import Config as AConfig -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH -from zhenxun.services.log import logger - - -# 原神 -class GenshinConfig(BaseModel, extra=Extra.ignore): - GENSHIN_FIVE_P: float = 0.006 - GENSHIN_FOUR_P: float = 0.051 - GENSHIN_THREE_P: float = 0.43 - GENSHIN_G_FIVE_P: float = 0.016 - GENSHIN_G_FOUR_P: float = 0.13 - I72_ADD: float = 0.0585 - - -# 明日方舟 -class PrtsConfig(BaseModel, extra=Extra.ignore): - PRTS_SIX_P: float = 0.02 - PRTS_FIVE_P: float = 0.08 - PRTS_FOUR_P: float = 0.48 - PRTS_THREE_P: float = 0.42 - - -# 赛马娘 -class PrettyConfig(BaseModel, extra=Extra.ignore): - PRETTY_THREE_P: float = 0.03 - PRETTY_TWO_P: float = 0.18 - PRETTY_ONE_P: float = 0.79 - - -# 坎公骑冠剑 -class GuardianConfig(BaseModel, extra=Extra.ignore): - GUARDIAN_THREE_CHAR_P: float = 0.0275 - GUARDIAN_TWO_CHAR_P: float = 0.19 - GUARDIAN_ONE_CHAR_P: float = 0.7825 - GUARDIAN_THREE_CHAR_UP_P: float = 0.01375 - GUARDIAN_THREE_CHAR_OTHER_P: float = 0.01375 - GUARDIAN_EXCLUSIVE_ARMS_P: float = 0.03 - GUARDIAN_FIVE_ARMS_P: float = 0.03 - GUARDIAN_FOUR_ARMS_P: float = 0.09 - GUARDIAN_THREE_ARMS_P: float = 0.27 - GUARDIAN_TWO_ARMS_P: float = 0.58 - GUARDIAN_EXCLUSIVE_ARMS_UP_P: float = 0.01 - GUARDIAN_EXCLUSIVE_ARMS_OTHER_P: float = 0.02 - - -# 公主连结 -class PcrConfig(BaseModel, extra=Extra.ignore): - PCR_THREE_P: float = 0.025 - PCR_TWO_P: float = 0.18 - PCR_ONE_P: float = 0.795 - PCR_G_THREE_P: float = 0.025 - PCR_G_TWO_P: float = 0.975 - - -# 碧蓝航线 -class AzurConfig(BaseModel, extra=Extra.ignore): - AZUR_FIVE_P: float = 0.012 - AZUR_FOUR_P: float = 0.07 - AZUR_THREE_P: float = 0.12 - AZUR_TWO_P: float = 0.51 - AZUR_ONE_P: float = 0.3 - - -# 命运-冠位指定 -class FgoConfig(BaseModel, extra=Extra.ignore): - FGO_SERVANT_FIVE_P: float = 0.01 - FGO_SERVANT_FOUR_P: float = 0.03 - FGO_SERVANT_THREE_P: float = 0.4 - FGO_CARD_FIVE_P: float = 0.04 - FGO_CARD_FOUR_P: float = 0.12 - FGO_CARD_THREE_P: float = 0.4 - - -# 阴阳师 -class OnmyojiConfig(BaseModel, extra=Extra.ignore): - ONMYOJI_SP: float = 0.0025 - ONMYOJI_SSR: float = 0.01 - ONMYOJI_SR: float = 0.2 - ONMYOJI_R: float = 0.7875 - - -# 碧蓝档案 -class BaConfig(BaseModel, extra=Extra.ignore): - BA_THREE_P: float = 0.025 - BA_TWO_P: float = 0.185 - BA_ONE_P: float = 0.79 - BA_G_TWO_P: float = 0.975 - - -class Config(BaseModel, extra=Extra.ignore): - # 开关 - PRTS_FLAG: bool = True - GENSHIN_FLAG: bool = True - PRETTY_FLAG: bool = True - GUARDIAN_FLAG: bool = True - PCR_FLAG: bool = True - AZUR_FLAG: bool = True - FGO_FLAG: bool = True - ONMYOJI_FLAG: bool = True - BA_FLAG: bool = True - - # 其他配置 - PCR_TAI: bool = False - SEMAPHORE: int = 5 - - # 抽卡概率 - prts: PrtsConfig = PrtsConfig() - genshin: GenshinConfig = GenshinConfig() - pretty: PrettyConfig = PrettyConfig() - guardian: GuardianConfig = GuardianConfig() - pcr: PcrConfig = PcrConfig() - azur: AzurConfig = AzurConfig() - fgo: FgoConfig = FgoConfig() - onmyoji: OnmyojiConfig = OnmyojiConfig() - ba: BaConfig = BaConfig() - - -driver = nonebot.get_driver() - -# DRAW_PATH = Path("data/draw_card").absolute() -DRAW_PATH = IMAGE_PATH / "draw_card" -# try: -# DRAW_PATH = Path(global_config.draw_path).absolute() -# except: -# pass -config_path = DATA_PATH / "draw_card" / "draw_card_config" / "draw_card_config.json" - -draw_config: Config = Config() - -for game_flag, game_name in zip( - [ - "PRTS_FLAG", - "GENSHIN_FLAG", - "PRETTY_FLAG", - "GUARDIAN_FLAG", - "PCR_FLAG", - "AZUR_FLAG", - "FGO_FLAG", - "ONMYOJI_FLAG", - "PCR_TAI", - "BA_FLAG", - ], - [ - "明日方舟", - "原神", - "赛马娘", - "坎公骑冠剑", - "公主连结", - "碧蓝航线", - "命运-冠位指定(FGO)", - "阴阳师", - "pcr台服卡池", - "碧蓝档案", - ], -): - AConfig.add_plugin_config( - "draw_card", - game_flag, - True, - help=f"{game_name} 抽卡开关", - default_value=True, - type=bool, - ) -AConfig.add_plugin_config( - "draw_card", - "SEMAPHORE", - 5, - help=f"异步数据下载数量限制", - default_value=5, - type=int, -) -AConfig.set_name("draw_card", "游戏抽卡") - - -@driver.on_startup -def check_config(): - global draw_config - - if not config_path.exists(): - config_path.parent.mkdir(parents=True, exist_ok=True) - draw_config = Config() - logger.warning("draw_card:配置文件不存在,已重新生成配置文件...") - else: - with config_path.open("r", encoding="utf8") as fp: - data = json.load(fp) - try: - draw_config = Config.parse_obj({**data}) - except ValidationError: - draw_config = Config() - logger.warning("draw_card:配置文件格式错误,已重新生成配置文件...") - - with config_path.open("w", encoding="utf8") as fp: - json.dump( - draw_config.dict(), - fp, - indent=4, - ensure_ascii=False, - ) diff --git a/zhenxun/plugins/draw_card/count_manager.py b/zhenxun/plugins/draw_card/count_manager.py deleted file mode 100644 index 7768b057..00000000 --- a/zhenxun/plugins/draw_card/count_manager.py +++ /dev/null @@ -1,149 +0,0 @@ -from typing import Optional, TypeVar, Generic -from pydantic import BaseModel -from cachetools import TTLCache - - -class BaseUserCount(BaseModel): - count: int = 0 # 当前抽卡次数 - - -TCount = TypeVar("TCount", bound="BaseUserCount") - - -class DrawCountManager(Generic[TCount]): - """ - 抽卡统计保底 - """ - - def __init__( - self, game_draw_count_rule: tuple, star2name: tuple, max_draw_count: int - ): - """ - 初始化保底统计 - - 例如:DrawCountManager((10, 90, 180), ("4", "5", "5")) - - 抽卡保底需要的次数和返回的对应名称,例如星级等 - - :param game_draw_count_rule:抽卡规则 - :param star2name:星级对应的名称 - :param max_draw_count:最大累计抽卡次数,当下次单次抽卡超过该次数时将会清空数据 - - """ - # 只有保底 - # 超过60秒重置抽卡次数 - self._data: TTLCache[int, TCount] = TTLCache(maxsize=1000, ttl=60) - self._guarantee_tuple = game_draw_count_rule - self._star2name = star2name - self._max_draw_count = max_draw_count - - @classmethod - def get_count_class(cls) -> TCount: - raise NotImplementedError - - def _get_count(self, key: int) -> TCount: - if self._data.get(key) is None: - self._data[key] = self.get_count_class() - else: - self._data[key] = self._data[key] - return self._data[key] - - def increase(self, key: int, value: int = 1): - """ - 用户抽卡次数加1 - """ - self._get_count(key).count += value - - def get_max_guarantee(self): - """ - 获取最大保底抽卡次数 - """ - return self._guarantee_tuple[-1] - - def get_user_count(self, key: int) -> int: - """ - 获取当前抽卡次数 - """ - return self._get_count(key).count - - def reset(self, key: int): - """ - 清空记录 - """ - self._data.pop(key, None) - - -class GenshinUserCount(BaseUserCount): - five_index: int = 0 # 获取五星时的抽卡次数 - four_index: int = 0 # 获取四星时的抽卡次数 - is_up: bool = False # 下次五星是否必定为up - - -class GenshinCountManager(DrawCountManager[GenshinUserCount]): - @classmethod - def get_count_class(cls) -> GenshinUserCount: - return GenshinUserCount() - - def set_is_up(self, key: int, value: bool): - """ - 设置下次是否必定up - """ - self._get_count(key).is_up = value - - def is_up(self, key: int) -> bool: - """ - 判断该次保底是否必定为up - """ - return self._get_count(key).is_up - - def get_user_five_index(self, key: int) -> int: - """ - 获取用户上次获取五星的次数 - """ - return self._get_count(key).five_index - - def get_user_four_index(self, key: int) -> int: - """ - 获取用户上次获取四星的次数 - """ - return self._get_count(key).four_index - - def mark_five_index(self, key: int): - """ - 标记用户该次次数为五星 - """ - self._get_count(key).five_index = self._get_count(key).count - - def mark_four_index(self, key: int): - """ - 标记用户该次次数为四星 - """ - self._get_count(key).four_index = self._get_count(key).count - - def check_count(self, key: int, count: int): - """ - 检查用户该次抽卡次数累计是否超过最大限制次数 - """ - if self._get_count(key).count + count > self._max_draw_count: - self._data.pop(key, None) - - def get_user_guarantee_count(self, key: int) -> int: - user = self._get_count(key) - return ( - self.get_max_guarantee() - - (user.count % self.get_max_guarantee() - user.five_index) - ) % self.get_max_guarantee() or self.get_max_guarantee() - - def check(self, key: int) -> Optional[int]: - """ - 是否保底 - """ - # print(self._data) - user = self._get_count(key) - if user.count - user.five_index == 90: - user.five_index = user.count - return 5 - if user.count - user.four_index == 10: - user.four_index = user.count - return 4 - return None diff --git a/zhenxun/plugins/draw_card/handles/azur_handle.py b/zhenxun/plugins/draw_card/handles/azur_handle.py deleted file mode 100644 index 67242a77..00000000 --- a/zhenxun/plugins/draw_card/handles/azur_handle.py +++ /dev/null @@ -1,308 +0,0 @@ -import random -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle -from .base_handle import UpChar as _UpChar -from .base_handle import UpEvent as _UpEvent - - -class AzurChar(BaseData): - type_: str # 舰娘类型 - - @property - def star_str(self) -> str: - return ["白", "蓝", "紫", "金"][self.star - 1] - - -class UpChar(_UpChar): - type_: str # 舰娘类型 - - -class UpEvent(_UpEvent): - up_char: list[UpChar] # up对象 - - -class AzurHandle(BaseHandle[AzurChar]): - def __init__(self): - super().__init__("azur", "碧蓝航线") - self.max_star = 4 - self.config = draw_config.azur - self.ALL_CHAR: list[AzurChar] = [] - self.UP_EVENT: UpEvent | None = None - - def get_card(self, pool_name: str, **kwargs) -> AzurChar: - if pool_name == "轻型": - type_ = ["驱逐", "轻巡", "维修"] - elif pool_name == "重型": - type_ = ["重巡", "战列", "战巡", "重炮"] - else: - type_ = ["维修", "潜艇", "重巡", "轻航", "航母"] - up_pool_flag = pool_name == "活动" - # Up - up_ship = ( - [x for x in self.UP_EVENT.up_char if x.zoom > 0] if self.UP_EVENT else [] - ) - # print(up_ship) - acquire_char = None - if up_ship and up_pool_flag: - up_zoom: list[tuple[float, float]] = [(0, up_ship[0].zoom / 100)] - # 初始化概率 - cur_ = up_ship[0].zoom / 100 - for i in range(len(up_ship)): - try: - up_zoom.append((cur_, cur_ + up_ship[i + 1].zoom / 100)) - cur_ += up_ship[i + 1].zoom / 100 - except IndexError: - pass - rand = random.random() - # 抽取up - for i, zoom in enumerate(up_zoom): - if zoom[0] <= rand <= zoom[1]: - try: - acquire_char = [ - x for x in self.ALL_CHAR if x.name == up_ship[i].name - ][0] - except IndexError: - pass - # 没有up或者未抽取到up - if not acquire_char: - star = self.get_star( - # [4, 3, 2, 1], - [4, 3, 2, 2], - [ - self.config.AZUR_FOUR_P, - self.config.AZUR_THREE_P, - self.config.AZUR_TWO_P, - self.config.AZUR_ONE_P, - ], - ) - acquire_char = random.choice( - [ - x - for x in self.ALL_CHAR - if x.star == star and x.type_ in type_ and not x.limited - ] - ) - return acquire_char - - async def draw(self, count: int, **kwargs) -> UniMessage: - index2card = self.get_cards(count, **kwargs) - cards = [card[0] for card in index2card] - up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else [] - result = self.format_result(index2card, **{**kwargs, "up_list": up_list}) - gen_img = await self.generate_img(cards) - return MessageUtils.build_message([gen_img.pic2bytes(), result]) - - async def generate_card_img(self, card: AzurChar) -> BuildImage: - sep_w = 5 - sep_t = 5 - sep_b = 20 - w = 100 - h = 100 - bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b) - frame_path = str(self.img_path / f"{card.star}_star.png") - frame = BuildImage(w, h, background=frame_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(w, h, background=img_path) - # 加圆角 - await frame.circle_corner(6) - await img.circle_corner(6) - await bg.paste(img, (sep_w, sep_t)) - await bg.paste(frame, (sep_w, sep_t)) - # 加名字 - text = card.name[:6] + "..." if len(card.name) > 7 else card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2), - text, - font=font, - fill=["#808080", "#3b8bff", "#8000ff", "#c90", "#ee494c"][card.star - 1], - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - AzurChar( - name=value["名称"], - star=int(value["星级"]), - limited="可以建造" not in value["获取途径"], - type_=value["类型"], - ) - for value in self.load_data().values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_EVENT = UpEvent.parse_obj(data.get("char", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_EVENT: - data = {"char": json.loads(self.UP_EVENT.json())} - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - info = {} - # 更新图鉴 - url = "https://wiki.biligame.com/blhx/舰娘图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - contents = dom.xpath( - "//div[@class='mw-body-content mw-content-ltr']/div[@class='mw-parser-output']" - ) - for index, content in enumerate(contents): - char_list = content.xpath("./div[@id='CardSelectTr']/div") - for char in char_list: - try: - name = char.xpath("./span/a/text()")[0] - frame = char.xpath("./div/div/a/img/@alt")[0] - avatar = char.xpath("./div/img/@srcset")[0] - except IndexError: - continue - member_dict = { - "名称": remove_prohibited_str(name), - "头像": unquote(str(avatar).split(" ")[-2]), - "星级": self.parse_star(frame), - "类型": char.xpath("./@data-param1")[0].split(",")[-1], - } - info[member_dict["名称"]] = member_dict - # 更新额外信息 - for key in info.keys(): - # TODO: 各种舰娘·改获取错误 - url = f"https://wiki.biligame.com/blhx/{key}" - result = await self.get_url(url) - if not result: - info[key]["获取途径"] = [] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - time = dom.xpath( - "//table[@class='wikitable sv-general']/tbody[1]/tr[4]/td[2]//text()" - )[0] - sources = [] - if "无法建造" in time: - sources.append("无法建造") - elif "活动已关闭" in time: - sources.append("活动限定") - else: - sources.append("可以建造") - info[key]["获取途径"] = sources - except IndexError: - info[key]["获取途径"] = [] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载头像框 - idx = 1 - BLHX_URL = "https://patchwiki.biligame.com/images/blhx" - for url in [ - "/1/15/pxho13xsnkyb546tftvh49etzdh74cf.png", - "/a/a9/k8t7nx6c8pan5vyr8z21txp45jxeo66.png", - "/a/a5/5whkzvt200zwhhx0h0iz9qo1kldnidj.png", - "/a/a2/ptog1j220x5q02hytpwc8al7f229qk9.png", - "/6/6d/qqv5oy3xs40d3055cco6bsm0j4k4gzk.png", - ]: - await self.download_img(BLHX_URL + url, f"{idx}_star") - idx += 1 - await self.update_up_char() - - @staticmethod - def parse_star(star: str) -> int: - if star in ["舰娘头像外框普通.png", "舰娘头像外框白色.png"]: - return 1 - elif star in ["舰娘头像外框稀有.png", "舰娘头像外框蓝色.png"]: - return 2 - elif star in ["舰娘头像外框精锐.png", "舰娘头像外框紫色.png"]: - return 3 - elif star in ["舰娘头像外框超稀有.png", "舰娘头像外框金色.png"]: - return 4 - elif star in ["舰娘头像外框海上传奇.png", "舰娘头像外框彩色.png"]: - return 5 - elif star in [ - "舰娘头像外框最高方案.png", - "舰娘头像外框决战方案.png", - "舰娘头像外框超稀有META.png", - "舰娘头像外框精锐META.png", - ]: - return 6 - else: - return 6 - - async def update_up_char(self): - url = "https://wiki.biligame.com/blhx/游戏活动表" - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取活动表出错") - return - try: - dom = etree.HTML(result, etree.HTMLParser()) - dd = dom.xpath("//div[@class='timeline2']/dl/dd/a")[0] - url = "https://wiki.biligame.com" + dd.xpath("./@href")[0] - title = dd.xpath("string(.)") - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取活动页面出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - timer = dom.xpath("//span[@class='eventTimer']")[0] - start_time = dateparser.parse(timer.xpath("./@data-start")[0]) - end_time = dateparser.parse(timer.xpath("./@data-end")[0]) - ships = dom.xpath("//table[@class='shipinfo']") - up_chars = [] - for ship in ships: - name = ship.xpath("./tbody/tr/td[2]/p/a/@title")[0] - type_ = ship.xpath("./tbody/tr/td[2]/p/small/text()")[0] # 舰船类型 - try: - p = float(str(ship.xpath(".//sup/text()")[0]).strip("%")) - except (IndexError, ValueError): - p = 0 - star = self.parse_star( - ship.xpath("./tbody/tr/td[1]/div/div/div/a/img/@alt")[0] - ) - up_chars.append( - UpChar(name=name, star=star, limited=False, zoom=p, type_=type_) - ) - self.UP_EVENT = UpEvent( - title=title, - pool_img="", - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.dump_up_char() - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错", e=e) - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_EVENT: - return MessageUtils.build_message( - f"重载成功!\n当前活动:{self.UP_EVENT.title}" - ) diff --git a/zhenxun/plugins/draw_card/handles/ba_handle.py b/zhenxun/plugins/draw_card/handles/ba_handle.py deleted file mode 100644 index d347504a..00000000 --- a/zhenxun/plugins/draw_card/handles/ba_handle.py +++ /dev/null @@ -1,157 +0,0 @@ -import random - -from PIL import ImageDraw - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font -from .base_handle import BaseData, BaseHandle - - -class BaChar(BaseData): - pass - - -class BaHandle(BaseHandle[BaChar]): - def __init__(self): - super().__init__("ba", "碧蓝档案") - self.max_star = 3 - self.config = draw_config.ba - self.ALL_CHAR: list[BaChar] = [] - - def get_card(self, mode: int = 1) -> BaChar: - if mode == 2: - star = self.get_star( - [3, 2], [self.config.BA_THREE_P, self.config.BA_G_TWO_P] - ) - else: - star = self.get_star( - [3, 2, 1], - [self.config.BA_THREE_P, self.config.BA_TWO_P, self.config.BA_ONE_P], - ) - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - return random.choice(chars) - - def get_cards(self, count: int, **kwargs) -> list[tuple[BaChar, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(2) - card_count = 0 - else: - card = self.get_card(1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - async def generate_card_img(self, card: BaChar) -> BuildImage: - sep_w = 5 - sep_h = 5 - star_h = 15 - img_w = 90 - img_h = 100 - font_h = 20 - bar_h = 20 - bar_w = 90 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5") - img_path = self.img_path / f"{cn2py(card.name)}.png" - img = BuildImage( - img_w, img_h, background=img_path if img_path.exists() else None - ) - bar = BuildImage(bar_w, bar_h, color="#6495ED") - await bg.paste(img, (sep_w, sep_h)) - await bg.paste(bar, (sep_w, img_h - bar_h + sep_h)) - if card.star == 1: - star_path = str(self.img_path / "star-1.png") - star_w = 15 - elif card.star == 2: - star_path = str(self.img_path / "star-2.png") - star_w = 30 - else: - star_path = str(self.img_path / "star-3.png") - star_w = 45 - star = BuildImage(star_w, star_h, background=star_path) - await bg.paste(star, (img_w // 2 - 15 * (card.star - 1) // 2, img_h - star_h)) - text = card.name[:5] + "..." if len(card.name) > 6 else card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - BaChar( - name=value["名称"], - star=int(value["星级"]), - limited=True if "(" in key else False, - ) - for key, value in self.load_data().items() - ] - - def title2star(self, title: int): - if title == "Star-3.png": - return 3 - elif title == "Star-2.png": - return 2 - else: - return 1 - - async def _update_info(self): - # TODO: ba获取链接失效 - info = {} - url = "https://schale.gg/data/cn/students.min.json?v=49" - result = (await AsyncHttpx.get(url)).json() - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - else: - for char in result: - try: - name = char["Name"] - id = str(char["Id"]) - avatar = ( - "https://github.com/SchaleDB/SchaleDB/raw/main/images/student/icon/" - + id - + ".webp" - ) - star = char["StarGrade"] - star = char["StarGrade"] - except IndexError: - continue - member_dict = { - "头像": avatar, - "名称": name, - "星级": star, - } - info[member_dict["名称"]] = member_dict - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - await self.download_img( - "https://patchwiki.biligame.com/images/bluearchive/thumb/e/e0/82nj2x9sxko473g7782r14fztd4zyky.png/15px-Star-1.png", - "star-1", - ) - await self.download_img( - "https://patchwiki.biligame.com/images/bluearchive/thumb/0/0b/msaff2g0zk6nlyl1rrn7n1ri4yobcqc.png/30px-Star-2.png", - "star-2", - ) - await self.download_img( - "https://patchwiki.biligame.com/images/bluearchive/thumb/8/8a/577yv79x1rwxk8efdccpblo0lozl158.png/46px-Star-3.png", - "star-3", - ) diff --git a/zhenxun/plugins/draw_card/handles/base_handle.py b/zhenxun/plugins/draw_card/handles/base_handle.py deleted file mode 100644 index 3483d246..00000000 --- a/zhenxun/plugins/draw_card/handles/base_handle.py +++ /dev/null @@ -1,295 +0,0 @@ -import asyncio -import random -from asyncio.exceptions import TimeoutError -from datetime import datetime -from typing import Generic, TypeVar - -import aiohttp -import anyio -import ujson as json -from nonebot_plugin_alconna import UniMessage -from PIL import Image -from pydantic import BaseModel, Extra - -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import DRAW_PATH, draw_config -from ..util import circled_number, cn2py - - -class BaseData(BaseModel, extra=Extra.ignore): - name: str # 名字 - star: int # 星级 - limited: bool # 限定 - - def __eq__(self, other: "BaseData"): - return self.name == other.name - - def __hash__(self): - return hash(self.name) - - @property - def star_str(self) -> str: - return "".join(["★" for _ in range(self.star)]) - - -class UpChar(BaseData): - zoom: float # up提升倍率 - - -class UpEvent(BaseModel): - title: str # up池标题 - pool_img: str # up池封面 - start_time: datetime | None # 开始时间 - end_time: datetime | None # 结束时间 - up_char: list[UpChar] # up对象 - - -TC = TypeVar("TC", bound="BaseData") - - -class BaseHandle(Generic[TC]): - def __init__(self, game_name: str, game_name_cn: str): - self.game_name = game_name - self.game_name_cn = game_name_cn - self.max_star = 1 # 最大星级 - self.game_card_color: str = "#ffffff" - self.data_path = DATA_PATH / "draw_card" - self.img_path = DRAW_PATH / f"{self.game_name}" - self.up_path = DATA_PATH / "draw_card" / "draw_card_up" - self.img_path.mkdir(parents=True, exist_ok=True) - self.up_path.mkdir(parents=True, exist_ok=True) - self.data_files: list[str] = [f"{self.game_name}.json"] - - async def draw(self, count: int, **kwargs) -> UniMessage: - index2card = self.get_cards(count, **kwargs) - cards = [card[0] for card in index2card] - result = self.format_result(index2card) - gen_img = await self.generate_img(cards) - return MessageUtils.build_message([gen_img, result]) - - # 抽取卡池 - def get_card(self, **kwargs) -> TC: - raise NotImplementedError - - def get_cards(self, count: int, **kwargs) -> list[tuple[TC, int]]: - return [(self.get_card(**kwargs), i) for i in range(count)] - - # 获取星级 - @staticmethod - def get_star(star_list: list[int], probability_list: list[float]) -> int: - return random.choices(star_list, weights=probability_list, k=1)[0] - - def format_result(self, index2card: list[tuple[TC, int]], **kwargs) -> str: - card_list = [card[0] for card in index2card] - results = [ - self.format_star_result(card_list, **kwargs), - self.format_max_star(index2card, **kwargs), - self.format_max_card(card_list, **kwargs), - ] - results = [rst for rst in results if rst] - return "\n".join(results) - - def format_star_result(self, card_list: list[TC], **kwargs) -> str: - star_dict: dict[str, int] = {} # 记录星级及其次数 - - card_list_sorted = sorted(card_list, key=lambda c: c.star, reverse=True) - for card in card_list_sorted: - try: - star_dict[card.star_str] += 1 - except KeyError: - star_dict[card.star_str] = 1 - - rst = "" - for star_str, count in star_dict.items(): - rst += f"[{star_str}×{count}] " - return rst.strip() - - def format_max_star( - self, card_list: list[tuple[TC, int]], up_list: list[str] = [], **kwargs - ) -> str: - up_list = up_list or kwargs.get("up_list", []) - rst = "" - for card, index in card_list: - if card.star == self.max_star: - if card.name in up_list: - rst += f"第 {index} 抽获取UP {card.name}\n" - else: - rst += f"第 {index} 抽获取 {card.name}\n" - return rst.strip() - - def format_max_card(self, card_list: list[TC], **kwargs) -> str: - card_dict: dict[TC, int] = {} # 记录卡牌抽取次数 - - for card in card_list: - try: - card_dict[card] += 1 - except KeyError: - card_dict[card] = 1 - - max_count = max(card_dict.values()) - max_card = list(card_dict.keys())[list(card_dict.values()).index(max_count)] - if max_count <= 1: - return "" - return f"抽取到最多的是{max_card.name},共抽取了{max_count}次" - - async def generate_img( - self, - cards: list[TC], - num_per_line: int = 5, - max_per_line: tuple[int, int] = (40, 10), - ) -> BuildImage: - """ - 生成统计图片 - cards: 卡牌列表 - num_per_line: 单行角色显示数量 - max_per_line: 当card_list超过一定数值时,更改单行数量 - """ - if len(cards) > max_per_line[0]: - num_per_line = max_per_line[1] - if len(cards) > 90: - card_dict: dict[TC, int] = {} # 记录卡牌抽取次数 - for card in cards: - try: - card_dict[card] += 1 - except KeyError: - card_dict[card] = 1 - card_list = list(card_dict) - num_list = list(card_dict.values()) - else: - card_list = cards - num_list = [1] * len(cards) - - card_imgs: list[BuildImage] = [] - for card, num in zip(card_list, num_list): - card_img = await self.generate_card_img(card) - # 数量 > 1 时加数字上标 - if num > 1: - label = circled_number(num) - label_w = int(min(card_img.width, card_img.height) / 7) - label = label.resize( - ( - int(label_w * label.width / label.height), - label_w, - ), - Image.ANTIALIAS, # type: ignore - ) - await card_img.paste(label) - - card_imgs.append(card_img) - - # img_w = card_imgs[0].width - # img_h = card_imgs[0].height - # if len(card_imgs) < num_per_line: - # w = img_w * len(card_imgs) - # else: - # w = img_w * num_per_line - # h = img_h * math.ceil(len(card_imgs) / num_per_line) - # img = BuildImage(w, h, img_w, img_h, color=self.game_card_color) - # for card_img in card_imgs: - # await img.paste(card_img) - return await BuildImage.auto_paste(card_imgs, 10, color=self.game_card_color) # type: ignore - - async def generate_card_img(self, card: TC) -> BuildImage: - img = str(self.img_path / f"{cn2py(card.name)}.png") - return BuildImage(100, 100, background=img) - - def load_data(self, filename: str = "") -> dict: - if not filename: - filename = f"{self.game_name}.json" - filepath = self.data_path / filename - if not filepath.exists(): - return {} - with filepath.open("r", encoding="utf8") as f: - return json.load(f) - - def dump_data(self, data: dict, filename: str = ""): - if not filename: - filename = f"{self.game_name}.json" - filepath = self.data_path / filename - with filepath.open("w", encoding="utf8") as f: - json.dump(data, f, ensure_ascii=False, indent=4) - - def data_exists(self) -> bool: - for file in self.data_files: - if not (self.data_path / file).exists(): - return False - return True - - def _init_data(self): - raise NotImplementedError - - def init_data(self): - try: - self._init_data() - except Exception as e: - logger.warning(f"{self.game_name_cn} 导入角色数据错误:{type(e)}:{e}") - - async def _update_info(self): - raise NotImplementedError - - def client(self) -> aiohttp.ClientSession: - headers = { - "User-Agent": '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"' - } - return aiohttp.ClientSession(headers=headers) - - async def update_info(self): - try: - async with asyncio.Semaphore(draw_config.SEMAPHORE): - async with self.client() as session: - self.session = session - await self._update_info() - except Exception as e: - logger.warning(f"{self.game_name_cn} 更新数据错误:{type(e)}:{e}") - self.init_data() - - async def get_url(self, url: str) -> str: - result = "" - retry = 5 - for i in range(retry): - try: - async with self.session.get(url, timeout=10) as response: - result = await response.text() - break - except TimeoutError: - logger.warning(f"访问 {url} 超时, 重试 {i + 1}/{retry}") - await asyncio.sleep(1) - return result - - async def download_img(self, url: str, name: str) -> bool: - img_path = self.img_path / f"{cn2py(name)}.png" - if img_path.exists(): - return True - try: - async with self.session.get(url, timeout=10) as response: - async with await anyio.open_file(img_path, "wb") as f: - await f.write(await response.read()) - return True - except TimeoutError: - logger.warning( - f"下载 {self.game_name_cn} 图片超时,名称:{name},url:{url}" - ) - return False - except: - logger.warning( - f"下载 {self.game_name_cn} 链接错误,名称:{name},url:{url}" - ) - return False - - async def _reload_pool(self) -> UniMessage | None: - return None - - async def reload_pool(self) -> UniMessage | None: - try: - async with self.client() as session: - self.session = session - return await self._reload_pool() - except Exception as e: - logger.warning(f"{self.game_name_cn} 重载UP池错误", e=e) - - def reset_count(self, user_id: str) -> bool: - return False diff --git a/zhenxun/plugins/draw_card/handles/fgo_handle.py b/zhenxun/plugins/draw_card/handles/fgo_handle.py deleted file mode 100644 index 5acb8c5f..00000000 --- a/zhenxun/plugins/draw_card/handles/fgo_handle.py +++ /dev/null @@ -1,223 +0,0 @@ -import random - -import ujson as json -from lxml import etree -from PIL import ImageDraw - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle - - -class FgoData(BaseData): - pass - - -class FgoChar(FgoData): - pass - - -class FgoCard(FgoData): - pass - - -class FgoHandle(BaseHandle[FgoData]): - def __init__(self): - super().__init__("fgo", "命运-冠位指定") - self.data_files.append("fgo_card.json") - self.max_star = 5 - self.config = draw_config.fgo - self.ALL_CHAR: list[FgoChar] = [] - self.ALL_CARD: list[FgoCard] = [] - - def get_card(self, mode: int = 1) -> FgoData: - if mode == 1: - star = self.get_star( - [8, 7, 6, 5, 4, 3], - [ - self.config.FGO_SERVANT_FIVE_P, - self.config.FGO_SERVANT_FOUR_P, - self.config.FGO_SERVANT_THREE_P, - self.config.FGO_CARD_FIVE_P, - self.config.FGO_CARD_FOUR_P, - self.config.FGO_CARD_THREE_P, - ], - ) - elif mode == 2: - star = self.get_star( - [5, 4], [self.config.FGO_CARD_FIVE_P, self.config.FGO_CARD_FOUR_P] - ) - else: - star = self.get_star( - [8, 7, 6], - [ - self.config.FGO_SERVANT_FIVE_P, - self.config.FGO_SERVANT_FOUR_P, - self.config.FGO_SERVANT_THREE_P, - ], - ) - if star > 5: - star -= 3 - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - else: - chars = [x for x in self.ALL_CARD if x.star == star and not x.limited] - return random.choice(chars) - - def get_cards(self, count: int, **kwargs) -> list[tuple[FgoData, int]]: - card_list = [] # 获取所有角色 - servant_count = 0 # 保底计算 - card_count = 0 # 保底计算 - for i in range(count): - servant_count += 1 - card_count += 1 - if card_count == 9: # 四星卡片保底 - mode = 2 - elif servant_count == 10: # 三星从者保底 - mode = 3 - else: # 普通抽 - mode = 1 - card = self.get_card(mode) - if isinstance(card, FgoCard) and card.star > self.max_star - 2: - card_count = 0 - if isinstance(card, FgoChar): - servant_count = 0 - card_list.append((card, i + 1)) - return card_list - - async def generate_card_img(self, card: FgoData) -> BuildImage: - sep_w = 5 - sep_t = 5 - sep_b = 20 - w = 128 - h = 140 - bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(w, h, background=img_path) - await bg.paste(img, (sep_w, sep_t)) - # 加名字 - text = card.name[:6] + "..." if len(card.name) > 7 else card.name - font = load_font(fontsize=16) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - FgoChar( - name=value["名称"], - star=int(value["星级"]), - limited=( - True - if not ( - "圣晶石召唤" in value["入手方式"] - or "圣晶石召唤(Story卡池)" in value["入手方式"] - ) - else False - ), - ) - for value in self.load_data().values() - ] - self.ALL_CARD = [ - FgoCard(name=value["名称"], star=int(value["星级"]), limited=False) - for value in self.load_data("fgo_card.json").values() - ] - - async def _update_info(self): - # TODO: fgo获取链接失效 - fgo_info = {} - for i in range(500): - url = f"http://fgo.vgtime.com/servant/ajax?card=&wd=&ids=&sort=12777&o=desc&pn={i}" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} page {i} 出错") - continue - fgo_data = json.loads(result) - if int(fgo_data["nums"]) <= 0: - break - for x in fgo_data["data"]: - name = remove_prohibited_str(x["name"]) - member_dict = { - "id": x["id"], - "card_id": x["charid"], - "头像": x["icon"], - "名称": remove_prohibited_str(x["name"]), - "职阶": x["classes"], - "星级": int(x["star"]), - "hp": x["lvmax4hp"], - "atk": x["lvmax4atk"], - "card_quick": x["cardquick"], - "card_arts": x["cardarts"], - "card_buster": x["cardbuster"], - "宝具": x["tprop"], - } - fgo_info[name] = member_dict - # 更新额外信息 - for key in fgo_info.keys(): - url = f'http://fgo.vgtime.com/servant/{fgo_info[key]["id"]}' - result = await self.get_url(url) - if not result: - fgo_info[key]["入手方式"] = ["圣晶石召唤"] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - obtain = dom.xpath( - "//table[contains(string(.),'入手方式')]/tr[8]/td[3]/text()" - )[0] - obtain = str(obtain).strip() - if "限时活动免费获取 活动结束后无法获得" in obtain: - obtain = ["活动获取"] - elif "非限时UP无法获得" in obtain: - obtain = ["限时召唤"] - else: - if "&" in obtain: - obtain = obtain.split("&") - else: - obtain = obtain.split(" ") - obtain = [s.strip() for s in obtain if s.strip()] - fgo_info[key]["入手方式"] = obtain - except IndexError: - fgo_info[key]["入手方式"] = ["圣晶石召唤"] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - self.dump_data(fgo_info) - logger.info(f"{self.game_name_cn} 更新成功") - # fgo_card.json - fgo_card_info = {} - for i in range(500): - url = f"http://fgo.vgtime.com/equipment/ajax?wd=&ids=&sort=12958&o=desc&pn={i}" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn}卡牌 page {i} 出错") - continue - fgo_data = json.loads(result) - if int(fgo_data["nums"]) <= 0: - break - for x in fgo_data["data"]: - name = remove_prohibited_str(x["name"]) - member_dict = { - "id": x["id"], - "card_id": x["equipid"], - "头像": x["icon"], - "名称": name, - "星级": int(x["star"]), - "hp": x["lvmax_hp"], - "atk": x["lvmax_atk"], - "skill_e": str(x["skill_e"]).split("
")[:-1], - } - fgo_card_info[name] = member_dict - self.dump_data(fgo_card_info, "fgo_card.json") - logger.info(f"{self.game_name_cn} 卡牌更新成功") - # 下载头像 - for value in fgo_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in fgo_card_info.values(): - await self.download_img(value["头像"], value["名称"]) diff --git a/zhenxun/plugins/draw_card/handles/genshin_handle.py b/zhenxun/plugins/draw_card/handles/genshin_handle.py deleted file mode 100644 index 61edcf30..00000000 --- a/zhenxun/plugins/draw_card/handles/genshin_handle.py +++ /dev/null @@ -1,461 +0,0 @@ -import random -from datetime import datetime, timedelta -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import Image, ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..count_manager import GenshinCountManager -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class GenshinData(BaseData): - pass - - -class GenshinChar(GenshinData): - pass - - -class GenshinArms(GenshinData): - pass - - -class GenshinHandle(BaseHandle[GenshinData]): - def __init__(self): - super().__init__("genshin", "原神") - self.data_files.append("genshin_arms.json") - self.max_star = 5 - self.game_card_color = "#ebebeb" - self.config = draw_config.genshin - - self.ALL_CHAR: list[GenshinData] = [] - self.ALL_ARMS: list[GenshinData] = [] - self.UP_CHAR: UpEvent | None = None - self.UP_CHAR_LIST: UpEvent | None = [] - self.UP_ARMS: UpEvent | None = None - - self.count_manager = GenshinCountManager((10, 90), ("4", "5"), 180) - - # 抽取卡池 - def get_card( - self, - pool_name: str, - mode: int = 1, - add: float = 0.0, - is_up: bool = False, - card_index: int = 0, - ): - """ - mode 1:普通抽 2:四星保底 3:五星保底 - """ - if mode == 1: - star = self.get_star( - [5, 4, 3], - [ - self.config.GENSHIN_FIVE_P + add, - self.config.GENSHIN_FOUR_P, - self.config.GENSHIN_THREE_P, - ], - ) - elif mode == 2: - star = self.get_star( - [5, 4], - [self.config.GENSHIN_G_FIVE_P + add, self.config.GENSHIN_G_FOUR_P], - ) - else: - star = 5 - - if pool_name == "char": - up_event = self.UP_CHAR_LIST[card_index] - all_list = self.ALL_CHAR + [ - x for x in self.ALL_ARMS if x.star == star and x.star < 5 - ] - elif pool_name == "arms": - up_event = self.UP_ARMS - all_list = self.ALL_ARMS + [ - x for x in self.ALL_CHAR if x.star == star and x.star < 5 - ] - else: - up_event = None - all_list = self.ALL_ARMS + self.ALL_CHAR - - acquire_char = None - # 是否UP - if up_event and star > 3: - # 获取up角色列表 - up_list = [x.name for x in up_event.up_char if x.star == star] - # 成功获取up角色 - if random.random() < 0.5 or is_up: - up_name = random.choice(up_list) - try: - acquire_char = [x for x in all_list if x.name == up_name][0] - except IndexError: - pass - if not acquire_char: - chars = [x for x in all_list if x.star == star and not x.limited] - acquire_char = random.choice(chars) - return acquire_char - - def get_cards( - self, count: int, user_id: int, pool_name: str, card_index: int = 0 - ) -> list[tuple[GenshinData, int]]: - card_list = [] # 获取角色列表 - add = 0.0 - count_manager = self.count_manager - count_manager.check_count(user_id, count) # 检查次数累计 - pool = self.UP_CHAR_LIST[card_index] if pool_name == "char" else self.UP_ARMS - for i in range(count): - count_manager.increase(user_id) - star = count_manager.check(user_id) # 是否有四星或五星保底 - if ( - count_manager.get_user_count(user_id) - - count_manager.get_user_five_index(user_id) - ) % count_manager.get_max_guarantee() >= 72: - add += draw_config.genshin.I72_ADD - if star: - if star == 4: - card = self.get_card(pool_name, 2, add=add, card_index=card_index) - else: - card = self.get_card( - pool_name, - 3, - add, - count_manager.is_up(user_id), - card_index=card_index, - ) - else: - card = self.get_card( - pool_name, - 1, - add, - count_manager.is_up(user_id), - card_index=card_index, - ) - # print(f"{count_manager.get_user_count(user_id)}:", - # count_manager.get_user_five_index(user_id), star, card.star, add) - # 四星角色 - if card.star == 4: - count_manager.mark_four_index(user_id) - # 五星角色 - elif card.star == self.max_star: - add = 0 - count_manager.mark_five_index(user_id) # 记录五星保底 - count_manager.mark_four_index(user_id) # 记录四星保底 - if pool and card.name in [ - x.name for x in pool.up_char if x.star == self.max_star - ]: - count_manager.set_is_up(user_id, True) - else: - count_manager.set_is_up(user_id, False) - card_list.append((card, count_manager.get_user_count(user_id))) - return card_list - - async def generate_card_img(self, card: GenshinData) -> BuildImage: - sep_w = 10 - sep_h = 5 - frame_w = 112 - frame_h = 132 - img_w = 106 - img_h = 106 - bg = BuildImage(frame_w + sep_w * 2, frame_h + sep_h * 2, color="#EBEBEB") - frame_path = str(self.img_path / "avatar_frame.png") - frame = Image.open(frame_path) - # 加名字 - text = card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(frame) - draw.text( - ((frame_w - text_w) / 2, frame_h - 15 - text_h / 2), - text, - font=font, - fill="gray", - ) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - if isinstance(card, GenshinArms): - # 武器卡背景不是透明的,切去上方两个圆弧 - r = 12 - circle = Image.new("L", (r * 2, r * 2), 0) - alpha = Image.new("L", img.size, 255) - alpha.paste(circle, (-r - 3, -r - 3)) # 左上角 - alpha.paste(circle, (img_h - r + 3, -r - 3)) # 右上角 - img.markImg.putalpha(alpha) - star_path = str(self.img_path / f"{card.star}_star.png") - star = Image.open(star_path) - await bg.paste(frame, (sep_w, sep_h)) - await bg.paste(img, (sep_w + 3, sep_h + 3)) - await bg.paste(star, (sep_w + int((frame_w - star.width) / 2), sep_h - 6)) - return bg - - def format_pool_info(self, pool_name: str, card_index: int = 0) -> str: - info = "" - up_event = None - if pool_name == "char": - up_event = self.UP_CHAR_LIST[card_index] - elif pool_name == "arms": - up_event = self.UP_ARMS - if up_event: - star5_list = [x.name for x in up_event.up_char if x.star == 5] - star4_list = [x.name for x in up_event.up_char if x.star == 4] - if star5_list: - info += f"五星UP:{' '.join(star5_list)}\n" - if star4_list: - info += f"四星UP:{' '.join(star4_list)}\n" - info = f"当前up池:{up_event.title}\n{info}" - return info.strip() - - async def draw( - self, count: int, user_id: int, pool_name: str = "", **kwargs - ) -> UniMessage: - card_index = 0 - if "1" in pool_name: - card_index = 1 - pool_name = pool_name.replace("1", "") - index2cards = self.get_cards(count, user_id, pool_name, card_index) - cards = [card[0] for card in index2cards] - up_event = None - if pool_name == "char": - if card_index == 1 and len(self.UP_CHAR_LIST) == 1: - return MessageUtils.build_message("当前没有第二个角色UP池") - up_event = self.UP_CHAR_LIST[card_index] - elif pool_name == "arms": - up_event = self.UP_ARMS - up_list = [x.name for x in up_event.up_char] if up_event else [] - result = self.format_star_result(cards) - result += ( - "\n" + max_star_str - if (max_star_str := self.format_max_star(index2cards, up_list=up_list)) - else "" - ) - result += f"\n距离保底发还剩 {self.count_manager.get_user_guarantee_count(user_id)} 抽" - # result += "\n【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】" - pool_info = self.format_pool_info(pool_name, card_index) - img = await self.generate_img(cards) - bk = BuildImage(img.width, img.height + 50, font_size=20, color="#ebebeb") - await bk.paste(img) - await bk.text( - (0, img.height + 10), - "【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】", - ) - return MessageUtils.build_message([pool_info, bk, result]) - - def _init_data(self): - self.ALL_CHAR = [ - GenshinChar( - name=value["名称"], - star=int(value["星级"]), - limited=value["常驻/限定"] == "限定UP", - ) - for key, value in self.load_data().items() - if "旅行者" not in key - ] - self.ALL_ARMS = [ - GenshinArms( - name=value["名称"], - star=int(value["星级"]), - limited="祈愿" not in value["获取途径"], - ) - for value in self.load_data("genshin_arms.json").values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_CHAR_LIST.append(UpEvent.parse_obj(data.get("char", {}))) - self.UP_CHAR_LIST.append(UpEvent.parse_obj(data.get("char1", {}))) - self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_CHAR_LIST and self.UP_ARMS: - data = { - "char": json.loads(self.UP_CHAR_LIST[0].json()), - "arms": json.loads(self.UP_ARMS.json()), - } - if len(self.UP_CHAR_LIST) > 1: - data["char1"] = json.loads(self.UP_CHAR_LIST[1].json()) - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - # genshin.json - char_info = {} - url = "https://wiki.biligame.com/ys/角色筛选" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = char.xpath("./td[3]/text()")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()[:1]), - } - char_info[member_dict["名称"]] = member_dict - # 更新额外信息 - for key in char_info.keys(): - result = await self.get_url(f"https://wiki.biligame.com/ys/{key}") - if not result: - char_info[key]["常驻/限定"] = "未知" - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - limit = dom.xpath( - "//table[contains(string(.),'常驻/限定')]/tbody/tr[6]/td/text()" - )[0] - char_info[key]["常驻/限定"] = str(limit).strip() - except IndexError: - char_info[key]["常驻/限定"] = "未知" - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - self.dump_data(char_info) - logger.info(f"{self.game_name_cn} 更新成功") - # genshin_arms.json - arms_info = {} - url = "https://wiki.biligame.com/ys/武器图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = char.xpath("./td[4]/img/@alt")[0] - sources = str(char.xpath("./td[5]/text()")[0]).split(",") - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()[:1]), - "获取途径": [s.strip() for s in sources if s.strip()], - } - arms_info[member_dict["名称"]] = member_dict - self.dump_data(arms_info, "genshin_arms.json") - logger.info(f"{self.game_name_cn} 武器更新成功") - # 下载头像 - for value in char_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in arms_info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - idx = 1 - YS_URL = "https://patchwiki.biligame.com/images/ys" - for url in [ - "/1/13/7xzg7tgf8dsr2hjpmdbm5gn9wvzt2on.png", - "/b/bc/sd2ige6d7lvj7ugfumue3yjg8gyi0d1.png", - "/e/ec/l3mnhy56pyailhn3v7r873htf2nofau.png", - "/9/9c/sklp02ffk3aqszzvh8k1c3139s0awpd.png", - "/c/c7/qu6xcndgj6t14oxvv7yz2warcukqv1m.png", - ]: - await self.download_img(YS_URL + url, f"{idx}_star") - idx += 1 - # 下载头像框 - await self.download_img( - YS_URL + "/2/2e/opbcst4xbtcq0i4lwerucmosawn29ti.png", f"avatar_frame" - ) - await self.update_up_char() - - async def update_up_char(self): - self.UP_CHAR_LIST = [] - url = "https://wiki.biligame.com/ys/祈愿" - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取祈愿页面出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - tables = dom.xpath( - "//div[@class='mw-parser-output']/div[@class='row']/div/table[@class='wikitable']/tbody" - ) - if not tables or len(tables) < 2: - logger.warning(f"{self.game_name_cn}获取活动祈愿出错") - return - try: - for index, table in enumerate(tables): - title = table.xpath("./tr[1]/th/img/@title")[0] - title = str(title).split("」")[0] + "」" if "」" in title else title - pool_img = str(table.xpath("./tr[1]/th/img/@srcset")[0]).split(" ")[-2] - time = table.xpath("./tr[2]/td/text()")[0] - star5_list = table.xpath("./tr[3]/td/a/@title") - star4_list = table.xpath("./tr[4]/td/a/@title") - start, end = str(time).split("~") - start_time = dateparser.parse(start) - end_time = dateparser.parse(end) - if not start_time and end_time: - start_time = end_time - timedelta(days=20) - if start_time and end_time and start_time <= datetime.now() <= end_time: - up_event = UpEvent( - title=title, - pool_img=pool_img, - start_time=start_time, - end_time=end_time, - up_char=[ - UpChar(name=name, star=5, limited=False, zoom=50) - for name in star5_list - ] - + [ - UpChar(name=name, star=4, limited=False, zoom=50) - for name in star4_list - ], - ) - if "神铸赋形" not in title: - self.UP_CHAR_LIST.append(up_event) - else: - self.UP_ARMS = up_event - if self.UP_CHAR_LIST and self.UP_ARMS: - self.dump_up_char() - char_title = " & ".join([x.title for x in self.UP_CHAR_LIST]) - logger.info( - f"成功获取{self.game_name_cn}当前up信息...当前up池: {char_title} & {self.UP_ARMS.title}" - ) - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错", e=e) - - def reset_count(self, user_id: str) -> bool: - self.count_manager.reset(user_id) - return True - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_CHAR_LIST and self.UP_ARMS: - if len(self.UP_CHAR_LIST) > 1: - return MessageUtils.build_message( - [ - f"重载成功!\n当前UP池子:{self.UP_CHAR_LIST[0].title} & {self.UP_CHAR_LIST[1].title} & {self.UP_ARMS.title}", - self.UP_CHAR_LIST[0].pool_img, - self.UP_CHAR_LIST[1].pool_img, - self.UP_ARMS.pool_img, - ] - ) - return UniMessage( - [ - f"重载成功!\n当前UP池子:{char_title} & {self.UP_ARMS.title}", - self.UP_CHAR_LIST[0].pool_img, - self.UP_ARMS.pool_img, - ] - ) diff --git a/zhenxun/plugins/draw_card/handles/guardian_handle.py b/zhenxun/plugins/draw_card/handles/guardian_handle.py deleted file mode 100644 index 517f126d..00000000 --- a/zhenxun/plugins/draw_card/handles/guardian_handle.py +++ /dev/null @@ -1,400 +0,0 @@ -import random -import re -from datetime import datetime -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class GuardianData(BaseData): - pass - - -class GuardianChar(GuardianData): - pass - - -class GuardianArms(GuardianData): - pass - - -class GuardianHandle(BaseHandle[GuardianData]): - def __init__(self): - super().__init__("guardian", "坎公骑冠剑") - self.data_files.append("guardian_arms.json") - self.config = draw_config.guardian - - self.ALL_CHAR: list[GuardianChar] = [] - self.ALL_ARMS: list[GuardianArms] = [] - self.UP_CHAR: UpEvent | None = None - self.UP_ARMS: UpEvent | None = None - - def get_card(self, pool_name: str, mode: int = 1) -> GuardianData: - if pool_name == "char": - if mode == 1: - star = self.get_star( - [3, 2, 1], - [ - self.config.GUARDIAN_THREE_CHAR_P, - self.config.GUARDIAN_TWO_CHAR_P, - self.config.GUARDIAN_ONE_CHAR_P, - ], - ) - else: - star = self.get_star( - [3, 2], - [ - self.config.GUARDIAN_THREE_CHAR_P, - self.config.GUARDIAN_TWO_CHAR_P, - ], - ) - up_event = self.UP_CHAR - self.max_star = 3 - all_data = self.ALL_CHAR - else: - if mode == 1: - star = self.get_star( - [5, 4, 3, 2], - [ - self.config.GUARDIAN_FIVE_ARMS_P, - self.config.GUARDIAN_FOUR_ARMS_P, - self.config.GUARDIAN_THREE_ARMS_P, - self.config.GUARDIAN_TWO_ARMS_P, - ], - ) - else: - star = self.get_star( - [5, 4], - [ - self.config.GUARDIAN_FIVE_ARMS_P, - self.config.GUARDIAN_FOUR_ARMS_P, - ], - ) - up_event = self.UP_ARMS - self.max_star = 5 - all_data = self.ALL_ARMS - - acquire_char = None - # 是否UP - if up_event and star == self.max_star and pool_name: - # 获取up角色列表 - up_list = [x.name for x in up_event.up_char if x.star == star] - # 成功获取up角色 - if random.random() < 0.5: - up_name = random.choice(up_list) - try: - acquire_char = [x for x in all_data if x.name == up_name][0] - except IndexError: - pass - if not acquire_char: - chars = [x for x in all_data if x.star == star and not x.limited] - acquire_char = random.choice(chars) - return acquire_char - - def get_cards(self, count: int, pool_name: str) -> list[tuple[GuardianData, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(pool_name, 2) - card_count = 0 - else: - card = self.get_card(pool_name, 1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - def format_pool_info(self, pool_name: str) -> str: - info = "" - up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS - if up_event: - if pool_name == "char": - up_list = [x.name for x in up_event.up_char if x.star == 3] - info += f'三星UP:{" ".join(up_list)}\n' - else: - up_list = [x.name for x in up_event.up_char if x.star == 5] - info += f'五星UP:{" ".join(up_list)}\n' - info = f"当前up池:{up_event.title}\n{info}" - return info.strip() - - async def draw(self, count: int, pool_name: str, **kwargs) -> UniMessage: - index2card = self.get_cards(count, pool_name) - cards = [card[0] for card in index2card] - up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS - up_list = [x.name for x in up_event.up_char] if up_event else [] - result = self.format_result(index2card, up_list=up_list) - pool_info = self.format_pool_info(pool_name) - img = await self.generate_img(cards) - return MessageUtils.build_message([pool_info, img, result]) - - async def generate_card_img(self, card: GuardianData) -> BuildImage: - sep_w = 1 - sep_h = 1 - block_w = 170 - block_h = 90 - img_w = 90 - img_h = 90 - if isinstance(card, GuardianChar): - block_color = "#2e2923" - font_color = "#e2ccad" - star_w = 90 - star_h = 30 - star_name = f"{card.star}_star.png" - frame_path = "" - else: - block_color = "#EEE4D5" - font_color = "#A65400" - star_w = 45 - star_h = 45 - star_name = f"{card.star}_star_rank.png" - frame_path = str(self.img_path / "avatar_frame.png") - bg = BuildImage(block_w + sep_w * 2, block_h + sep_h * 2, color="#F6F4ED") - block = BuildImage(block_w, block_h, color=block_color) - star_path = str(self.img_path / star_name) - star = BuildImage(star_w, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await block.paste(img, (0, 0)) - if frame_path: - frame = BuildImage(img_w, img_h, background=frame_path) - await block.paste(frame, (0, 0)) - await block.paste( - star, - (int((block_w + img_w - star_w) / 2), block_h - star_h - 30), - ) - # 加名字 - text = card.name[:4] + "..." if len(card.name) > 5 else card.name - font = load_font(fontsize=14) - text_w, _ = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(block.markImg) - draw.text( - ((block_w + img_w - text_w) / 2, 55), - text, - font=font, - fill=font_color, - ) - await bg.paste(block, (sep_w, sep_h)) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - GuardianChar(name=value["名称"], star=int(value["星级"]), limited=False) - for value in self.load_data().values() - ] - self.ALL_ARMS = [ - GuardianArms(name=value["名称"], star=int(value["星级"]), limited=False) - for value in self.load_data("guardian_arms.json").values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_CHAR = UpEvent.parse_obj(data.get("char", {})) - self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_CHAR and self.UP_ARMS: - data = { - "char": json.loads(self.UP_CHAR.json()), - "arms": json.loads(self.UP_ARMS.json()), - } - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - # guardian.json - guardian_info = {} - url = "https://wiki.biligame.com/gt/英雄筛选表" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - # name = char.xpath("./td[1]/a/@title")[0] - # avatar = char.xpath("./td[1]/a/img/@src")[0] - # star = char.xpath("./td[1]/span/img/@alt")[0] - name = char.xpath("./th[1]/a[1]/@title")[0] - avatar = char.xpath("./th[1]/a/img/@src")[0] - star = char.xpath("./th[1]/span/img/@alt")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar)), - "名称": remove_prohibited_str(name), - "星级": int(str(star).split(" ")[0].replace("Rank", "")), - } - guardian_info[member_dict["名称"]] = member_dict - self.dump_data(guardian_info) - logger.info(f"{self.game_name_cn} 更新成功") - # guardian_arms.json - guardian_arms_info = {} - url = "https://wiki.biligame.com/gt/武器" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 武器出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[2]/a/@title")[0] - avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0] - url = char.xpath("./td[3]/img/@srcset")[0] - if r := re.search(r"Rank-mini-star_(\d).png", url): - star = r.group(1) - else: - continue - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar)), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()), - } - guardian_arms_info[member_dict["名称"]] = member_dict - self.dump_data(guardian_arms_info, "guardian_arms.json") - logger.info(f"{self.game_name_cn} 武器更新成功") - url = "https://wiki.biligame.com/gt/盾牌" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 盾牌出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath( - "//div[@class='resp-tabs-container']/div[2]/div/table[1]/tbody/tr" - ) - for char in char_list: - try: - name = char.xpath("./td[2]/a/@title")[0] - avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0] - star = char.xpath("./td[3]/text()")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar)), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()), - } - guardian_arms_info[member_dict["名称"]] = member_dict - self.dump_data(guardian_arms_info, "guardian_arms.json") - logger.info(f"{self.game_name_cn} 盾牌更新成功") - # 下载头像 - for value in guardian_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in guardian_arms_info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - idx = 1 - GT_URL = "https://patchwiki.biligame.com/images/gt" - for url in [ - "/4/4b/ardr3bi2yf95u4zomm263tc1vke6i3i.png", - "/5/55/6vow7lh76gzus6b2g9cfn325d1sugca.png", - "/b/b9/du8egrd2vyewg0cuyra9t8jh0srl0ds.png", - ]: - await self.download_img(GT_URL + url, f"{idx}_star") - idx += 1 - # 另一种星星 - idx = 1 - for url in [ - "/6/66/4e2tfa9kvhfcbikzlyei76i9crva145.png", - "/1/10/r9ihsuvycgvsseyneqz4xs22t53026m.png", - "/7/7a/o0k86ru9k915y04azc26hilxead7xp1.png", - "/c/c9/rxz99asysz0rg391j3b02ta09mnpa7v.png", - "/2/2a/sfxz0ucv1s6ewxveycz9mnmrqs2rw60.png", - ]: - await self.download_img(GT_URL + url, f"{idx}_star_rank") - idx += 1 - # 头像框 - await self.download_img( - GT_URL + "/8/8e/ogbqslbhuykjhnc8trtoa0p0nhfzohs.png", f"avatar_frame" - ) - await self.update_up_char() - - async def update_up_char(self): - url = "https://wiki.biligame.com/gt/首页" - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告出错") - return - try: - dom = etree.HTML(result, etree.HTMLParser()) - announcement = dom.xpath( - "//div[@class='mw-parser-output']/div/div[3]/div[2]/div/div[2]/div[3]" - )[0] - title = announcement.xpath("./font/p/b/text()")[0] - match = re.search(r"从(.*?)开始.*?至(.*?)结束", title) - if not match: - logger.warning(f"{self.game_name_cn}找不到UP时间") - return - start, end = match.groups() - start_time = dateparser.parse(start.replace("月", "/").replace("日", "")) - end_time = dateparser.parse(end.replace("月", "/").replace("日", "")) - if not (start_time and end_time) or not ( - start_time <= datetime.now() <= end_time - ): - return - divs = announcement.xpath("./font/div") - char_index = 0 - arms_index = 0 - for index, div in enumerate(divs): - if div.xpath("string(.)") == "角色": - char_index = index - elif div.xpath("string(.)") == "武器": - arms_index = index - chars = divs[char_index + 1 : arms_index] - arms = divs[arms_index + 1 :] - up_chars = [] - up_arms = [] - for char in chars: - name = char.xpath("./p/a/@title")[0] - up_chars.append(UpChar(name=name, star=3, limited=False, zoom=0)) - for arm in arms: - name = arm.xpath("./p/a/@title")[0] - up_arms.append(UpChar(name=name, star=5, limited=False, zoom=0)) - self.UP_CHAR = UpEvent( - title=title, - pool_img="", - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.UP_ARMS = UpEvent( - title=title, - pool_img="", - start_time=start_time, - end_time=end_time, - up_char=up_arms, - ) - self.dump_up_char() - logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}") - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}") - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_CHAR and self.UP_ARMS: - return MessageUtils.build_message( - f"重载成功!\n当前UP池子:{self.UP_CHAR.title}" - ) diff --git a/zhenxun/plugins/draw_card/handles/onmyoji_handle.py b/zhenxun/plugins/draw_card/handles/onmyoji_handle.py deleted file mode 100644 index 25d05c38..00000000 --- a/zhenxun/plugins/draw_card/handles/onmyoji_handle.py +++ /dev/null @@ -1,178 +0,0 @@ -import random - -import ujson as json -from lxml import etree -from PIL import Image, ImageDraw -from PIL.Image import Image as IMG - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle - - -class OnmyojiChar(BaseData): - @property - def star_str(self) -> str: - return ["N", "R", "SR", "SSR", "SP"][self.star - 1] - - -class OnmyojiHandle(BaseHandle[OnmyojiChar]): - def __init__(self): - super().__init__("onmyoji", "阴阳师") - self.max_star = 5 - self.config = draw_config.onmyoji - self.ALL_CHAR: list[OnmyojiChar] = [] - - def get_card(self, **kwargs) -> OnmyojiChar: - star = self.get_star( - [5, 4, 3, 2], - [ - self.config.ONMYOJI_SP, - self.config.ONMYOJI_SSR, - self.config.ONMYOJI_SR, - self.config.ONMYOJI_R, - ], - ) - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - return random.choice(chars) - - def format_max_star(self, card_list: list[tuple[OnmyojiChar, int]]) -> str: - rst = "" - for card, index in card_list: - if card.star == self.max_star: - rst += f"第 {index} 抽获取SP {card.name}\n" - elif card.star == self.max_star - 1: - rst += f"第 {index} 抽获取SSR {card.name}\n" - return rst.strip() - - @staticmethod - def star_label(star: int) -> IMG: - text, color1, color2 = [ - ("N", "#7E7E82", "#F5F6F7"), - ("R", "#014FA8", "#37C6FD"), - ("SR", "#6E0AA4", "#E94EFD"), - ("SSR", "#E5511D", "#FAF905"), - ("SP", "#FA1F2D", "#FFBBAF"), - ][star - 1] - w = 200 - h = 110 - # 制作渐变色图片 - base = Image.new("RGBA", (w, h), color1) - top = Image.new("RGBA", (w, h), color2) - mask = Image.new("L", (w, h)) - mask_data = [] - for y in range(h): - mask_data.extend([int(255 * (y / h))] * w) - mask.putdata(mask_data) - base.paste(top, (0, 0), mask) - # 透明图层 - font = load_font("gorga.otf", 100) - alpha = Image.new("L", (w, h)) - draw = ImageDraw.Draw(alpha) - draw.text((20, -30), text, fill="white", font=font) - base.putalpha(alpha) - # stroke - bg = Image.new("RGBA", (w, h)) - draw = ImageDraw.Draw(bg) - draw.text( - (20, -30), - text, - font=font, - fill="gray", - stroke_width=3, - stroke_fill="gray", - ) - bg.paste(base, (0, 0), base) - return bg - - async def generate_img(self, card_list: list[OnmyojiChar]) -> BuildImage: - return await super().generate_img(card_list, num_per_line=10) - - async def generate_card_img(self, card: OnmyojiChar) -> BuildImage: - bg = BuildImage(73, 240, color="#F1EFE9") - img_path = str(self.img_path / f"{cn2py(card.name)}_mark_btn.png") - img = BuildImage(0, 0, background=img_path) - img = Image.open(img_path).convert("RGBA") - label = self.star_label(card.star).resize((60, 33), Image.ANTIALIAS) - await bg.paste(img, (0, 0)) - await bg.paste(label, (0, 135)) - font = load_font("msyh.ttf", 16) - draw = ImageDraw.Draw(bg.markImg) - text = "\n".join([t for t in card.name[:4]]) - _, text_h = font.getsize_multiline(text, spacing=0) - draw.text( - (40, 150 + (90 - text_h) / 2), text, font=font, fill="gray", spacing=0 - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - OnmyojiChar( - name=value["名称"], - star=["N", "R", "SR", "SSR", "SP"].index(value["星级"]) + 1, - limited=( - True - if key - in [ - "奴良陆生", - "卖药郎", - "鬼灯", - "阿香", - "蜜桃&芥子", - "犬夜叉", - "杀生丸", - "桔梗", - "朽木露琪亚", - "黑崎一护", - "灶门祢豆子", - "灶门炭治郎", - ] - else False - ), - ) - for key, value in self.load_data().items() - ] - - async def _update_info(self): - info = {} - url = "https://yys.res.netease.com/pc/zt/20161108171335/js/app/all_shishen.json?v74=" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - data = json.loads(result) - for x in data: - name = remove_prohibited_str(x["name"]) - member_dict = { - "id": x["id"], - "名称": name, - "星级": x["level"], - } - info[name] = member_dict - # logger.info(f"{name} is update...") - # 更新头像 - for key in info.keys(): - url = f'https://yys.163.com/shishen/{info[key]["id"]}.html' - result = await self.get_url(url) - if not result: - info[key]["头像"] = "" - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - avatar = dom.xpath("//div[@class='pic_wrap']/img/@src")[0] - avatar = "https:" + avatar - info[key]["头像"] = avatar - except IndexError: - info[key]["头像"] = "" - logger.warning(f"{self.game_name_cn} 获取头像错误 {key}") - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载书签形式的头像 - url = f"https://yys.res.netease.com/pc/zt/20161108171335/data/mark_btn/{value['id']}.png" - await self.download_img(url, value["名称"] + "_mark_btn") diff --git a/zhenxun/plugins/draw_card/handles/pcr_handle.py b/zhenxun/plugins/draw_card/handles/pcr_handle.py deleted file mode 100644 index 666a6842..00000000 --- a/zhenxun/plugins/draw_card/handles/pcr_handle.py +++ /dev/null @@ -1,149 +0,0 @@ -import random -from urllib.parse import unquote - -from lxml import etree -from PIL import ImageDraw - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle - - -class PcrChar(BaseData): - pass - - -class PcrHandle(BaseHandle[PcrChar]): - def __init__(self): - super().__init__("pcr", "公主连结") - self.max_star = 3 - self.config = draw_config.pcr - self.ALL_CHAR: list[PcrChar] = [] - - def get_card(self, mode: int = 1) -> PcrChar: - if mode == 2: - star = self.get_star( - [3, 2], [self.config.PCR_G_THREE_P, self.config.PCR_G_TWO_P] - ) - else: - star = self.get_star( - [3, 2, 1], - [self.config.PCR_THREE_P, self.config.PCR_TWO_P, self.config.PCR_ONE_P], - ) - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - return random.choice(chars) - - def get_cards(self, count: int, **kwargs) -> list[tuple[PcrChar, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(2) - card_count = 0 - else: - card = self.get_card(1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - async def generate_card_img(self, card: PcrChar) -> BuildImage: - sep_w = 5 - sep_h = 5 - star_h = 15 - img_w = 90 - img_h = 90 - font_h = 20 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5") - star_path = str(self.img_path / "star.png") - star = BuildImage(star_h, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await bg.paste(img, (sep_w, sep_h)) - for i in range(card.star): - await bg.paste(star, (sep_w + img_w - star_h * (i + 1), sep_h)) - # 加名字 - text = card.name[:5] + "..." if len(card.name) > 6 else card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - PcrChar( - name=value["名称"], - star=int(value["星级"]), - limited=True if "(" in key else False, - ) - for key, value in self.load_data().items() - ] - - async def _update_info(self): - info = {} - if draw_config.PCR_TAI: - url = "https://wiki.biligame.com/pcr/角色图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - # TODO: PCR台湾更新失败 - char_list = dom.xpath( - "//*[@id='CardSelectCard']/div[@class='unit-icon trcard']" - ) - for char in char_list: - try: - name = char.xpath("./a/@title")[0] - avatar = char.xpath("./a/img/@srcset")[0] - star = len(char.xpath("./div[1]/img")) - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": star, - } - info[member_dict["名称"]] = member_dict - else: - url = "https://wiki.biligame.com/pcr/角色筛选表" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = char.xpath("./td[4]/text()")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()), - } - info[member_dict["名称"]] = member_dict - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - await self.download_img( - "https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png", - "star", - ) diff --git a/zhenxun/plugins/draw_card/handles/pretty_handle.py b/zhenxun/plugins/draw_card/handles/pretty_handle.py deleted file mode 100644 index 535e2b19..00000000 --- a/zhenxun/plugins/draw_card/handles/pretty_handle.py +++ /dev/null @@ -1,423 +0,0 @@ -import random -import re -from datetime import datetime -from urllib.parse import unquote - -import dateparser -import ujson as json -from bs4 import BeautifulSoup -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class PrettyData(BaseData): - pass - - -class PrettyChar(PrettyData): - pass - - -class PrettyCard(PrettyData): - @property - def star_str(self) -> str: - return ["R", "SR", "SSR"][self.star - 1] - - -class PrettyHandle(BaseHandle[PrettyData]): - def __init__(self): - super().__init__("pretty", "赛马娘") - self.data_files.append("pretty_card.json") - self.max_star = 3 - self.game_card_color = "#eff2f5" - self.config = draw_config.pretty - - self.ALL_CHAR: list[PrettyChar] = [] - self.ALL_CARD: list[PrettyCard] = [] - self.UP_CHAR: UpEvent | None = None - self.UP_CARD: UpEvent | None = None - - def get_card(self, pool_name: str, mode: int = 1) -> PrettyData: - if mode == 1: - star = self.get_star( - [3, 2, 1], - [ - self.config.PRETTY_THREE_P, - self.config.PRETTY_TWO_P, - self.config.PRETTY_ONE_P, - ], - ) - else: - star = self.get_star( - [3, 2], [self.config.PRETTY_THREE_P, self.config.PRETTY_TWO_P] - ) - up_pool = None - if pool_name == "char": - up_pool = self.UP_CHAR - all_list = self.ALL_CHAR - else: - up_pool = self.UP_CARD - all_list = self.ALL_CARD - - all_char = [x for x in all_list if x.star == star and not x.limited] - acquire_char = None - # 有UP池子 - if up_pool and star in [x.star for x in up_pool.up_char]: - up_list = [x.name for x in up_pool.up_char if x.star == star] - # 抽到UP - if random.random() < 1 / len(all_char) * (0.7 / 0.1385): - up_name = random.choice(up_list) - try: - acquire_char = [x for x in all_list if x.name == up_name][0] - except IndexError: - pass - if not acquire_char: - acquire_char = random.choice(all_char) - return acquire_char - - def get_cards(self, count: int, pool_name: str) -> list[tuple[PrettyData, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(pool_name, 2) - card_count = 0 - else: - card = self.get_card(pool_name, 1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - def format_pool_info(self, pool_name: str) -> str: - info = "" - up_event = self.UP_CHAR if pool_name == "char" else self.UP_CARD - if up_event: - star3_list = [x.name for x in up_event.up_char if x.star == 3] - star2_list = [x.name for x in up_event.up_char if x.star == 2] - star1_list = [x.name for x in up_event.up_char if x.star == 1] - if star3_list: - if pool_name == "char": - info += f'三星UP:{" ".join(star3_list)}\n' - else: - info += f'SSR UP:{" ".join(star3_list)}\n' - if star2_list: - if pool_name == "char": - info += f'二星UP:{" ".join(star2_list)}\n' - else: - info += f'SR UP:{" ".join(star2_list)}\n' - if star1_list: - if pool_name == "char": - info += f'一星UP:{" ".join(star1_list)}\n' - else: - info += f'R UP:{" ".join(star1_list)}\n' - info = f"当前up池:{up_event.title}\n{info}" - return info.strip() - - async def draw(self, count: int, pool_name: str, **kwargs) -> UniMessage: - pool_name = "char" if not pool_name else pool_name - index2card = self.get_cards(count, pool_name) - cards = [card[0] for card in index2card] - up_event = self.UP_CHAR if pool_name == "char" else self.UP_CARD - up_list = [x.name for x in up_event.up_char] if up_event else [] - result = self.format_result(index2card, up_list=up_list) - pool_info = self.format_pool_info(pool_name) - img = await self.generate_img(cards) - return MessageUtils.build_message([pool_info, img, result]) - - async def generate_card_img(self, card: PrettyData) -> BuildImage: - if isinstance(card, PrettyChar): - star_h = 30 - img_w = 200 - img_h = 219 - font_h = 50 - bg = BuildImage(img_w, img_h + font_h, color="#EFF2F5") - star_path = str(self.img_path / "star.png") - star = BuildImage(star_h, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - star_w = star_h * card.star - for i in range(card.star): - await bg.paste(star, (int((img_w - star_w) / 2) + star_h * i, 0)) - await bg.paste(img, (0, 0)) - # 加名字 - text = card.name[:5] + "..." if len(card.name) > 6 else card.name - font = load_font(fontsize=30) - text_w, _ = font.getsize(text) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - ((img_w - text_w) / 2, img_h), - text, - font=font, - fill="gray", - ) - return bg - else: - sep_w = 10 - img_w = 200 - img_h = 267 - font_h = 75 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h, color="#EFF2F5") - label_path = str(self.img_path / f"{card.star}_label.png") - label = BuildImage(40, 40, background=label_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await bg.paste(img, (sep_w, 0)) - await bg.paste(label, (30, 3)) - # 加名字 - text = "" - texts = [] - font = load_font(fontsize=25) - for t in card.name: - if BuildImage.get_text_size((text + t), font)[0] > 190: - texts.append(text) - text = "" - if len(texts) >= 2: - texts[-1] += "..." - break - else: - text += t - if text: - texts.append(text) - text = "\n".join(texts) - text_w, _ = font.getsize_multiline(text) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - ((img_w - text_w) / 2, img_h), - text, - font=font, - align="center", - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - PrettyChar( - name=value["名称"], - star=int(value["初始星级"]), - limited=False, - ) - for value in self.load_data().values() - ] - self.ALL_CARD = [ - PrettyCard( - name=value["中文名"], - star=["R", "SR", "SSR"].index(value["稀有度"]) + 1, - limited=True if "卡池" not in value["获取方式"] else False, - ) - for value in self.load_data("pretty_card.json").values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_CHAR = UpEvent.parse_obj(data.get("char", {})) - self.UP_CARD = UpEvent.parse_obj(data.get("card", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_CHAR and self.UP_CARD: - data = { - "char": json.loads(self.UP_CHAR.json()), - "card": json.loads(self.UP_CARD.json()), - } - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - # pretty.json - pretty_info = {} - url = "https://wiki.biligame.com/umamusume/赛马娘图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = len(char.xpath("./td[3]/img")) - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "初始星级": star, - } - pretty_info[member_dict["名称"]] = member_dict - self.dump_data(pretty_info) - logger.info(f"{self.game_name_cn} 更新成功") - # pretty_card.json - pretty_card_info = {} - url = "https://wiki.biligame.com/umamusume/支援卡图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 卡牌出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/div/a/@title")[0] - name_cn = char.xpath("./td[3]/a/text()")[0] - avatar = char.xpath("./td[1]/div/a/img/@srcset")[0] - star = str(char.xpath("./td[5]/text()")[0]).strip() - sources = str(char.xpath("./td[7]/text()")[0]).strip() - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "中文名": remove_prohibited_str(name_cn), - "稀有度": star, - "获取方式": [sources] if sources else [], - } - pretty_card_info[member_dict["中文名"]] = member_dict - self.dump_data(pretty_card_info, "pretty_card.json") - logger.info(f"{self.game_name_cn} 卡牌更新成功") - # 下载头像 - for value in pretty_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in pretty_card_info.values(): - await self.download_img(value["头像"], value["中文名"]) - # 下载星星 - PRETTY_URL = "https://patchwiki.biligame.com/images/umamusume" - await self.download_img( - PRETTY_URL + "/1/13/e1hwjz4vmhtvk8wlyb7c0x3ld1s2ata.png", "star" - ) - # 下载稀有度标志 - idx = 1 - for url in [ - "/f/f7/afqs7h4snmvovsrlifq5ib8vlpu2wvk.png", - "/3/3b/d1jmpwrsk4irkes1gdvoos4ic6rmuht.png", - "/0/06/q23szwkbtd7pfkqrk3wcjlxxt9z595o.png", - ]: - await self.download_img(PRETTY_URL + url, f"{idx}_label") - idx += 1 - await self.update_up_char() - - async def update_up_char(self): - announcement_url = "https://wiki.biligame.com/umamusume/公告" - result = await self.get_url(announcement_url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - announcements = dom.xpath("//div[@id='mw-content-text']/div/div/span/a") - title = "" - url = "" - for announcement in announcements: - try: - title = announcement.xpath("./@title")[0] - url = "https://wiki.biligame.com/" + announcement.xpath("./@href")[0] - if re.match(r".*?\d{8}$", title) or re.match( - r"^\d{1,2}月\d{1,2}日.*?", title - ): - break - except IndexError: - continue - if not title: - logger.warning(f"{self.game_name_cn}未找到新UP公告") - return - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取UP公告出错") - return - try: - start_time = None - end_time = None - char_img = "" - card_img = "" - up_chars = [] - up_cards = [] - soup = BeautifulSoup(result, "lxml") - heads = soup.find_all("span", {"class": "mw-headline"}) - for head in heads: - if "时间" in head.text: - time = head.find_next("p").text.split("\n")[0] - if "~" in time: - start, end = time.split("~") - start_time = dateparser.parse(start) - end_time = dateparser.parse(end) - elif "赛马娘" in head.text: - char_img = head.find_next("a", {"class": "image"}).find("img")[ - "src" - ] - lines = str(head.find_next("p").text).split("\n") - chars = [ - line - for line in lines - if "★" in line and "(" in line and ")" in line - ] - for char in chars: - star = char.count("★") - name = re.split(r"[()]", char)[-2].strip() - up_chars.append( - UpChar(name=name, star=star, limited=False, zoom=70) - ) - elif "支援卡" in head.text: - card_img = head.find_next("a", {"class": "image"}).find("img")[ - "src" - ] - lines = str(head.find_next("p").text).split("\n") - cards = [ - line - for line in lines - if "R" in line and "(" in line and ")" in line - ] - for card in cards: - star = 3 if "SSR" in card else 2 if "SR" in card else 1 - name = re.split(r"[()]", card)[-2].strip() - up_cards.append( - UpChar(name=name, star=star, limited=False, zoom=70) - ) - if start_time and end_time: - if start_time <= datetime.now() <= end_time: - self.UP_CHAR = UpEvent( - title=title, - pool_img=char_img, - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.UP_CARD = UpEvent( - title=title, - pool_img=card_img, - start_time=start_time, - end_time=end_time, - up_char=up_cards, - ) - self.dump_up_char() - logger.info( - f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}" - ) - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错", e=e) - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_CHAR and self.UP_CARD: - return MessageUtils.build_message( - [ - f"重载成功!\n当前UP池子:{self.UP_CHAR.title}", - self.UP_CHAR.pool_img, - self.UP_CARD.pool_img, - ] - ) diff --git a/zhenxun/plugins/draw_card/handles/prts_handle.py b/zhenxun/plugins/draw_card/handles/prts_handle.py deleted file mode 100644 index 18a86fc3..00000000 --- a/zhenxun/plugins/draw_card/handles/prts_handle.py +++ /dev/null @@ -1,344 +0,0 @@ -import random -import re -from datetime import datetime -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from lxml.etree import _Element -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class Operator(BaseData): - recruit_only: bool # 公招限定 - event_only: bool # 活动获得干员 - core_only: bool # 中坚干员 - # special_only: bool # 升变/异格干员 - - -class PrtsHandle(BaseHandle[Operator]): - def __init__(self): - super().__init__(game_name="prts", game_name_cn="明日方舟") - self.max_star = 6 - self.game_card_color = "#eff2f5" - self.config = draw_config.prts - - self.ALL_OPERATOR: list[Operator] = [] - self.UP_EVENT: UpEvent | None = None - - def get_card(self, add: float) -> Operator: - star = self.get_star( - star_list=[6, 5, 4, 3], - probability_list=[ - self.config.PRTS_SIX_P + add, - self.config.PRTS_FIVE_P, - self.config.PRTS_FOUR_P, - self.config.PRTS_THREE_P, - ], - ) - - all_operators = [ - x - for x in self.ALL_OPERATOR - if x.star == star - and not any([x.limited, x.recruit_only, x.event_only, x.core_only]) - ] - acquire_operator = None - - if self.UP_EVENT: - up_operators = [x for x in self.UP_EVENT.up_char if x.star == star] - # UPs - try: - zooms = [x.zoom for x in up_operators] - zoom_sum = sum(zooms) - if random.random() < zoom_sum: - up_name = random.choices(up_operators, weights=zooms, k=1)[0].name - acquire_operator = [ - x for x in self.ALL_OPERATOR if x.name == up_name - ][0] - except IndexError: - pass - if not acquire_operator: - acquire_operator = random.choice(all_operators) - return acquire_operator - - def get_cards(self, count: int, **kwargs) -> list[tuple[Operator, int]]: - card_list = [] # 获取所有角色 - add = 0.0 - count_idx = 0 - for i in range(count): - count_idx += 1 - card = self.get_card(add) - if card.star == self.max_star: - add = 0.0 - count_idx = 0 - elif count_idx > 50: - add += 0.02 - card_list.append((card, i + 1)) - return card_list - - def format_pool_info(self) -> str: - info = "" - if self.UP_EVENT: - star6_list = [x.name for x in self.UP_EVENT.up_char if x.star == 6] - star5_list = [x.name for x in self.UP_EVENT.up_char if x.star == 5] - star4_list = [x.name for x in self.UP_EVENT.up_char if x.star == 4] - if star6_list: - info += f"六星UP:{' '.join(star6_list)}\n" - if star5_list: - info += f"五星UP:{' '.join(star5_list)}\n" - if star4_list: - info += f"四星UP:{' '.join(star4_list)}\n" - info = f"当前up池: {self.UP_EVENT.title}\n{info}" - return info.strip() - - async def draw(self, count: int, **kwargs) -> UniMessage: - index2card = self.get_cards(count) - """这里cards修复了抽卡图文不符的bug""" - cards = [card[0] for card in index2card] - up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else [] - result = self.format_result(index2card, up_list=up_list) - pool_info = self.format_pool_info() - img = await self.generate_img(cards) - return MessageUtils.build_message([pool_info, img, result]) - - async def generate_card_img(self, card: Operator) -> BuildImage: - sep_w = 5 - sep_h = 5 - star_h = 15 - img_w = 120 - img_h = 120 - font_h = 20 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5") - star_path = str(self.img_path / "star.png") - star = BuildImage(star_h, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await bg.paste(img, (sep_w, sep_h)) - for i in range(card.star): - await bg.paste(star, (sep_w + img_w - 5 - star_h * (i + 1), sep_h)) - # 加名字 - text = card.name[:7] + "..." if len(card.name) > 8 else card.name - font = load_font(fontsize=16) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_OPERATOR = [ - Operator( - name=value["名称"], - star=int(value["星级"]), - limited="标准寻访" not in value["获取途径"] - and "中坚寻访" not in value["获取途径"], - recruit_only=( - True - if "标准寻访" not in value["获取途径"] - and "中坚寻访" not in value["获取途径"] - and "公开招募" in value["获取途径"] - else False - ), - event_only=True if "活动获取" in value["获取途径"] else False, - core_only=( - True - if "标准寻访" not in value["获取途径"] - and "中坚寻访" in value["获取途径"] - else False - ), - ) - for key, value in self.load_data().items() - if "阿米娅" not in key - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - """这里的 waring 有点模糊,更新游戏信息时没有up池的情况下也会报错,所以细分了一下""" - if not data: - logger.warning(f"当前无UP池或 {self.game_name}_up_char.json 文件不存在") - else: - self.UP_EVENT = UpEvent.parse_obj(data.get("char", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_EVENT: - data = {"char": json.loads(self.UP_EVENT.json())} - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - """更新信息""" - info = {} - url = "https://wiki.biligame.com/arknights/干员数据表" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - char_list: list[_Element] = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - avatar = char.xpath("./td[1]/div/div/div/a/img/@srcset")[0] - name = char.xpath("./td[1]/center/a/text()")[0] - star = char.xpath("./td[2]/text()")[0] - """这里sources修好了干员获取标签有问题的bug,如三星只能抽到卡缇就是这个原因""" - sources = [_.strip("\n") for _ in char.xpath("./td[7]/text()")] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(str(name).strip()), - "星级": int(str(star).strip()), - "获取途径": sources, - } - info[member_dict["名称"]] = member_dict - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - await self.download_img( - "https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png", - "star", - ) - await self.update_up_char() - - async def update_up_char(self): - """重载卡池""" - announcement_url = "https://ak.hypergryph.com/news.html" - result = await self.get_url(announcement_url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - activity_urls = dom.xpath( - "//ol[@class='articlelist' and @data-category-key='ACTIVITY']/li/a/@href" - ) - start_time = None - end_time = None - up_chars = [] - pool_img = "" - for activity_url in activity_urls[:10]: # 减少响应时间, 10个就够了 - activity_url = f"https://ak.hypergryph.com{activity_url}" - result = await self.get_url(activity_url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告 {activity_url} 出错") - continue - - """因为鹰角的前端太自由了,这里重写了匹配规则以尽可能避免因为前端乱七八糟而导致的重载失败""" - dom = etree.HTML(result, etree.HTMLParser()) - contents = dom.xpath( - "//div[@class='article-content']/p/text() | //div[@class='article-content']/p/span/text() | //div[@class='article-content']/div[@class='media-wrap image-wrap']/img/@src" - ) - title = "" - time = "" - chars: list[str] = [] - for index, content in enumerate(contents): - if re.search("(.*)(寻访|复刻).*?开启", content): - title = re.split(r"[【】]", content) - title = "".join(title[1:-1]) if "-" in title else title[1] - lines = [ - contents[index - 2 + _] for _ in range(8) - ] # 从 -2 开始是因为xpath获取的时间有的会在寻访开启这一句之前 - lines.append("") # 防止IndexError,加个空字符串 - for idx, line in enumerate(lines): - match = re.search( - r"(\d{1,2}月\d{1,2}日.*?-.*?\d{1,2}月\d{1,2}日.*?$)", line - ) - if match: - time = match.group(1) - """因为

的诡异排版,所以有了下面的一段""" - if ("★★" in line and "%" in line) or ( - "★★" in line and "%" in lines[idx + 1] - ): - ( - chars.append(line) - if ("★★" in line and "%" in line) - else chars.append(line + lines[idx + 1]) - ) - if not time: - continue - start, end = ( - time.replace("月", "/").replace("日", " ").split("-")[:2] - ) # 日替换为空格是因为有日后面不接空格的情况,导致 split 出问题 - start_time = dateparser.parse(start) - end_time = dateparser.parse(end) - pool_img = contents[index - 2] - r"""两类格式:用/分割,用\分割;★+(概率)+名字,★+名字+(概率)""" - for char in chars: - star = char.split("(")[0].count("★") - name = ( - re.split(r"[:(]", char)[1] - if "★(" not in char - else re.split("):", char)[1] - ) # 有的括号在前面有的在后面 - dual_up = False - if "\\" in name: - names = name.split("\\") - dual_up = True - elif "/" in name: - names = name.split("/") - dual_up = True - else: - names = [name] # 既有用/分割的,又有用\分割的 - - names = [name.replace("[限定]", "").strip() for name in names] - zoom = 1 - if "权值" in char: - zoom = 0.03 - else: - match = re.search(r"(占.*?的.*?(\d+).*?%)", char) - if dual_up == True: - zoom = float(match.group(1)) / 2 - else: - zoom = float(match.group(1)) - zoom = zoom / 100 if zoom > 1 else zoom - for name in names: - up_chars.append( - UpChar(name=name, star=star, limited=False, zoom=zoom) - ) - break # 这里break会导致个问题:如果一个公告里有两个池子,会漏掉下面的池子,比如 5.19 的定向寻访。但目前我也没啥好想法解决 - if title and start_time and end_time: - if start_time <= datetime.now() <= end_time: - self.UP_EVENT = UpEvent( - title=title, - pool_img=pool_img, - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.dump_up_char() - logger.info( - f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}" - ) - break - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_EVENT: - return MessageUtils.build_message( - [ - f"重载成功!\n当前UP池子:{self.UP_EVENT.title}", - self.UP_EVENT.pool_img, - ] - ) diff --git a/zhenxun/plugins/draw_card/rule.py b/zhenxun/plugins/draw_card/rule.py deleted file mode 100644 index 49746d95..00000000 --- a/zhenxun/plugins/draw_card/rule.py +++ /dev/null @@ -1,10 +0,0 @@ -from nonebot.internal.rule import Rule - -from zhenxun.configs.config import Config - - -def rule(game) -> Rule: - async def _rule() -> bool: - return Config.get_config("draw_card", game.config_name, True) - - return Rule(_rule) diff --git a/zhenxun/plugins/draw_card/util.py b/zhenxun/plugins/draw_card/util.py deleted file mode 100644 index d0cefc91..00000000 --- a/zhenxun/plugins/draw_card/util.py +++ /dev/null @@ -1,61 +0,0 @@ -import platform -from pathlib import Path - -import pypinyin -from PIL import Image, ImageDraw, ImageFont -from PIL.Image import Image as IMG -from PIL.ImageFont import FreeTypeFont - -from zhenxun.configs.path_config import FONT_PATH -from zhenxun.utils._build_image import BuildImage - -dir_path = Path(__file__).parent.absolute() - - -def cn2py(word) -> str: - """保存声调,防止出现类似方舟干员红与吽拼音相同声调不同导致红照片无法保存的问题""" - temp = "" - for i in pypinyin.pinyin(word, style=pypinyin.Style.TONE3): - temp += "".join(i) - return temp - - -# 移除windows和linux下特殊字符 -def remove_prohibited_str(name: str) -> str: - if platform.system().lower() == "windows": - tmp = "" - for i in name: - if i not in ["\\", "/", ":", "*", "?", '"', "<", ">", "|"]: - tmp += i - name = tmp - else: - name = name.replace("/", "\\") - return name - - -def load_font(fontname: str = "msyh.ttf", fontsize: int = 16) -> FreeTypeFont: - return ImageFont.truetype( - str(FONT_PATH / f"{fontname}"), fontsize, encoding="utf-8" - ) - - -def circled_number(num: int) -> IMG: - font = load_font(fontsize=450) - text = str(num) - text_w = BuildImage.get_text_size(text, font=font)[0] - w = 240 + text_w - w = w if w >= 500 else 500 - img = Image.new("RGBA", (w, 500)) - draw = ImageDraw.Draw(img) - draw.ellipse(((0, 0), (500, 500)), fill="red") - draw.ellipse(((w - 500, 0), (w, 500)), fill="red") - draw.rectangle(((250, 0), (w - 250, 500)), fill="red") - draw.text( - (120, -60), - text, - font=font, - fill="white", - stroke_width=10, - stroke_fill="white", - ) - return img diff --git a/zhenxun/plugins/epic/__init__.py b/zhenxun/plugins/epic/__init__.py deleted file mode 100644 index 23c084aa..00000000 --- a/zhenxun/plugins/epic/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, UniMessage, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import get_epic_free - -__plugin_meta__ = PluginMetadata( - name="epic免费游戏", - description="可以不玩,不能没有,每日白嫖", - usage=""" - epic - """.strip(), - extra=PluginExtraData( - author="AkashiCoin", - version="0.1", - ).dict(), -) - -_matcher = on_alconna(Alconna("epic"), priority=5, block=True) - - -@_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - type_ = "Group" if gid else "Private" - msg_list, code = await get_epic_free(bot, type_) - if code == 404 and isinstance(msg_list, str): - await MessageUtils.build_message(msg_list).finish() - elif isinstance(bot, (v11Bot, v12Bot)) and isinstance(msg_list, list): - await bot.send_group_forward_msg(group_id=gid, messages=msg_list) - elif isinstance(msg_list, UniMessage): - await msg_list.send() - logger.info(f"获取epic免费游戏", arparma.header_result, session=session) diff --git a/zhenxun/plugins/epic/data_source.py b/zhenxun/plugins/epic/data_source.py deleted file mode 100644 index 87bc5a63..00000000 --- a/zhenxun/plugins/epic/data_source.py +++ /dev/null @@ -1,182 +0,0 @@ -from datetime import datetime - -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot_plugin_alconna import Image, UniMessage - -from zhenxun.configs.config import NICKNAME -from zhenxun.services.log import logger -from zhenxun.utils._build_image import BuildImage -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - - -# 获取所有 Epic Game Store 促销游戏 -# 方法参考:RSSHub /epicgames 路由 -# https://github.com/DIYgod/RSSHub/blob/master/lib/v2/epicgames/index.js -async def get_epic_game() -> dict | None: - epic_url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions?locale=zh-CN&country=CN&allowCountries=CN" - headers = { - "Referer": "https://www.epicgames.com/store/zh-CN/", - "Content-Type": "application/json; charset=utf-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36", - } - try: - res = await AsyncHttpx.get(epic_url, headers=headers, timeout=10) - res_json = res.json() - games = res_json["data"]["Catalog"]["searchStore"]["elements"] - return games - except Exception as e: - logger.error(f"Epic 访问接口错误", e=e) - return None - - -# 此处用于获取游戏简介 -async def get_epic_game_desp(name) -> dict | None: - desp_url = ( - "https://store-content-ipv4.ak.epicgames.com/api/zh-CN/content/products/" - + str(name) - ) - headers = { - "Referer": "https://store.epicgames.com/zh-CN/p/" + str(name), - "Content-Type": "application/json; charset=utf-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36", - } - try: - res = await AsyncHttpx.get(desp_url, headers=headers, timeout=10) - res_json = res.json() - gamesDesp = res_json["pages"][0]["data"]["about"] - return gamesDesp - except Exception as e: - logger.error(f"Epic 访问接口错误", e=e) - return None - - -# 获取 Epic Game Store 免费游戏信息 -# 处理免费游戏的信息方法借鉴 pip 包 epicstore_api 示例 -# https://github.com/SD4RK/epicstore_api/blob/master/examples/free_games_example.py -async def get_epic_free( - bot: Bot, type_event: str -) -> tuple[UniMessage | list | str, int]: - games = await get_epic_game() - if not games: - return "Epic 可能又抽风啦,请稍后再试(", 404 - else: - msg_list = [] - for game in games: - game_name = game["title"] - game_corp = game["seller"]["name"] - game_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - # 赋初值以避免 local variable referenced before assignment - game_thumbnail, game_dev, game_pub = None, game_corp, game_corp - try: - game_promotions = game["promotions"]["promotionalOffers"] - upcoming_promotions = game["promotions"]["upcomingPromotionalOffers"] - if not game_promotions and upcoming_promotions: - # 促销暂未上线,但即将上线 - promotion_data = upcoming_promotions[0]["promotionalOffers"][0] - start_date_iso, end_date_iso = ( - promotion_data["startDate"][:-1], - promotion_data["endDate"][:-1], - ) - # 删除字符串中最后一个 "Z" 使 Python datetime 可处理此时间 - start_date = datetime.fromisoformat(start_date_iso).strftime( - "%b.%d %H:%M" - ) - end_date = datetime.fromisoformat(end_date_iso).strftime( - "%b.%d %H:%M" - ) - if type_event == "Group": - _message = f"\n由 {game_corp} 公司发行的游戏 {game_name} ({game_price}) 在 UTC 时间 {start_date} 即将推出免费游玩,预计截至 {end_date}。" - msg_list.append(_message) - else: - msg = "\n由 {} 公司发行的游戏 {} ({}) 在 UTC 时间 {} 即将推出免费游玩,预计截至 {}。".format( - game_corp, game_name, game_price, start_date, end_date - ) - msg_list.append(msg) - else: - for image in game["keyImages"]: - if ( - image.get("url") - and not game_thumbnail - and image["type"] - in [ - "Thumbnail", - "VaultOpened", - "DieselStoreFrontWide", - "OfferImageWide", - ] - ): - game_thumbnail = image["url"] - break - for pair in game["customAttributes"]: - if pair["key"] == "developerName": - game_dev = pair["value"] - if pair["key"] == "publisherName": - game_pub = pair["value"] - if game.get("productSlug"): - if gamesDesp := await get_epic_game_desp(game["productSlug"]): - try: - # 是否存在简短的介绍 - if "shortDescription" in gamesDesp: - game_desp = gamesDesp["shortDescription"] - except KeyError: - game_desp = gamesDesp["description"] - else: - game_desp = game["description"] - try: - end_date_iso = game["promotions"]["promotionalOffers"][0][ - "promotionalOffers" - ][0]["endDate"][:-1] - end_date = datetime.fromisoformat(end_date_iso).strftime( - "%b.%d %H:%M" - ) - except IndexError: - end_date = "未知" - # API 返回不包含游戏商店 URL,此处自行拼接,可能出现少数游戏 404 请反馈 - if game.get("productSlug"): - game_url = "https://store.epicgames.com/zh-CN/p/{}".format( - game["productSlug"].replace("/home", "") - ) - elif game.get("url"): - game_url = game["url"] - else: - slugs = ( - [ - x["pageSlug"] - for x in game.get("offerMappings", []) - if x.get("pageType") == "productHome" - ] - + [ - x["pageSlug"] - for x in game.get("catalogNs", {}).get("mappings", []) - if x.get("pageType") == "productHome" - ] - + [ - x["value"] - for x in game.get("customAttributes", []) - if "productSlug" in x.get("key") - ] - ) - game_url = "https://store.epicgames.com/zh-CN{}".format( - f"/p/{slugs[0]}" if len(slugs) else "" - ) - if isinstance(bot, (v11Bot, v12Bot)) and type_event == "Group": - _message = [ - Image(url=game_thumbnail), - f"\nFREE now :: {game_name} ({game_price})\n{game_desp}\n此游戏由 {game_dev} 开发、{game_pub} 发行,将在 UTC 时间 {end_date} 结束免费游玩,戳链接速度加入你的游戏库吧~\n{game_url}\n", - ] - msg_list.append(_message) - else: - _message = [] - if game_thumbnail: - _message.append(Image(url=game_thumbnail)) - _message.append( - f"\n\nFREE now :: {game_name} ({game_price})\n{game_desp}\n此游戏由 {game_dev} 开发、{game_pub} 发行,将在 UTC 时间 {end_date} 结束免费游玩,戳链接速度加入你的游戏库吧~\n{game_url}\n" - ) - return MessageUtils.build_message(_message), 200 - except TypeError as e: - # logger.info(str(e)) - pass - return MessageUtils.template2forward(msg_list, bot.self_id), 200 diff --git a/zhenxun/plugins/fudu.py b/zhenxun/plugins/fudu.py deleted file mode 100644 index b81a0747..00000000 --- a/zhenxun/plugins/fudu.py +++ /dev/null @@ -1,151 +0,0 @@ -import random - -from nonebot import on_message -from nonebot.adapters import Event -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Image as alcImg -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task -from zhenxun.models.task_info import TaskInfo -from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import get_download_image_hash -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_group - -__plugin_meta__ = PluginMetadata( - name="复读", - description="群友的本质是什么?是复读机哒!", - usage=""" - usage: - 重复3次相同的消息时会复读 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - tasks=[Task(module="fudu", name="复读")], - configs=[ - RegisterConfig( - key="FUDU_PROBABILITY", - value=0.7, - help="复读概率", - default_value=0.7, - type=float, - ), - RegisterConfig( - module="_task", - key="DEFAULT_FUDU", - value=True, - help="被动 复读 进群默认开关状态", - default_value=True, - type=bool, - ), - ], - ).dict(), -) - - -class Fudu: - def __init__(self): - self.data = {} - - def append(self, key, content): - self._create(key) - self.data[key]["data"].append(content) - - def clear(self, key): - self._create(key) - self.data[key]["data"] = [] - self.data[key]["is_repeater"] = False - - def size(self, key) -> int: - self._create(key) - return len(self.data[key]["data"]) - - def check(self, key, content) -> bool: - self._create(key) - return self.data[key]["data"][0] == content - - def get(self, key): - self._create(key) - return self.data[key]["data"][0] - - def is_repeater(self, key): - self._create(key) - return self.data[key]["is_repeater"] - - def set_repeater(self, key): - self._create(key) - self.data[key]["is_repeater"] = True - - def _create(self, key): - if self.data.get(key) is None: - self.data[key] = {"is_repeater": False, "data": []} - - -_manage = Fudu() - - -base_config = Config.get("fudu") - - -_matcher = on_message(rule=ensure_group, priority=999) - - -@_matcher.handle() -async def _(message: UniMsg, event: Event, session: EventSession): - group_id = session.id2 or "" - if await TaskInfo.is_block("fudu", group_id): - return - if event.is_tome(): - return - plain_text = message.extract_plain_text() - image_list = [] - for m in message: - if isinstance(m, alcImg): - if m.url: - image_list.append(m.url) - if not plain_text and not image_list: - return - if plain_text and plain_text.startswith(f"@可爱的{NICKNAME}"): - await MessageUtils.build_message("复制粘贴的虚空艾特?").send(reply_to=True) - if image_list: - img_hash = await get_download_image_hash(image_list[0], group_id) - else: - img_hash = "" - add_msg = plain_text + "|-|" + img_hash - if _manage.size(group_id) == 0: - _manage.append(group_id, add_msg) - elif _manage.check(group_id, add_msg): - _manage.append(group_id, add_msg) - else: - _manage.clear(group_id) - _manage.append(group_id, add_msg) - if _manage.size(group_id) > 2: - if random.random() < base_config.get( - "FUDU_PROBABILITY" - ) and not _manage.is_repeater(group_id): - if random.random() < 0.2: - if plain_text.startswith("打断施法"): - await MessageUtils.build_message("打断" + plain_text).finish() - else: - await MessageUtils.build_message("打断施法!").finish() - _manage.set_repeater(group_id) - rst = None - if image_list and plain_text: - rst = MessageUtils.build_message( - [plain_text, TEMP_PATH / f"compare_download_{group_id}_img.jpg"] - ) - elif image_list: - rst = MessageUtils.build_message( - TEMP_PATH / f"compare_download_{group_id}_img.jpg" - ) - elif plain_text: - rst = MessageUtils.build_message(plain_text) - if rst: - await rst.finish() diff --git a/zhenxun/plugins/gold_redbag/__init__.py b/zhenxun/plugins/gold_redbag/__init__.py deleted file mode 100644 index f46c6639..00000000 --- a/zhenxun/plugins/gold_redbag/__init__.py +++ /dev/null @@ -1,349 +0,0 @@ -import time -import uuid -from datetime import datetime, timedelta - -from apscheduler.jobstores.base import JobLookupError -from nonebot.adapters import Bot -from nonebot.exception import ActionFailed -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Args, Arparma, At, Match, Option, on_alconna -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.depends import GetConfig, UserName -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.rules import ensure_group - -from .config import FESTIVE_KEY, FestiveRedBagManage -from .data_source import RedBagManager - -__plugin_meta__ = PluginMetadata( - name="金币红包", - description="运气项目又来了", - usage=""" - 塞红包 [金币数] ?[红包数=5] ?[at指定人]: 塞入红包 - 开/抢: 打开红包 - 退回红包: 退回未开完的红包,必须在一分钟后使用 - - * 不同群组同一个节日红包用户只能开一次 - - 示例: - 塞红包 1000 - 塞红包 1000 10 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - superuser_help=""" - 节日红包 [金额] [红包数] ?[指定主题文字] ? -g [群id] [群id] ... - - * 不同群组同一个节日红包用户只能开一次 - - 示例: - 节日红包 10000 20 今日出道贺金 - 节日红包 10000 20 明日出道贺金 -g 123123123 - - """, - configs=[ - RegisterConfig( - key="DEFAULT_TIMEOUT", - value=600, - help="普通红包默认超时时间", - default_value=600, - type=int, - ), - RegisterConfig( - key="DEFAULT_INTERVAL", - value=60, - help="用户发送普通红包最小间隔时间", - default_value=60, - type=int, - ), - RegisterConfig( - key="RANK_NUM", - value=10, - help="结算排行显示前N位", - default_value=10, - type=int, - ), - ], - limits=[PluginCdBlock(result="急什么急什么,待会再发!")], - ).dict(), -) - - -# def rule(session: EventSession) -> bool: -# if gid := session.id3 or session.id2: -# if group_red_bag := RedBagManager.get_group_data(gid): -# return group_red_bag.check_open(gid) -# return False - - -# async def rule_group(session: EventSession): -# return rule(session) and ensure_group(session) - - -_red_bag_matcher = on_alconna( - Alconna("塞红包", Args["amount", int]["num", int, 5]["user?", At]), - aliases={"金币红包"}, - priority=5, - block=True, - rule=ensure_group, -) - -_open_matcher = on_alconna( - Alconna("开"), - aliases={"抢", "开红包", "抢红包"}, - priority=5, - block=True, - rule=ensure_group, -) - -_return_matcher = on_alconna( - Alconna("退回红包"), aliases={"退还红包"}, priority=5, block=True, rule=ensure_group -) - -_festive_matcher = on_alconna( - Alconna( - "节日红包", - Args["amount", int]["num", int]["text?", str], - Option("-g|--group", Args["groups", str] / "\n", help_text="指定群"), - ), - priority=1, - block=True, - permission=SUPERUSER, - rule=to_me(), -) - - -@_red_bag_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - amount: int, - num: int, - user: Match[At], - default_interval: int = GetConfig(config="DEFAULT_INTERVAL"), - user_name: str = UserName(), -): - at_user = None - if user.available: - at_user = user.result.target - # group_id = session.id3 or session.id2 - group_id = session.id2 - """以频道id为键""" - user_id = session.id1 - if not user_id: - await MessageUtils.build_message("用户id为空").finish() - if not group_id: - await MessageUtils.build_message("群组id为空").finish() - group_red_bag = RedBagManager.get_group_data(group_id) - # 剩余过期时间 - time_remaining = group_red_bag.check_timeout(user_id) - if time_remaining != -1: - # 判断用户红包是否存在且是否过时覆盖 - if user_red_bag := group_red_bag.get_user_red_bag(user_id): - now = time.time() - if now < user_red_bag.start_time + default_interval: - await MessageUtils.build_message( - f"你的红包还没消化完捏...还剩下 {user_red_bag.num - len(user_red_bag.open_user)} 个! 请等待红包领取完毕..." - f"(或等待{time_remaining}秒红包cd)" - ).finish() - result = await RedBagManager.check_gold(user_id, amount, session.platform) - if result: - await MessageUtils.build_message(result).finish(at_sender=True) - await group_red_bag.add_red_bag( - f"{user_name}的红包", - int(amount), - 1 if at_user else num, - user_name, - user_id, - assigner=at_user, - platform=session.platform, - ) - image = await RedBagManager.random_red_bag_background( - user_id, platform=session.platform - ) - message_list: list = [f"{user_name}发起了金币红包\n金额: {amount}\n数量: {num}\n"] - if at_user: - message_list.append("指定人: ") - message_list.append(At(flag="user", target=at_user)) - message_list.append("\n") - message_list.append(image) - await MessageUtils.build_message(message_list).send() - - logger.info( - f"塞入 {num} 个红包,共 {amount} 金币", arparma.header_result, session=session - ) - - -@_open_matcher.handle() -async def _( - session: EventSession, - rank_num: int = GetConfig(config="RANK_NUM"), -): - # group_id = session.id3 or session.id2 - group_id = session.id2 - """以频道id为键""" - user_id = session.id1 - if not user_id: - await MessageUtils.build_message("用户id为空").finish() - if not group_id: - await MessageUtils.build_message("群组id为空").finish() - if group_red_bag := RedBagManager.get_group_data(group_id): - open_data, settlement_list = await group_red_bag.open(user_id, session.platform) - # send_msg = Text("没有红包给你开!") - send_msg = [] - for _, item in open_data.items(): - amount, red_bag = item - result_image = await RedBagManager.build_open_result_image( - red_bag, user_id, amount, session.platform - ) - send_msg.append(f"开启了 {red_bag.promoter} 的红包, 获取 {amount} 个金币\n") - send_msg.append(result_image) - send_msg.append("\n") - logger.info( - f"抢到了 {red_bag.promoter}({red_bag.promoter_id}) 的红包,获取了{amount}个金币", - "开红包", - session=session, - ) - send_msg = ( - MessageUtils.build_message(send_msg[:-1]) - if send_msg - else MessageUtils.build_message("没有红包给你开!") - ) - await send_msg.send(reply_to=True) - if settlement_list: - for red_bag in settlement_list: - result_image = await red_bag.build_amount_rank( - rank_num, session.platform - ) - await MessageUtils.build_message( - [f"{red_bag.name}已结算\n", result_image] - ).send() - - -@_return_matcher.handle() -async def _( - session: EventSession, - default_interval: int = GetConfig(config="DEFAULT_INTERVAL"), - rank_num: int = GetConfig(config="RANK_NUM"), -): - group_id = session.id3 or session.id2 - user_id = session.id1 - if not user_id: - await MessageUtils.build_message("用户id为空").finish() - if not group_id: - await MessageUtils.build_message("群组id为空").finish() - if group_red_bag := RedBagManager.get_group_data(group_id): - if user_red_bag := group_red_bag.get_user_red_bag(user_id): - now = time.time() - if now - user_red_bag.start_time < default_interval: - await MessageUtils.build_message( - f"你的红包还没有过时, 在 {int(default_interval - now + user_red_bag.start_time)} " - f"秒后可以退回..." - ).finish(reply_to=True) - user_red_bag = group_red_bag.get_user_red_bag(user_id) - if user_red_bag and ( - data := await group_red_bag.settlement(user_id, session.platform) - ): - image_result = await user_red_bag.build_amount_rank( - rank_num, session.platform - ) - logger.info(f"退回了红包 {data[0]} 金币", "红包退回", session=session) - await MessageUtils.build_message( - [ - f"已成功退还了 " f"{data[0]} 金币\n", - image_result, - ] - ).finish(reply_to=True) - await MessageUtils.build_message("目前没有红包可以退回...").finish(reply_to=True) - - -@_festive_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - amount: int, - num: int, - text: Match[str], - groups: Match[str], -): - greetings = "恭喜发财 大吉大利" - if text.available: - greetings = text.result - gl = [] - if groups.available: - gl = groups.result.strip().split() - else: - g_l, platform = await PlatformUtils.get_group_list(bot) - gl = [g.channel_id or g.group_id for g in g_l] - _uuid = str(uuid.uuid1()) - FestiveRedBagManage.add(_uuid) - _suc_cnt = 0 - for g in gl: - if target := PlatformUtils.get_target(bot, group_id=g): - group_red_bag = RedBagManager.get_group_data(g) - if festive_red_bag := group_red_bag.get_festive_red_bag(): - group_red_bag.remove_festive_red_bag() - if festive_red_bag.uuid: - FestiveRedBagManage.remove(festive_red_bag.uuid) - rank_image = await festive_red_bag.build_amount_rank(10, platform) - try: - await MessageUtils.build_message( - [ - f"{NICKNAME}的节日红包过时了,一共开启了 " - f"{len(festive_red_bag.open_user)}" - f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n", - rank_image, - ] - ).send(target=target, bot=bot) - except ActionFailed: - pass - try: - scheduler.remove_job(f"{FESTIVE_KEY}_{g}") - await RedBagManager.end_red_bag( - g, is_festive=True, platform=session.platform - ) - except JobLookupError: - pass - await group_red_bag.add_red_bag( - f"{NICKNAME}的红包", - amount, - num, - NICKNAME, - FESTIVE_KEY, - _uuid, - platform=session.platform, - ) - scheduler.add_job( - RedBagManager._auto_end_festive_red_bag, - "date", - run_date=(datetime.now() + timedelta(hours=24)).replace(microsecond=0), - id=f"{FESTIVE_KEY}_{g}", - args=[bot, g, session.platform], - ) - try: - image_result = await RedBagManager.random_red_bag_background( - bot.self_id, greetings, session.platform - ) - await MessageUtils.build_message( - [ - f"{NICKNAME}发起了节日金币红包\n金额: {amount}\n数量: {num}\n", - image_result, - ] - ).send(target=target, bot=bot) - _suc_cnt += 1 - logger.debug("节日红包图片信息发送成功...", "节日红包", group_id=g) - except ActionFailed: - logger.warning(f"节日红包图片信息发送失败...", "节日红包", group_id=g) - if gl: - await MessageUtils.build_message( - f"节日红包发送成功,累计成功发送 {_suc_cnt} 个群组!" - ).send() diff --git a/zhenxun/plugins/gold_redbag/config.py b/zhenxun/plugins/gold_redbag/config.py deleted file mode 100644 index da8c0d39..00000000 --- a/zhenxun/plugins/gold_redbag/config.py +++ /dev/null @@ -1,372 +0,0 @@ -import random -import time -from io import BytesIO -from typing import Dict - -from pydantic import BaseModel - -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.models.user_console import UserConsole -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.utils import get_user_avatar - -from .model import RedbagUser - -FESTIVE_KEY = "FESTIVE" -"""节日红包KEY""" - - -class FestiveRedBagManage: - - _data: Dict[str, list[str]] = {} - - @classmethod - def add(cls, uuid: str): - cls._data[uuid] = [] - - @classmethod - def open(cls, uuid: str, uid: str): - if uuid in cls._data and uid not in cls._data[uuid]: - cls._data[uuid].append(uid) - - @classmethod - def remove(cls, uuid: str): - if uuid in cls._data: - del cls._data[uuid] - - @classmethod - def check(cls, uuid: str, uid: str): - if uuid in cls._data: - return uid not in cls._data[uuid] - return False - - -class RedBag(BaseModel): - """ - 红包 - """ - - group_id: str - """所属群聊""" - name: str - """红包名称""" - amount: int - """总金币""" - num: int - """红包数量""" - promoter: str - """发起人昵称""" - promoter_id: str - """发起人id""" - is_festival: bool - """是否为节日红包""" - timeout: int - """过期时间""" - assigner: str | None = None - """指定人id""" - start_time: float - """红包发起时间""" - open_user: Dict[str, int] = {} - """开启用户""" - red_bag_list: list[int] - """红包金额列表""" - uuid: str | None - """uuid""" - - async def build_amount_rank(self, num: int, platform: str) -> BuildImage: - """生成结算红包图片 - - 参数: - num: 查看的排名数量. - platform: 平台. - - 返回: - BuildImage: 结算红包图片 - """ - user_image_list = [] - if self.open_user: - sort_data = sorted( - self.open_user.items(), key=lambda item: item[1], reverse=True - ) - num = num if num < len(self.open_user) else len(self.open_user) - user_id_list = [sort_data[i][0] for i in range(num)] - group_user_list = await GroupInfoUser.filter( - group_id=self.group_id, user_id__in=user_id_list - ).all() - for i in range(num): - user_background = BuildImage(600, 100, font_size=30) - user_id, amount = sort_data[i] - user_ava_bytes = await PlatformUtils.get_user_avatar(user_id, platform) - user_ava = None - if user_ava_bytes: - user_ava = BuildImage(80, 80, background=BytesIO(user_ava_bytes)) - else: - user_ava = BuildImage(80, 80) - await user_ava.circle_corner(10) - await user_background.paste(user_ava, (130, 10)) - no_image = BuildImage(100, 100, font_size=65, font="CJGaoDeGuo.otf") - await no_image.text((0, 0), f"{i+1}", center_type="center") - await no_image.line((99, 10, 99, 90), "#b9b9b9") - await user_background.paste(no_image) - name = [ - user.user_name - for user in group_user_list - if user_id == user.user_id - ] - await user_background.text((225, 15), name[0] if name else "") - amount_image = await BuildImage.build_text_image( - f"{amount} 元", size=30, font_color="#cdac72" - ) - await user_background.paste( - amount_image, (user_background.width - amount_image.width - 20, 50) - ) - await user_background.line((225, 99, 590, 99), "#b9b9b9") - user_image_list.append(user_background) - background = BuildImage(600, 150 + len(user_image_list) * 100) - top = BuildImage(600, 100, color="#f55545", font_size=30) - promoter_ava_bytes = await PlatformUtils.get_user_avatar( - self.promoter_id, platform - ) - promoter_ava = None - if promoter_ava_bytes: - promoter_ava = BuildImage(60, 60, background=BytesIO(promoter_ava_bytes)) - else: - promoter_ava = BuildImage(60, 60) - await promoter_ava.circle() - await top.paste(promoter_ava, (10, 0), "height") - await top.text((80, 33), self.name, (255, 255, 255)) - right_text = BuildImage(150, 100, color="#f55545", font_size=30) - await right_text.text((10, 33), "结算排行", (255, 255, 255)) - await right_text.line((4, 10, 4, 90), (255, 255, 255), 2) - await top.paste(right_text, (460, 0)) - await background.paste(top) - cur_h = 110 - for user_image in user_image_list: - await background.paste(user_image, (0, cur_h)) - cur_h += user_image.height - return background - - -class GroupRedBag: - """ - 群组红包管理 - """ - - def __init__(self, group_id: str): - self.group_id = group_id - self._data: Dict[str, RedBag] = {} - """红包列表""" - - def remove_festive_red_bag(self): - """删除节日红包""" - _key = None - for k, red_bag in self._data.items(): - if red_bag.is_festival: - _key = k - break - if _key: - del self._data[_key] - - def get_festive_red_bag(self) -> RedBag | None: - """获取节日红包 - - 返回: - RedBag | None: 节日红包 - """ - for _, red_bag in self._data.items(): - if red_bag.is_festival: - return red_bag - return None - - def get_user_red_bag(self, user_id: str) -> RedBag | None: - """获取用户塞红包数据 - - 参数: - user_id: 用户id - - 返回: - RedBag | None: RedBag - """ - return self._data.get(str(user_id)) - - def check_open(self, user_id: str) -> bool: - """检查是否有可开启的红包 - - 参数: - user_id: 用户id - - 返回: - bool: 是否有可开启的红包 - """ - user_id = str(user_id) - for _, red_bag in self._data.items(): - if red_bag.assigner: - if red_bag.assigner == user_id: - return True - else: - if user_id not in red_bag.open_user: - return True - return False - - def check_timeout(self, user_id: str) -> int: - """判断用户红包是否过期 - - 参数: - user_id: 用户id - - 返回: - int: 距离过期时间 - """ - if user_id in self._data: - reg_bag = self._data[user_id] - now = time.time() - if now < reg_bag.timeout + reg_bag.start_time: - return int(reg_bag.timeout + reg_bag.start_time - now) - return -1 - - async def open( - self, user_id: str, platform: str | None = None - ) -> tuple[Dict[str, tuple[int, RedBag]], list[RedBag]]: - """开启红包 - - 参数: - user_id: 用户id - platform: 所属平台 - - 返回: - Dict[str, tuple[int, RedBag]]: 键为发起者id, 值为开启金额以及对应RedBag - list[RedBag]: 开完的红包 - """ - open_data = {} - settlement_list: list[RedBag] = [] - for _, red_bag in self._data.items(): - if red_bag.num > len(red_bag.open_user): - if red_bag.is_festival and red_bag.uuid: - if not FestiveRedBagManage.check(red_bag.uuid, user_id): - continue - FestiveRedBagManage.open(red_bag.uuid, user_id) - is_open = False - if red_bag.assigner: - is_open = red_bag.assigner == user_id - else: - is_open = user_id not in red_bag.open_user - if is_open: - random_amount = red_bag.red_bag_list.pop() - await RedbagUser.add_redbag_data( - user_id, self.group_id, "get", random_amount - ) - await UserConsole.add_gold( - user_id, random_amount, "gold_redbag", platform - ) - red_bag.open_user[user_id] = random_amount - open_data[red_bag.promoter_id] = (random_amount, red_bag) - if red_bag.num == len(red_bag.open_user): - # 红包开完,结算 - settlement_list.append(red_bag) - if settlement_list: - for uid in [red_bag.promoter_id for red_bag in settlement_list]: - if uid in self._data: - del self._data[uid] - return open_data, settlement_list - - def festive_red_bag_expire(self) -> RedBag | None: - """节日红包过期 - - 返回: - RedBag | None: 过期的节日红包 - """ - if FESTIVE_KEY in self._data: - red_bag = self._data[FESTIVE_KEY] - del self._data[FESTIVE_KEY] - return red_bag - return None - - async def settlement( - self, user_id: str, platform: str | None = None - ) -> tuple[int | None, RedBag | None]: - """红包退回 - - 参数: - user_id: 用户id, 指定id时结算指定用户红包. - platform: 用户平台 - - 返回: - tuple[int | None, RedBag | None]: 退回金币, 红包 - """ - if red_bag := self._data.get(user_id): - del self._data[user_id] - if red_bag.is_festival and red_bag.uuid: - FestiveRedBagManage.remove(red_bag.uuid) - if red_bag.red_bag_list: - """退还剩余金币""" - if amount := sum(red_bag.red_bag_list): - await UserConsole.add_gold(user_id, amount, "gold_redbag", platform) - return amount, red_bag - return None, None - - async def add_red_bag( - self, - name: str, - amount: int, - num: int, - promoter: str, - promoter_id: str, - festival_uuid: str | None = None, - timeout: int = 60, - assigner: str | None = None, - platform: str | None = None, - ): - """添加红包 - - 参数: - name: 红包名称 - amount: 金币数量 - num: 红包数量 - promoter: 发起人昵称 - promoter_id: 发起人id - festival_uuid: 节日红包uuid. - timeout: 超时时间. - assigner: 指定人. - platform: 用户平台. - """ - user = await UserConsole.get_user(promoter_id, platform) - if not festival_uuid and (amount < 1 or user.gold < amount): - raise ValueError("红包金币不足或用户金币不足") - red_bag_list = self._random_red_bag(amount, num) - if not festival_uuid: - user.gold -= amount - await RedbagUser.add_redbag_data(promoter_id, self.group_id, "send", amount) - await user.save(update_fields=["gold"]) - self._data[promoter_id] = RedBag( - group_id=self.group_id, - name=name, - amount=amount, - num=num, - promoter=promoter, - promoter_id=promoter_id, - is_festival=bool(festival_uuid), - timeout=timeout, - start_time=time.time(), - assigner=assigner, - red_bag_list=red_bag_list, - uuid=festival_uuid, - ) - - def _random_red_bag(self, amount: int, num: int) -> list[int]: - """初始化红包金币 - - 参数: - amount: 金币数量 - num: 红包数量 - - 返回: - list[int]: 红包列表 - """ - red_bag_list = [] - for _ in range(num - 1): - tmp = int(amount / random.choice(range(3, num + 3))) - red_bag_list.append(tmp) - amount -= tmp - red_bag_list.append(amount) - return red_bag_list diff --git a/zhenxun/plugins/gold_redbag/data_source.py b/zhenxun/plugins/gold_redbag/data_source.py deleted file mode 100644 index ec903100..00000000 --- a/zhenxun/plugins/gold_redbag/data_source.py +++ /dev/null @@ -1,234 +0,0 @@ -import asyncio -import os -import random -from io import BytesIO -from typing import Dict - -from nonebot.adapters import Bot -from nonebot.exception import ActionFailed -from nonebot_plugin_alconna import UniMessage - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.models.user_console import UserConsole -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from .config import FestiveRedBagManage, GroupRedBag, RedBag - - -class RedBagManager: - - _data: Dict[str, GroupRedBag] = {} - - @classmethod - def get_group_data(cls, group_id: str) -> GroupRedBag: - """获取群组红包数据 - - 参数: - group_id: 群组id - - 返回: - GroupRedBag | None: GroupRedBag - """ - if group_id not in cls._data: - cls._data[group_id] = GroupRedBag(group_id) - return cls._data[group_id] - - @classmethod - async def _auto_end_festive_red_bag(cls, bot: Bot, group_id: str, platform: str): - """自动结算节日红包 - - 参数: - bot: Bot - group_id: 群组id - platform: 平台 - """ - if target := PlatformUtils.get_target(bot, group_id=group_id): - rank_num = Config.get_config("gold_redbag", "RANK_NUM") or 10 - group_red_bag = cls.get_group_data(group_id) - red_bag = group_red_bag.get_festive_red_bag() - if not red_bag: - return - rank_image = await red_bag.build_amount_rank(rank_num, platform) - if red_bag.is_festival and red_bag.uuid: - FestiveRedBagManage.remove(red_bag.uuid) - await asyncio.sleep(random.randint(1, 5)) - try: - await MessageUtils.build_message( - [ - f"{NICKNAME}的节日红包过时了,一共开启了 " - f"{len(red_bag.open_user)}" - f" 个红包,共 {sum(red_bag.open_user.values())} 金币\n", - rank_image, - ] - ).send(target=target, bot=bot) - except ActionFailed: - pass - - @classmethod - async def end_red_bag( - cls, - group_id: str, - user_id: str | None = None, - is_festive: bool = False, - platform: str = "", - ) -> UniMessage | None: - """结算红包 - - 参数: - group_id: 群组id或频道id - user_id: 用户id - is_festive: 是否节日红包 - platform: 用户平台 - """ - rank_num = Config.get_config("gold_redbag", "RANK_NUM") or 10 - group_red_bag = cls.get_group_data(group_id) - if not group_red_bag: - return None - if is_festive: - if festive_red_bag := group_red_bag.festive_red_bag_expire(): - rank_image = await festive_red_bag.build_amount_rank(rank_num, platform) - return MessageUtils.build_message( - [ - f"{NICKNAME}的节日红包过时了,一共开启了 " - f"{len(festive_red_bag.open_user)}" - f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n", - rank_image, - ] - ) - else: - if not user_id: - return None - return_gold, red_bag = await group_red_bag.settlement(user_id, platform) - if red_bag: - rank_image = await red_bag.build_amount_rank(rank_num, platform) - return MessageUtils.build_message( - [ - f"已成功退还了 " f"{return_gold} 金币\n", - rank_image.pic2bytes(), - ] - ) - - @classmethod - async def check_gold(cls, user_id: str, amount: int, platform: str) -> str | None: - """检查金币数量是否合法 - - 参数: - user_id: 用户id - amount: 金币数量 - platform: 所属平台 - - 返回: - tuple[bool, str]: 是否合法以及提示语 - """ - user = await UserConsole.get_user(user_id, platform) - if amount < 1: - return "小气鬼,要别人倒贴金币给你嘛!" - if user.gold < amount: - return "没有金币的话请不要发红包..." - return None - - @classmethod - async def random_red_bag_background( - cls, user_id: str, msg: str = "恭喜发财 大吉大利", platform: str = "" - ) -> BuildImage: - """构造发送红包图片 - - 参数: - user_id: 用户id - msg: 红包消息. - platform: 平台. - - 异常: - ValueError: 图片背景列表为空 - - 返回: - BuildImage: 构造后的图片 - """ - background_list = os.listdir(f"{IMAGE_PATH}/prts/redbag_2") - if not background_list: - raise ValueError("prts/redbag_1 背景图列表为空...") - random_redbag = random.choice(background_list) - redbag = BuildImage( - 0, - 0, - font_size=38, - background=IMAGE_PATH / "prts" / "redbag_2" / random_redbag, - ) - ava_byte = await PlatformUtils.get_user_avatar(user_id, platform) - ava = None - if ava_byte: - ava = BuildImage(65, 65, background=BytesIO(ava_byte)) - else: - ava = BuildImage(65, 65, color=(0, 0, 0)) - await ava.circle() - await redbag.text( - (int((redbag.size[0] - redbag.getsize(msg)[0]) / 2), 210), - msg, - (240, 218, 164), - ) - await redbag.paste(ava, (int((redbag.size[0] - ava.size[0]) / 2), 130)) - return redbag - - @classmethod - async def build_open_result_image( - cls, red_bag: RedBag, user_id: str, amount: int, platform: str - ) -> BuildImage: - """构造红包开启图片 - - 参数: - red_bag: RedBag - user_id: 开启红包用户id - amount: 开启红包获取的金额 - platform: 平台 - - 异常: - ValueError: 图片背景列表为空 - - 返回: - BuildImage: 构造后的图片 - """ - background_list = os.listdir(f"{IMAGE_PATH}/prts/redbag_1") - if not background_list: - raise ValueError("prts/redbag_1 背景图列表为空...") - random_redbag = random.choice(background_list) - head = BuildImage( - 1000, - 980, - font_size=30, - background=IMAGE_PATH / "prts" / "redbag_1" / random_redbag, - ) - size = BuildImage.get_text_size(red_bag.name, font_size=50) - ava_bk = BuildImage(100 + size[0], 66, (255, 255, 255, 0), font_size=50) - - ava_byte = await PlatformUtils.get_user_avatar(user_id, platform) - ava = None - if ava_byte: - ava = BuildImage(66, 66, background=BytesIO(ava_byte)) - else: - ava = BuildImage(66, 66, color=(0, 0, 0)) - await ava_bk.paste(ava) - await ava_bk.text((100, 7), red_bag.name) - ava_bk_w, ava_bk_h = ava_bk.size - await head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300)) - size = BuildImage.get_text_size(str(amount), font_size=150) - amount_image = BuildImage(size[0], size[1], (255, 255, 255, 0), font_size=150) - await amount_image.text((0, 0), str(amount), fill=(209, 171, 108)) - # 金币中文 - await head.paste(amount_image, (int((1000 - size[0]) / 2) - 50, 460)) - await head.text( - (int((1000 - size[0]) / 2 + size[0]) - 50, 500 + size[1] - 70), - "金币", - fill=(209, 171, 108), - ) - # 剩余数量和金额 - text = ( - f"已领取" - f"{red_bag.num - len(red_bag.open_user)}" - f"/{red_bag.num}个," - f"共{sum(red_bag.open_user.values())}/{red_bag.amount}金币" - ) - await head.text((350, 900), text, (198, 198, 198)) - return head diff --git a/zhenxun/plugins/gold_redbag/model.py b/zhenxun/plugins/gold_redbag/model.py deleted file mode 100644 index a8e9359a..00000000 --- a/zhenxun/plugins/gold_redbag/model.py +++ /dev/null @@ -1,63 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class RedbagUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - send_redbag_count = fields.IntField(default=0) - """发送红包次数""" - get_redbag_count = fields.IntField(default=0) - """开启红包次数""" - spend_gold = fields.IntField(default=0) - """发送红包花费金额""" - get_gold = fields.IntField(default=0) - """开启红包获取金额""" - - class Meta: - table = "redbag_users" - table_description = "红包统计数据表" - unique_together = ("user_id", "group_id") - - @classmethod - async def add_redbag_data( - cls, user_id: str, group_id: str, i_type: str, money: int - ): - """添加收发红包数据 - - 参数: - user_id: 用户id - group_id: 群号 - i_type: 收或发 - money: 金钱数量 - """ - - user, _ = await cls.get_or_create(user_id=user_id, group_id=group_id) - if i_type == "get": - user.get_redbag_count = user.get_redbag_count + 1 - user.get_gold = user.get_gold + money - else: - user.send_redbag_count = user.send_redbag_count + 1 - user.spend_gold = user.spend_gold + money - await user.save( - update_fields=[ - "get_redbag_count", - "get_gold", - "send_redbag_count", - "spend_gold", - ] - ) - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE redbag_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE redbag_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE redbag_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/group_welcome_msg.py b/zhenxun/plugins/group_welcome_msg.py deleted file mode 100644 index 7148e8e9..00000000 --- a/zhenxun/plugins/group_welcome_msg.py +++ /dev/null @@ -1,62 +0,0 @@ -import re - -import ujson as json -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_group - -__plugin_meta__ = PluginMetadata( - name="查看群欢迎消息", - description="查看群欢迎消息", - usage=""" - usage: - 查看群欢迎消息 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - ).dict(), -) - -_matcher = on_alconna(Alconna("群欢迎消息"), rule=ensure_group, priority=5, block=True) - - -BASE_PATH = DATA_PATH / "welcome_message" - - -@_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, -): - path = BASE_PATH / f"{session.platform or session.bot_type}" / f"{session.id2}" - if session.id3: - path = ( - BASE_PATH - / f"{session.platform or session.bot_type}" - / f"{session.id3}" - / f"{session.id2}" - ) - file = path / "text.json" - if not file.exists(): - await MessageUtils.build_message("未设置群欢迎消息...").finish(reply_to=True) - message = json.load(open(file, encoding="utf8"))["message"] - message_split = re.split(r"\[image:\d+\]", message) - if len(message_split) == 1: - await MessageUtils.build_message(message_split[0]).finish(reply_to=True) - idx = 0 - data_list = [] - for msg in message_split[:-1]: - data_list.append(msg) - data_list.append(path / f"{idx}.png") - idx += 1 - data_list.append(message_split[-1]) - await MessageUtils.build_message(data_list).send(reply_to=True) - logger.info("查看群欢迎消息", arparma.header_result, session=session) diff --git a/zhenxun/plugins/image_management/__init__.py b/zhenxun/plugins/image_management/__init__.py deleted file mode 100644 index 8f24386d..00000000 --- a/zhenxun/plugins/image_management/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -from pathlib import Path -from typing import List, Tuple - -import nonebot - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH - -Config.add_plugin_config( - "image_management", - "IMAGE_DIR_LIST", - ["美图", "萝莉", "壁纸"], - help="公开图库列表,可自定义添加 [如果含有send_setu插件,请不要添加色图库]", - default_value=[], - type=List[str], -) - -Config.add_plugin_config( - "image_management", - "WITHDRAW_IMAGE_MESSAGE", - (0, 1), - help="自动撤回,参1:延迟撤回发送图库图片的时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], -) - -Config.add_plugin_config( - "image_management:delete_image", - "DELETE_IMAGE_LEVEL", - 7, - help="删除图库图片需要的管理员等级", - default_value=7, - type=int, -) - -Config.add_plugin_config( - "image_management:move_image", - "MOVE_IMAGE_LEVEL", - 7, - help="移动图库图片需要的管理员等级", - default_value=7, - type=int, -) - -Config.add_plugin_config( - "image_management:upload_image", - "UPLOAD_IMAGE_LEVEL", - 6, - help="上传图库图片需要的管理员等级", - default_value=6, - type=int, -) - -Config.add_plugin_config( - "image_management", - "SHOW_ID", - True, - help="是否消息显示图片下标id", - default_value=True, - type=bool, -) - -Config.set_name("image_management", "图库操作") - - -(IMAGE_PATH / "image_management").mkdir(parents=True, exist_ok=True) - - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/image_management/_config.py b/zhenxun/plugins/image_management/_config.py deleted file mode 100644 index d5e01f58..00000000 --- a/zhenxun/plugins/image_management/_config.py +++ /dev/null @@ -1,14 +0,0 @@ -from strenum import StrEnum - - -class ImageHandleType(StrEnum): - """ - 图片处理类型 - """ - - UPLOAD = "UPLOAD" - """上传""" - DELETE = "DELETE" - """删除""" - MOVE = "MOVE" - """移动""" diff --git a/zhenxun/plugins/image_management/_data_source.py b/zhenxun/plugins/image_management/_data_source.py deleted file mode 100644 index bc26b74f..00000000 --- a/zhenxun/plugins/image_management/_data_source.py +++ /dev/null @@ -1,196 +0,0 @@ -import os -import random -from pathlib import Path - -import aiofiles - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.utils import cn2py - -from .image_management_log import ImageHandleType, ImageManagementLog - -BASE_PATH = IMAGE_PATH / "image_management" - - -class ImageManagementManage: - - @classmethod - async def random_image(cls, name: str, file_id: int | None = None) -> Path | None: - """随机图片 - - 参数: - name: 图库名称 - file_id: 图片id. - - 返回: - Path | None: 图片路径 - """ - path = BASE_PATH / name - file_name = f"{file_id}.jpg" - if file_id is None: - if file_list := os.listdir(path): - file_name = random.choice(file_list) - _file = path / file_name - if not _file.exists(): - return None - return _file - - @classmethod - async def upload_image( - cls, - image_data: bytes | str, - name: str, - user_id: str, - platform: str | None = None, - ) -> str | None: - """上传图片 - - 参数: - image_data: 图片bytes - name: 图库名称 - user_id: 用户id - platform: 所属平台 - - 返回: - str | None: 文件名称 - """ - path = BASE_PATH / cn2py(name) - path.mkdir(exist_ok=True, parents=True) - _file_name = 0 - if file_list := os.listdir(path): - file_list.sort() - _file_name = int(file_list[-1].split(".")[0]) + 1 - _file_path = path / f"{_file_name}.jpg" - try: - await ImageManagementLog.create( - user_id=user_id, - path=_file_path, - handle_type=ImageHandleType.UPLOAD, - platform=platform, - ) - if isinstance(image_data, str): - await AsyncHttpx.download_file(image_data, _file_path) - else: - async with aiofiles.open(_file_path, "wb") as f: - await f.write(image_data) - logger.info( - f"上传图片至 {name}, 路径: {_file_path}", - "上传图片", - session=user_id, - ) - return f"{_file_name}.jpg" - except Exception as e: - logger.error("上传图片错误", "上传图片", e=e) - return None - - @classmethod - async def delete_image( - cls, name: str, file_id: int, user_id: str, platform: str | None = None - ) -> bool: - """删除图片 - - 参数: - name: 图库名称 - file_id: 图片id - user_id: 用户id - platform: 所属平台. - - 返回: - bool: 是否删除成功 - """ - path = BASE_PATH / cn2py(name) - if not path.exists(): - return False - _file_path = path / f"{file_id}.jpg" - if not _file_path.exists(): - return False - try: - await ImageManagementLog.create( - user_id=user_id, - path=_file_path, - handle_type=ImageHandleType.DELETE, - platform=platform, - ) - _file_path.unlink() - logger.info( - f"图库: {name}, 删除图片路径: {_file_path}", "删除图片", session=user_id - ) - if file_list := os.listdir(path): - file_list.sort() - _file_name = file_list[-1].split(".")[0] - _move_file = path / f"{_file_name}.jpg" - _move_file.rename(_file_path) - logger.info( - f"图库: {name}, 移动图片名称: {_file_name}.jpg -> {file_id}.jpg", - "删除图片", - session=user_id, - ) - except Exception as e: - logger.error("删除图片错误", "删除图片", e=e) - return False - return True - - @classmethod - async def move_image( - cls, - a_name: str, - b_name: str, - file_id: int, - user_id: str, - platform: str | None = None, - ) -> str | None: - """移动图片 - - 参数: - a_name: 源图库 - b_name: 模板图库 - file_id: 图片id - user_id: 用户id - platform: 所属平台. - - 返回: - bool: 是否移动成功 - """ - source_path = BASE_PATH / cn2py(a_name) - if not source_path.exists(): - return None - destination_path = BASE_PATH / cn2py(b_name) - destination_path.mkdir(exist_ok=True, parents=True) - source_file = source_path / f"{file_id}.jpg" - if not source_file.exists(): - return None - _destination_name = 0 - if file_list := os.listdir(destination_path): - file_list.sort() - _destination_name = int(file_list[-1].split(".")[0]) + 1 - destination_file = destination_path / f"{_destination_name}.jpg" - try: - await ImageManagementLog.create( - user_id=user_id, - path=source_file, - move=destination_file, - handle_type=ImageHandleType.MOVE, - platform=platform, - ) - source_file.rename(destination_file) - logger.info( - f"图库: {a_name} -> {b_name}, 移动图片路径: {source_file} -> {destination_file}", - "移动图片", - session=user_id, - ) - if file_list := os.listdir(source_path): - file_list.sort() - _file_name = file_list[-1].split(".")[0] - _move_file = source_path / f"{_file_name}.jpg" - _move_file.rename(source_file) - logger.info( - f"图库: {a_name}, 移动图片名称: {_file_name}.jpg -> {file_id}.jpg", - "移动图片", - session=user_id, - ) - except Exception as e: - logger.error("移动图片错误", "移动图片", e=e) - return None - return f"{source_file} -> {destination_file}" diff --git a/zhenxun/plugins/image_management/delete_image.py b/zhenxun/plugins/image_management/delete_image.py deleted file mode 100644 index 6cabb8e9..00000000 --- a/zhenxun/plugins/image_management/delete_image.py +++ /dev/null @@ -1,107 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot.typing import T_State -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, UniMessage, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from ._data_source import ImageManagementManage - -base_config = Config.get("image_management") - -__plugin_meta__ = PluginMetadata( - name="删除图片", - description="不好看的图片删掉删掉!", - usage=""" - 指令: - 删除图片 [图库] [id] - 查看图库 - 示例:删除图片 美图 666 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("DELETE_IMAGE_LEVEL"), - ).dict(), -) - - -_matcher = on_alconna( - Alconna("删除图片", Args["name?", str]["index?", str]), - rule=to_me(), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _( - name: Match[str], - index: Match[str], - state: T_State, -): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if name.available: - _matcher.set_path_arg("name", name.result) - if index.available: - _matcher.set_path_arg("index", index.result) - - -@_matcher.got_path( - "name", - prompt=UniMessage.template( - "请输入要删除的目标图库(id 或 名称)【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(name: str): - if name in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if name.isdigit(): - index = int(name) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _matcher.reject_path("name", "此目录不正确,请重新输入目录!") - _matcher.set_path_arg("name", name) - - -@_matcher.got_path("index", "请输入要删除的图片id?【发送'取消', '算了'来取消操作】") -async def _( - session: EventSession, - arparma: Arparma, - index: str, -): - if index in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - if not index.isdigit(): - await _matcher.reject_path("index", "图片id需要输入数字...") - name = _matcher.get_path_arg("name", None) - if not name: - await MessageUtils.build_message("图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if file_name := await ImageManagementManage.delete_image( - name, int(index), session.id1, session.platform - ): - logger.info( - f"删除图片成功 图库: {name} --- 名称: {file_name}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message( - f"删除图片成功!\n图库: {name}\n名称: {index}.jpg" - ).finish() - await MessageUtils.build_message("图片删除失败...").finish() diff --git a/zhenxun/plugins/image_management/image_management_log.py b/zhenxun/plugins/image_management/image_management_log.py deleted file mode 100644 index 756e58f2..00000000 --- a/zhenxun/plugins/image_management/image_management_log.py +++ /dev/null @@ -1,27 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - -from ._config import ImageHandleType - - -class ImageManagementLog(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255, description="用户id") - """用户id""" - path = fields.TextField(description="图片路径") - """图片路径""" - move = fields.TextField(null=True, description="移动路径") - """移动路径""" - handle_type = fields.CharEnumField(ImageHandleType, description="操作类型") - """操作类型""" - create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") - """创建时间""" - platform = fields.CharField(255, null=True, description="平台") - """平台""" - - class Meta: - table = "image_management_log" - table_description = "画廊操作记录" diff --git a/zhenxun/plugins/image_management/move_image.py b/zhenxun/plugins/image_management/move_image.py deleted file mode 100644 index 6c1ec5e9..00000000 --- a/zhenxun/plugins/image_management/move_image.py +++ /dev/null @@ -1,137 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot.typing import T_State -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, UniMessage, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from ._data_source import ImageManagementManage - -base_config = Config.get("image_management") - -__plugin_meta__ = PluginMetadata( - name="移动图片", - description="图库间的图片移动操作", - usage=""" - 指令: - 移动图片 [源图库] [目标图库] [id] - 查看图库 - 示例:移动图片 萝莉 美图 234 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("MOVE_IMAGE_LEVEL"), - ).dict(), -) - - -_matcher = on_alconna( - Alconna("移动图片", Args["source?", str]["destination?", str]["index?", str]), - rule=to_me(), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - source: Match[str], - destination: Match[str], - index: Match[str], - state: T_State, -): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if source.available: - _matcher.set_path_arg("source", source.result) - if destination.available: - _matcher.set_path_arg("destination", destination.result) - if index.available: - _matcher.set_path_arg("index", index.result) - - -@_matcher.got_path( - "source", - prompt=UniMessage.template( - "要从哪个图库移出?【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(source: str): - if source in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if source.isdigit(): - index = int(source) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _matcher.reject_path("source", "此目录不正确,请重新输入目录!") - _matcher.set_path_arg("source", name) - - -@_matcher.got_path( - "destination", - prompt=UniMessage.template( - "要移动到哪个图库?【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(destination: str): - if destination in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - name = None - if destination.isdigit(): - index = int(destination) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _matcher.reject_path("destination", "此目录不正确,请重新输入目录!") - _matcher.set_path_arg("destination", name) - - -@_matcher.got_path("index", "要移动的图片id是?【发送'取消', '算了'来取消操作】") -async def _( - session: EventSession, - arparma: Arparma, - index: str, -): - if index in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - if not index.isdigit(): - await _matcher.reject_path("index", "图片id需要输入数字...") - source = _matcher.get_path_arg("source", None) - destination = _matcher.get_path_arg("destination", None) - if not source: - await MessageUtils.build_message("转出图库名称为空...").finish() - if not destination: - await MessageUtils.build_message("转入图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if file_name := await ImageManagementManage.move_image( - source, destination, int(index), session.id1, session.platform - ): - logger.info( - f"移动图片成功 图库: {source} -> {destination} --- 名称: {file_name}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message( - f"移动图片成功!\n图库: {source} -> {destination}" - ).finish() - await MessageUtils.build_message("图片删除失败...").finish() diff --git a/zhenxun/plugins/image_management/upload_image.py b/zhenxun/plugins/image_management/upload_image.py deleted file mode 100644 index b69281a4..00000000 --- a/zhenxun/plugins/image_management/upload_image.py +++ /dev/null @@ -1,195 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot.typing import T_State -from nonebot_plugin_alconna import Alconna, Args, Arparma -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import Match, UniMessage, UniMsg, image_fetch, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -from ._data_source import ImageManagementManage - -base_config = Config.get("image_management") - -__plugin_meta__ = PluginMetadata( - name="上传图片", - description="上传图片至指定图库", - usage=""" - 指令: - 查看图库 - 上传图片 [图库] [图片] - 示例:上传图片 美图 [图片] - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("UPLOAD_IMAGE_LEVEL"), - ).dict(), -) - - -_upload_matcher = on_alconna( - Alconna("上传图片", Args["name?", str]["img?", alcImage]), - rule=to_me(), - priority=5, - block=True, -) - -_continuous_upload_matcher = on_alconna( - Alconna("连续上传图片", Args["name?", str]), - rule=to_me(), - priority=5, - block=True, -) - -_show_matcher = on_alconna(Alconna("查看公开图库"), priority=1, block=True) - - -@_show_matcher.handle() -async def _(): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - text = "公开图库列表:\n" - for i, e in enumerate(image_dir_list): - text += f"\t{i+1}.{e}\n" - await MessageUtils.build_message(text[:-1]).send() - - -@_upload_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - name: Match[str], - img: Match[bytes], - state: T_State, -): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if name.available: - _upload_matcher.set_path_arg("name", name.result) - if img.available: - result = await AsyncHttpx.get(img.result.url) # type: ignore - _upload_matcher.set_path_arg("img", result.content) - - -@_continuous_upload_matcher.handle() -async def _(bot: Bot, state: T_State, name: Match[str]): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if name.available: - _upload_matcher.set_path_arg("name", name.result) - - -@_continuous_upload_matcher.got_path( - "name", - prompt=UniMessage.template( - "请选择要上传的图库(id 或 名称)【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -@_upload_matcher.got_path( - "name", - prompt=UniMessage.template( - "请选择要上传的图库(id 或 名称)【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(name: str, state: T_State): - if name in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if name.isdigit(): - index = int(name) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _upload_matcher.reject_path("name", "此目录不正确,请重新输入目录!") - _upload_matcher.set_path_arg("name", name) - - -@_upload_matcher.got_path("img", "图呢图呢图呢图呢!GKD!", image_fetch) -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - img: bytes, -): - name = _upload_matcher.get_path_arg("name", None) - if not name: - await MessageUtils.build_message("图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if file_name := await ImageManagementManage.upload_image( - img, name, session.id1, session.platform - ): - logger.info( - f"图库: {name} --- 名称: {file_name}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message( - f"上传图片成功!\n图库: {name}\n名称: {file_name}" - ).finish() - await MessageUtils.build_message("图片上传失败...").finish() - - -@_continuous_upload_matcher.got( - "img", "图呢图呢图呢图呢!GKD!【在最后一张图片中+‘stop’为停止】" -) -async def _( - bot: Bot, - arparma: Arparma, - session: EventSession, - state: T_State, - message: UniMsg, -): - name = _continuous_upload_matcher.get_path_arg("name", None) - if not name: - await MessageUtils.build_message("图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not state.get("img_list"): - state["img_list"] = [] - msg = message.extract_plain_text().strip().replace(arparma.header_result, "", 1) - if msg in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - if msg != "stop": - for msg in message: - if isinstance(msg, alcImage): - state["img_list"].append(msg.url) - await _continuous_upload_matcher.reject("图再来!!【发送‘stop’为停止】") - if state["img_list"]: - await MessageUtils.build_message("正在下载, 请稍后...").send() - file_list = [] - for img in state["img_list"]: - if file_name := await ImageManagementManage.upload_image( - img, name, session.id1, session.platform - ): - file_list.append(img) - logger.info( - f"图库: {name} --- 名称: {file_name}", - "上传图片", - session=session, - ) - await MessageUtils.build_message( - f"上传图片成功!共上传了{len(file_list)}张图片\n图库: {name}\n名称: {', '.join(file_list)}" - ).finish() - await MessageUtils.build_message("图片上传失败...").finish() diff --git a/zhenxun/plugins/luxun.py b/zhenxun/plugins/luxun.py deleted file mode 100644 index 1c1ab09c..00000000 --- a/zhenxun/plugins/luxun.py +++ /dev/null @@ -1,74 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import BaseBlock, PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="鲁迅说", - description="鲁迅说了啥?", - usage=""" - 鲁迅说 [文本] - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - limits=[BaseBlock(result="你的鲁迅正在说,等会")], - ).dict(), -) - -_matcher = on_alconna( - Alconna("luxun", Args["content", str]), - priority=5, - block=True, -) - -_matcher.shortcut( - "鲁迅说", - command="luxun", - arguments=["{%0}"], - prefix=True, -) - - -_sign = None - - -@_matcher.handle() -async def _(content: Match[str]): - if content.available: - _matcher.set_path_arg("content", content.result) - - -@_matcher.got_path("content", prompt="你让鲁迅说点啥?") -async def _(content: str, session: EventSession, arparma: Arparma): - global _sign - if content.startswith(",") or content.startswith(","): - content = content[1:] - A = BuildImage( - font_size=37, background=f"{IMAGE_PATH}/other/luxun.jpg", font="msyh.ttf" - ) - text = "" - if len(content) > 40: - await MessageUtils.build_message("太长了,鲁迅说不完...").finish() - while A.getsize(content)[0] > A.width - 50: - n = int(len(content) / 2) - text += content[:n] + "\n" - content = content[n:] - text += content - if len(text.split("\n")) > 2: - await MessageUtils.build_message("太长了,鲁迅说不完...").finish() - await A.text( - (int((480 - A.getsize(text.split("\n")[0])[0]) / 2), 300), text, (255, 255, 255) - ) - if not _sign: - _sign = await BuildImage.build_text_image( - "--鲁迅", "msyh.ttf", 30, (255, 255, 255) - ) - await A.paste(_sign, (320, 400)) - await MessageUtils.build_message(A).send() - logger.info(f"鲁迅说: {content}", arparma.header_result, session=session) diff --git a/zhenxun/plugins/mute/__init__.py b/zhenxun/plugins/mute/__init__.py deleted file mode 100644 index eb35e275..00000000 --- a/zhenxun/plugins/mute/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/mute/_data_source.py b/zhenxun/plugins/mute/_data_source.py deleted file mode 100644 index 92bd3dd5..00000000 --- a/zhenxun/plugins/mute/_data_source.py +++ /dev/null @@ -1,124 +0,0 @@ -import time - -import ujson as json -from pydantic import BaseModel - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import DATA_PATH - -base_config = Config.get("mute_setting") - - -class GroupData(BaseModel): - - count: int - """次数""" - time: int - """检测时长""" - duration: int - """禁言时长""" - message_data: dict = {} - """消息存储""" - - -class MuteManage: - - file = DATA_PATH / "group_mute_data.json" - - def __init__(self) -> None: - self._group_data: dict[str, GroupData] = {} - if self.file.exists(): - _data = json.load(open(self.file)) - for gid in _data: - self._group_data[gid] = GroupData( - count=_data[gid]["count"], - time=_data[gid]["time"], - duration=_data[gid]["duration"], - ) - - def get_group_data(self, group_id: str) -> GroupData: - """获取群组数据 - - 参数: - group_id: 群组id - - 返回: - GroupData: GroupData - """ - if group_id not in self._group_data: - self._group_data[group_id] = GroupData( - count=base_config.get("MUTE_DEFAULT_COUNT", 10), - time=base_config.get("MUTE_DEFAULT_TIME", 7), - duration=base_config.get("MUTE_DEFAULT_DURATION", 10), - ) - return self._group_data[group_id] - - def reset(self, user_id: str, group_id: str): - """重置用户检查次数 - - 参数: - user_id: 用户id - group_id: 群组id - """ - if group_data := self._group_data.get(group_id): - if user_id in group_data.message_data: - group_data.message_data[user_id]["count"] = 0 - - def save_data(self): - """保存数据""" - data = {} - for gid in self._group_data: - data[gid] = { - "count": self._group_data[gid].count, - "time": self._group_data[gid].time, - "duration": self._group_data[gid].duration, - } - with open(self.file, "w") as f: - json.dump(data, f, indent=4, ensure_ascii=False) - - def add_message(self, user_id: str, group_id: str, message: str) -> int: - """添加消息 - - 参数: - user_id: 用户id - group_id: 群组id - message: 消息内容 - - 返回: - int: 禁言时长 - """ - if group_id not in self._group_data: - self._group_data[group_id] = GroupData( - count=base_config.get("MUTE_DEFAULT_COUNT"), - time=base_config.get("MUTE_DEFAULT_TIME"), - duration=base_config.get("MUTE_DEFAULT_DURATION"), - ) - group_data = self._group_data[group_id] - if group_data.duration == 0: - return 0 - message_data = group_data.message_data - if not message_data.get(user_id): - message_data[user_id] = { - "time": time.time(), - "count": 1, - "message": message, - } - else: - if message.find(message_data[user_id]["message"]) != -1: - message_data[user_id]["count"] += 1 - else: - message_data[user_id]["time"] = time.time() - message_data[user_id]["count"] = 1 - message_data[user_id]["message"] = message - if time.time() - message_data[user_id]["time"] > group_data.time: - message_data[user_id]["time"] = time.time() - message_data[user_id]["count"] = 1 - if ( - message_data[user_id]["count"] > group_data.count - and time.time() - message_data[user_id]["time"] < group_data.time - ): - return group_data.duration - return 0 - - -mute_manage = MuteManage() diff --git a/zhenxun/plugins/mute/mute_message.py b/zhenxun/plugins/mute/mute_message.py deleted file mode 100644 index ec655ccf..00000000 --- a/zhenxun/plugins/mute/mute_message.py +++ /dev/null @@ -1,56 +0,0 @@ -from nonebot import on_message -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.ban_console import BanConsole -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import get_download_image_hash -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from ._data_source import mute_manage - -__plugin_meta__ = PluginMetadata( - name="刷屏监听", - description="", - usage="", - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - ).dict(), -) - -_matcher = on_message(priority=1, block=False) - - -@_matcher.handle() -async def _(bot: Bot, session: EventSession, message: UniMsg): - group_id = session.id2 - if not session.id1 or not group_id: - return - if await BanConsole.is_ban(session.id1, group_id): - return - plain_text = message.extract_plain_text() - image_list = [m.url for m in message if isinstance(m, alcImage) and m.url] - img_hash = "" - for url in image_list: - img_hash += await get_download_image_hash(url, "_mute_") - _message = plain_text + img_hash - if duration := mute_manage.add_message(session.id1, group_id, _message): - try: - await PlatformUtils.ban_user(bot, session.id1, group_id, duration) - await MessageUtils.build_message( - f"检测到恶意刷屏,{NICKNAME}要把你关进小黑屋!" - ).send(at_sender=True) - mute_manage.reset(session.id1, group_id) - logger.info(f"检测刷屏 被禁言 {duration} 分钟", "禁言检查", session=session) - except Exception as e: - logger.error("禁言发送错误", "禁言检测", session=session, e=e) diff --git a/zhenxun/plugins/mute/mute_setting.py b/zhenxun/plugins/mute/mute_setting.py deleted file mode 100644 index f723f075..00000000 --- a/zhenxun/plugins/mute/mute_setting.py +++ /dev/null @@ -1,117 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_group - -from ._data_source import base_config, mute_manage - -__plugin_meta__ = PluginMetadata( - name="刷屏禁言", - description="刷屏禁言相关操作", - usage=""" - 刷屏禁言相关操作,需要 {NICKNAME} 有群管理员权限 - 指令: - 设置刷屏: 查看当前设置 - -c [count]: 检测最大次数 - -t [time]: 规定时间内 - -d [duration]: 禁言时长 - 示例: - 设置刷屏 -c 10: 设置最大次数为10 - 设置刷屏 -t 100 -d 20: 设置规定时间和禁言时长 - 设置刷屏 -d 10: 设置禁言时长为10 - * 即 X 秒内发送同样消息 N 次,禁言 M 分钟 * - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("MUTE_LEVEL", 5), - configs=[ - RegisterConfig( - key="MUTE_LEVEL", - value=5, - help="更改禁言设置的管理权限", - default_value=5, - type=int, - ), - RegisterConfig( - key="MUTE_DEFAULT_COUNT", - value=10, - help="刷屏禁言默认检测次数", - default_value=10, - type=int, - ), - RegisterConfig( - key="MUTE_DEFAULT_TIME", - value=7, - help="刷屏检测默认规定时间", - default_value=7, - type=int, - ), - RegisterConfig( - key="MUTE_DEFAULT_DURATION", - value=10, - help="刷屏检测默禁言时长(分钟)", - default_value=10, - type=int, - ), - ], - ).dict(), -) - - -_setting_matcher = on_alconna( - Alconna( - "刷屏设置", - Option("-t|--time", Args["time", int], help_text="检测时长"), - Option("-c|--count", Args["count", int], help_text="检测次数"), - Option("-d|--duration", Args["duration", int], help_text="禁言时长"), - ), - rule=ensure_group, - block=True, - priority=5, -) - - -@_setting_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - time: Match[int], - count: Match[int], - duration: Match[int], -): - group_id = session.id2 - if not session.id1 or not group_id: - return - _time = time.result if time.available else None - _count = count.result if count.available else None - _duration = duration.result if duration.available else None - group_data = mute_manage.get_group_data(group_id) - if _time is None and _count is None and _duration is None: - await MessageUtils.build_message( - f"最大次数:{group_data.count} 次\n" - f"规定时间:{group_data.time} 秒\n" - f"禁言时长:{group_data.duration:.2f} 分钟\n" - f"【在规定时间内发送相同消息超过最大次数则禁言\n当禁言时长为0时关闭此功能】" - ).finish(reply_to=True) - if _time is not None: - group_data.time = _time - if _count is not None: - group_data.count = _count - if _duration is not None: - group_data.duration = _duration - await MessageUtils.build_message("设置成功!").send(reply_to=True) - logger.info( - f"设置禁言配置 time: {_time}, count: {_count}, duration: {_duration}", - arparma.header_result, - session=session, - ) - mute_manage.save_data() diff --git a/zhenxun/plugins/nbnhhsh.py b/zhenxun/plugins/nbnhhsh.py deleted file mode 100644 index 7ab78aaa..00000000 --- a/zhenxun/plugins/nbnhhsh.py +++ /dev/null @@ -1,62 +0,0 @@ -import ujson as json -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="能不能好好说话", - description="能不能好好说话,说人话", - usage=""" - 说人话 - 指令: - nbnhhsh [文本] - 能不能好好说话 [文本] - 示例: - nbnhhsh xsx - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1", aliases={"nbnhhsh"}).dict(), -) - -URL = "https://lab.magiconch.com/api/nbnhhsh/guess" - -_matcher = on_alconna( - Alconna("nbnhhsh", Args["text", str]), - aliases={"能不能好好说话"}, - priority=5, - block=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma, text: str): - response = await AsyncHttpx.post( - URL, - data=json.dumps({"text": text}), # type: ignore - timeout=5, - headers={"content-type": "application/json"}, - ) - try: - data = response.json() - tmp = "" - result = "" - for x in data: - trans = "" - if x.get("trans"): - trans = x["trans"][0] - elif x.get("inputting"): - trans = ",".join(x["inputting"]) - tmp += f'{x["name"]} -> {trans}\n' - result += trans - logger.info( - f" 发送能不能好好说话: {text} -> {result}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message(f"{tmp}={result}").send(reply_to=True) - except (IndexError, KeyError): - await MessageUtils.build_message("没有找到对应的翻译....").send() diff --git a/zhenxun/plugins/one_friend/__init__.py b/zhenxun/plugins/one_friend/__init__.py deleted file mode 100644 index 7fa7c8bf..00000000 --- a/zhenxun/plugins/one_friend/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -import random -from io import BytesIO - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, At, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -__plugin_meta__ = PluginMetadata( - name="我有一个朋友", - description="我有一个朋友想问问...", - usage=""" - 指令: - 我有一个朋友想问问 [文本] ?[at]: 当at时你的朋友就是艾特对象 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("one-friend", Args["text", str]["at?", At]), priority=5, block=True -) - -_matcher.shortcut( - "^我.{0,4}朋友.{0,2}(?:想问问|说|让我问问|想问|让我问|想知道|让我帮他问问|让我帮他问|让我帮忙问|让我帮忙问问|问)(?P.{0,30})$", - command="one-friend", - arguments=["{content}"], - prefix=True, -) - - -@_matcher.handle() -async def _(bot: Bot, text: str, at: Match[At], session: EventSession): - gid = session.id3 or session.id2 - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - at_user = None - if at.available: - at_user = at.result.target - user = None - if at_user: - user = await PlatformUtils.get_user(bot, at_user, gid) - else: - if member_list := await PlatformUtils.get_group_member_list(bot, gid): - text = text.replace("他", "我").replace("她", "我").replace("它", "我") - user = random.choice(member_list) - if user: - ava_data = None - if PlatformUtils.get_platform(bot) == "qq": - ava_data = await PlatformUtils.get_user_avatar(user.user_id, "qq") - elif user.avatar_url: - ava_data = (await AsyncHttpx.get(user.avatar_url)).content - ava_img = BuildImage(200, 100, color=(0, 0, 0, 0)) - if ava_data: - ava_img = BuildImage(200, 100, background=BytesIO(ava_data)) - await ava_img.circle() - user_name = "朋友" - content = BuildImage(400, 30, font_size=30) - await content.text((0, 0), user_name) - A = BuildImage(700, 150, font_size=25, color="white") - await A.paste(ava_img, (30, 25)) - await A.paste(content, (150, 38)) - await A.text((150, 85), text, (125, 125, 125)) - logger.info(f"发送有一个朋友: {text}", "我有一个朋友", session=session) - await MessageUtils.build_message(A).finish() - await MessageUtils.build_message("获取用户信息失败...").send() diff --git a/zhenxun/plugins/open_cases/__init__.py b/zhenxun/plugins/open_cases/__init__.py deleted file mode 100644 index 2798942e..00000000 --- a/zhenxun/plugins/open_cases/__init__.py +++ /dev/null @@ -1,329 +0,0 @@ -import asyncio -import random -from datetime import datetime, timedelta -from typing import List - -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Arparma, Match -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig, Task -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import text2image -from zhenxun.utils.message import MessageUtils - -from .command import ( - _group_open_matcher, - _knifes_matcher, - _multiple_matcher, - _my_open_matcher, - _open_matcher, - _price_matcher, - _reload_matcher, - _show_case_matcher, - _update_image_matcher, - _update_matcher, -) -from .open_cases_c import ( - auto_update, - get_my_knifes, - group_statistics, - open_case, - open_multiple_case, - total_open_statistics, -) -from .utils import ( - CASE2ID, - KNIFE2ID, - CaseManager, - build_case_image, - download_image, - get_skin_case, - init_skin_trends, - reset_count_daily, - update_skin_data, -) - -__plugin_meta__ = PluginMetadata( - name="CSGO开箱", - description="csgo模拟开箱[戒赌]", - usage=""" - 指令: - 开箱 ?[武器箱] - [1-30]连开箱 ?[武器箱] - 我的开箱 - 我的金色 - 群开箱统计 - 查看武器箱?[武器箱] - * 不包含[武器箱]时随机开箱 * - 示例: 查看武器箱 - 示例: 查看武器箱英勇 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - superuser_help=""" - 更新皮肤指令 - 重置开箱: 重置今日开箱所有次数 - 指令: - 更新武器箱 ?[武器箱/ALL] - 更新皮肤 ?[名称/ALL1] - 更新皮肤 ?[名称/ALL1] -S: (必定更新罕见皮肤所属箱子) - 更新武器箱图片 - * 不指定武器箱时则全部更新 * - * 过多的爬取会导致账号API被封 * - """.strip(), - menu_type="抽卡相关", - tasks=[Task(module="open_case_reset_remind", name="每日开箱重置提醒")], - limits=[PluginCdBlock(result="着什么急啊,慢慢来!")], - configs=[ - RegisterConfig( - key="INITIAL_OPEN_CASE_COUNT", - value=20, - help="初始每日开箱次数", - default_value=20, - type=int, - ), - RegisterConfig( - key="EACH_IMPRESSION_ADD_COUNT", - value=3, - help="每 * 点好感度额外增加开箱次数", - default_value=3, - type=int, - ), - RegisterConfig(key="COOKIE", value=None, help="BUFF的cookie"), - RegisterConfig( - key="DAILY_UPDATE", - value=None, - help="每日自动更新的武器箱,存在'ALL'时则更新全部武器箱", - type=List[str], - ), - RegisterConfig( - key="DEFAULT_OPEN_CASE_RESET_REMIND", - module="_task", - value=True, - help="被动 每日开箱重置提醒 进群默认开关状态", - default_value=True, - type=bool, - ), - ], - ).dict(), -) - - -@_price_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - name: str, - skin: str, - abrasion: str, - day: Match[int], -): - name = name.replace("武器箱", "").strip() - _day = 7 - if day.available: - _day = day.result - if _day > 180: - await MessageUtils.build_message("天数必须大于0且小于180").finish() - result = await init_skin_trends(name, skin, abrasion, _day) - if not result: - await MessageUtils.build_message("未查询到数据...").finish(reply_to=True) - await MessageUtils.build_message(result).send() - logger.info( - f"查看 [{name}:{skin}({abrasion})] 价格趋势", - arparma.header_result, - session=session, - ) - - -@_reload_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - await reset_count_daily() - logger.info("重置开箱次数", arparma.header_result, session=session) - - -@_open_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - case_name = None - if name.available: - case_name = name.result.replace("武器箱", "").strip() - result = await open_case(session.id1, gid, case_name, session) - await result.finish(reply_to=True) - - -@_my_open_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - await MessageUtils.build_message( - await total_open_statistics(session.id1, gid), - ).send(reply_to=True) - logger.info("查询我的开箱", arparma.header_result, session=session) - - -@_group_open_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await group_statistics(gid) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info("查询群开箱统计", arparma.header_result, session=session) - - -@_knifes_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await get_my_knifes(session.id1, gid) - await result.send(reply_to=True) - logger.info("查询我的金色", arparma.header_result, session=session) - - -@_multiple_matcher.handle() -async def _(session: EventSession, arparma: Arparma, num: int, name: Match[str]): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if num > 30: - await MessageUtils.build_message("开箱次数不要超过30啊笨蛋!").finish() - if num < 0: - await MessageUtils.build_message("再负开箱就扣你明天开箱数了!").finish() - case_name = None - if name.available: - case_name = name.result.replace("武器箱", "").strip() - result = await open_multiple_case(session.id1, gid, case_name, num, session) - await result.send(reply_to=True) - logger.info(f"{num}连开箱", arparma.header_result, session=session) - - -@_update_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - case_name = None - if name.available: - case_name = name.result.strip() - if not case_name: - case_list = [] - skin_list = [] - for i, case_name in enumerate(CASE2ID): - if case_name in CaseManager.CURRENT_CASES: - case_list.append(f"{i+1}.{case_name} [已更新]") - else: - case_list.append(f"{i+1}.{case_name}") - for skin_name in KNIFE2ID: - skin_list.append(f"{skin_name}") - text = "武器箱:\n" + "\n".join(case_list) + "\n皮肤:\n" + ", ".join(skin_list) - img = await text2image(text, padding=20, color="#f9f6f2") - await MessageUtils.build_message( - ["未指定武器箱, 当前已包含武器箱/皮肤\n", img] - ).finish() - if case_name in ["ALL", "ALL1"]: - if case_name == "ALL": - case_list = list(CASE2ID.keys()) - type_ = "武器箱" - else: - case_list = list(KNIFE2ID.keys()) - type_ = "罕见皮肤" - await MessageUtils.build_message(f"即将更新所有{type_}, 请稍等").send() - for i, case_name in enumerate(case_list): - try: - info = await update_skin_data(case_name, arparma.find("s")) - if "请先登录" in info: - await MessageUtils.build_message( - f"未登录, 已停止更新, 请配置BUFF token..." - ).send() - return - rand = random.randint(300, 500) - result = f"更新全部{type_}完成" - if i < len(case_list) - 1: - next_case = case_list[i + 1] - result = f"将在 {rand} 秒后更新下一{type_}: {next_case}" - await MessageUtils.build_message(f"{info}, {result}").send() - logger.info(f"info, {result}", "更新武器箱", session=session) - await asyncio.sleep(rand) - except Exception as e: - logger.error(f"更新{type_}: {case_name}", session=session, e=e) - await MessageUtils.build_message( - f"更新{type_}: {case_name} 发生错误: {type(e)}: {e}" - ).send() - await MessageUtils.build_message(f"更新全部{type_}完成").send() - else: - await MessageUtils.build_message( - f"开始{arparma.header_result}: {case_name}, 请稍等" - ).send() - try: - await MessageUtils.build_message( - await update_skin_data(case_name, arparma.find("s")) - ).send(at_sender=True) - except Exception as e: - logger.error(f"{arparma.header_result}: {case_name}", session=session, e=e) - await MessageUtils.build_message( - f"成功{arparma.header_result}: {case_name} 发生错误: {type(e)}: {e}" - ).send() - - -@_show_case_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - case_name = None - if name.available: - case_name = name.result.strip() - result = await build_case_image(case_name) - if isinstance(result, str): - await MessageUtils.build_message(result).send() - else: - await MessageUtils.build_message(result).send() - logger.info("查看武器箱", arparma.header_result, session=session) - - -@_update_image_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - case_name = None - if name.available: - case_name = name.result.strip() - await MessageUtils.build_message("开始更新图片...").send(reply_to=True) - await download_image(case_name) - await MessageUtils.build_message("更新图片完成...").send(at_sender=True) - logger.info("更新武器箱图片", arparma.header_result, session=session) - - -# 重置开箱 -@scheduler.scheduled_job( - "cron", - hour=0, - minute=1, -) -async def _(): - await reset_count_daily() - - -@scheduler.scheduled_job( - "cron", - hour=0, - minute=10, -) -async def _(): - now = datetime.now() - hour = random.choice([0, 1, 2, 3]) - date = now + timedelta(hours=hour) - logger.debug(f"将在 {date} 时自动更新武器箱...", "更新武器箱") - scheduler.add_job( - auto_update, - "date", - run_date=date.replace(microsecond=0), - id=f"auto_update_csgo_cases", - ) diff --git a/zhenxun/plugins/open_cases/build_image.py b/zhenxun/plugins/open_cases/build_image.py deleted file mode 100644 index 8b8db8e2..00000000 --- a/zhenxun/plugins/open_cases/build_image.py +++ /dev/null @@ -1,155 +0,0 @@ -from datetime import timedelta, timezone - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.utils import cn2py - -from .config import COLOR2COLOR, COLOR2NAME -from .models.buff_skin import BuffSkin - -BASE_PATH = IMAGE_PATH / "csgo_cases" - -ICON_PATH = IMAGE_PATH / "_icon" - - -async def draw_card(skin: BuffSkin, rand: str) -> BuildImage: - """构造抽取图片 - - 参数: - skin (BuffSkin): BuffSkin - rand (str): 磨损 - - 返回: - BuildImage: BuildImage - """ - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - file_path = BASE_PATH / cn2py(skin.case_name.split(",")[0]) / f"{cn2py(name)}.jpg" - if not file_path.exists(): - logger.warning(f"皮肤图片: {name} 不存在", "开箱") - skin_bk = BuildImage( - 460, 200, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" - ) - if file_path.exists(): - skin_image = BuildImage(205, 153, background=file_path) - await skin_bk.paste(skin_image, (10, 30)) - await skin_bk.line((220, 10, 220, 180)) - await skin_bk.text((10, 10), skin.name, (255, 255, 255)) - name_icon = BuildImage(20, 20, background=ICON_PATH / "name_white.png") - await skin_bk.paste(name_icon, (240, 13)) - await skin_bk.text((265, 15), f"名称:", (255, 255, 255), font_size=20) - await skin_bk.text( - (310, 15), - f"{skin.skin_name + ('(St)' if skin.is_stattrak else '')}", - (255, 255, 255), - ) - tone_icon = BuildImage(20, 20, background=ICON_PATH / "tone_white.png") - await skin_bk.paste(tone_icon, (240, 45)) - await skin_bk.text((265, 45), "品质:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 45), COLOR2NAME[skin.color][:2], COLOR2COLOR[skin.color]) - type_icon = BuildImage(20, 20, background=ICON_PATH / "type_white.png") - await skin_bk.paste(type_icon, (240, 73)) - await skin_bk.text((265, 75), "类型:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 75), skin.weapon_type, (255, 255, 255)) - price_icon = BuildImage(20, 20, background=ICON_PATH / "price_white.png") - await skin_bk.paste(price_icon, (240, 103)) - await skin_bk.text((265, 105), "价格:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 105), str(skin.sell_min_price), (0, 255, 98)) - abrasion_icon = BuildImage(20, 20, background=ICON_PATH / "abrasion_white.png") - await skin_bk.paste(abrasion_icon, (240, 133)) - await skin_bk.text((265, 135), "磨损:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 135), skin.abrasion, (255, 255, 255)) - await skin_bk.text((228, 165), f"({rand})", (255, 255, 255)) - return skin_bk - - -async def generate_skin(skin: BuffSkin, update_count: int) -> BuildImage | None: - """构造皮肤图片 - - 参数: - skin (BuffSkin): BuffSkin - - 返回: - BuildImage | None: 图片 - """ - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - file_path = BASE_PATH / cn2py(skin.case_name.split(",")[0]) / f"{cn2py(name)}.jpg" - if not file_path.exists(): - logger.warning(f"皮肤图片: {name} 不存在", "查看武器箱") - if skin.color == "CASE": - case_bk = BuildImage( - 700, 200, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" - ) - if file_path.exists(): - skin_img = BuildImage(200, 200, background=file_path) - await case_bk.paste(skin_img, (10, 10)) - await case_bk.line((250, 10, 250, 190)) - await case_bk.line((280, 160, 660, 160)) - name_icon = BuildImage(30, 30, background=ICON_PATH / "box_white.png") - await case_bk.paste(name_icon, (260, 25)) - await case_bk.text((295, 30), "名称:", (255, 255, 255)) - await case_bk.text((345, 30), skin.case_name, (255, 0, 38), font_size=30) - - type_icon = BuildImage(30, 30, background=ICON_PATH / "type_white.png") - await case_bk.paste(type_icon, (260, 70)) - await case_bk.text((295, 75), "类型:", (255, 255, 255)) - await case_bk.text((345, 75), "武器箱", (0, 157, 255), font_size=30) - - price_icon = BuildImage(30, 30, background=ICON_PATH / "price_white.png") - await case_bk.paste(price_icon, (260, 114)) - await case_bk.text((295, 120), "单价:", (255, 255, 255)) - await case_bk.text( - (340, 120), str(skin.sell_min_price), (0, 255, 98), font_size=30 - ) - - update_count_icon = BuildImage( - 40, 40, background=ICON_PATH / "reload_white.png" - ) - await case_bk.paste(update_count_icon, (575, 10)) - await case_bk.text((625, 12), str(update_count), (255, 255, 255), font_size=45) - - num_icon = BuildImage(30, 30, background=ICON_PATH / "num_white.png") - await case_bk.paste(num_icon, (455, 70)) - await case_bk.text((490, 75), "在售:", (255, 255, 255)) - await case_bk.text((535, 75), str(skin.sell_num), (144, 0, 255), font_size=30) - - want_buy_icon = BuildImage(30, 30, background=ICON_PATH / "want_buy_white.png") - await case_bk.paste(want_buy_icon, (455, 114)) - await case_bk.text((490, 120), "求购:", (255, 255, 255)) - await case_bk.text((535, 120), str(skin.buy_num), (144, 0, 255), font_size=30) - - await case_bk.text((275, 165), "更新时间", (255, 255, 255), font_size=22) - date = str( - skin.update_time.replace(microsecond=0).astimezone( - timezone(timedelta(hours=8)) - ) - ).split("+")[0] - await case_bk.text( - (350, 165), - date, - (255, 255, 255), - font_size=30, - ) - return case_bk - else: - skin_bk = BuildImage( - 235, 250, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" - ) - if file_path.exists(): - skin_image = BuildImage(205, 153, background=file_path) - await skin_bk.paste(skin_image, (10, 30)) - update_count_icon = BuildImage( - 35, 35, background=ICON_PATH / "reload_white.png" - ) - await skin_bk.line((10, 180, 220, 180)) - await skin_bk.text((10, 10), skin.name, (255, 255, 255)) - await skin_bk.paste(update_count_icon, (140, 10)) - await skin_bk.text((175, 15), str(update_count), (255, 255, 255)) - await skin_bk.text((10, 185), f"{skin.skin_name}", (255, 255, 255), "width") - await skin_bk.text((10, 218), "品质:", (255, 255, 255)) - await skin_bk.text( - (55, 218), COLOR2NAME[skin.color][:2], COLOR2COLOR[skin.color] - ) - await skin_bk.text((100, 218), "类型:", (255, 255, 255)) - await skin_bk.text((145, 218), skin.weapon_type, (255, 255, 255)) - return skin_bk diff --git a/zhenxun/plugins/open_cases/command.py b/zhenxun/plugins/open_cases/command.py deleted file mode 100644 index ea86c2fc..00000000 --- a/zhenxun/plugins/open_cases/command.py +++ /dev/null @@ -1,75 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot_plugin_alconna import Alconna, Args, Option, on_alconna, store_true - -from zhenxun.utils.rules import ensure_group - -_open_matcher = on_alconna( - Alconna("开箱", Args["name?", str]), priority=5, block=True, rule=ensure_group -) - -_reload_matcher = on_alconna( - Alconna("重置开箱"), priority=5, block=True, permission=SUPERUSER, rule=ensure_group -) - -_my_open_matcher = on_alconna( - Alconna("我的开箱"), - aliases={"开箱统计", "开箱查询", "查询开箱"}, - priority=5, - block=True, - rule=ensure_group, -) - -_group_open_matcher = on_alconna( - Alconna("群开箱统计"), priority=5, block=True, rule=ensure_group -) - -_multiple_matcher = on_alconna( - Alconna("multiple-open", Args["num", int]["name?", str]), - priority=5, - block=True, - rule=ensure_group, -) - -_multiple_matcher.shortcut( - r"(?P\d+)连开箱(?P.*?)", - command="multiple-open", - arguments=["{num}", "{name}"], - prefix=True, -) - -_update_matcher = on_alconna( - Alconna( - "更新武器箱", - Args["name?", str], - Option("-s", action=store_true, help_text="是否必定更新所属箱子"), - ), - aliases={"更新皮肤"}, - priority=1, - permission=SUPERUSER, - block=True, -) - -_update_image_matcher = on_alconna( - Alconna("更新武器箱图片", Args["name?", str]), - priority=1, - permission=SUPERUSER, - block=True, -) - -_show_case_matcher = on_alconna( - Alconna("查看武器箱", Args["name?", str]), priority=5, block=True -) - -_knifes_matcher = on_alconna( - Alconna("我的金色"), priority=5, block=True, rule=ensure_group -) - -_show_skin_matcher = on_alconna(Alconna("查看皮肤"), priority=5, block=True) - -_price_matcher = on_alconna( - Alconna( - "价格趋势", Args["name", str]["skin", str]["abrasion", str]["day?", int, 7] - ), - priority=5, - block=True, -) diff --git a/zhenxun/plugins/open_cases/config.py b/zhenxun/plugins/open_cases/config.py deleted file mode 100644 index cefa7384..00000000 --- a/zhenxun/plugins/open_cases/config.py +++ /dev/null @@ -1,253 +0,0 @@ -import random -from enum import Enum - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger - -from .models.buff_skin import BuffSkin - -BLUE = 0.7981 -BLUE_ST = 0.0699 -PURPLE = 0.1626 -PURPLE_ST = 0.0164 -PINK = 0.0315 -PINK_ST = 0.0048 -RED = 0.0057 -RED_ST = 0.00021 -KNIFE = 0.0021 -KNIFE_ST = 0.000041 - -# 崭新 -FACTORY_NEW_S = 0 -FACTORY_NEW_E = 0.0699999 -# 略磨 -MINIMAL_WEAR_S = 0.07 -MINIMAL_WEAR_E = 0.14999 -# 久经 -FIELD_TESTED_S = 0.15 -FIELD_TESTED_E = 0.37999 -# 破损 -WELL_WORN_S = 0.38 -WELL_WORN_E = 0.44999 -# 战痕 -BATTLE_SCARED_S = 0.45 -BATTLE_SCARED_E = 0.99999 - - -class UpdateType(Enum): - """ - 更新类型 - """ - - CASE = "case" - WEAPON_TYPE = "weapon_type" - - -NAME2COLOR = { - "消费级": "WHITE", - "工业级": "LIGHTBLUE", - "军规级": "BLUE", - "受限": "PURPLE", - "保密": "PINK", - "隐秘": "RED", - "非凡": "KNIFE", -} - -COLOR2NAME = { - "WHITE": "消费级", - "LIGHTBLUE": "工业级", - "BLUE": "军规级", - "PURPLE": "受限", - "PINK": "保密", - "RED": "隐秘", - "KNIFE": "非凡", -} - -COLOR2COLOR = { - "WHITE": (255, 255, 255), - "LIGHTBLUE": (0, 179, 255), - "BLUE": (0, 85, 255), - "PURPLE": (149, 0, 255), - "PINK": (255, 0, 162), - "RED": (255, 34, 0), - "KNIFE": (255, 225, 0), -} - -ABRASION_SORT = ["崭新出厂", "略有磨损", "久经沙场", "破损不堪", "战横累累"] - -CASE_BACKGROUND = IMAGE_PATH / "csgo_cases" / "_background" / "shu" - -# 刀 -KNIFE2ID = { - "鲍伊猎刀": "weapon_knife_survival_bowie", - "蝴蝶刀": "weapon_knife_butterfly", - "弯刀": "weapon_knife_falchion", - "折叠刀": "weapon_knife_flip", - "穿肠刀": "weapon_knife_gut", - "猎杀者匕首": "weapon_knife_tactical", - "M9刺刀": "weapon_knife_m9_bayonet", - "刺刀": "weapon_bayonet", - "爪子刀": "weapon_knife_karambit", - "暗影双匕": "weapon_knife_push", - "短剑": "weapon_knife_stiletto", - "熊刀": "weapon_knife_ursus", - "折刀": "weapon_knife_gypsy_jackknife", - "锯齿爪刀": "weapon_knife_widowmaker", - "海豹短刀": "weapon_knife_css", - "系绳匕首": "weapon_knife_cord", - "求生匕首": "weapon_knife_canis", - "流浪者匕首": "weapon_knife_outdoor", - "骷髅匕首": "weapon_knife_skeleton", - "血猎手套": "weapon_bloodhound_gloves", - "驾驶手套": "weapon_driver_gloves", - "手部束带": "weapon_hand_wraps", - "摩托手套": "weapon_moto_gloves", - "专业手套": "weapon_specialist_gloves", - "运动手套": "weapon_sport_gloves", - "九头蛇手套": "weapon_hydra_gloves", - "狂牙手套": "weapon_brokenfang_gloves", -} - -WEAPON2ID = {} - -# 武器箱 -CASE2ID = { - "变革": "set_community_32", - "反冲": "set_community_31", - "梦魇": "set_community_30", - "激流": "set_community_29", - "蛇噬": "set_community_28", - "狂牙大行动": "set_community_27", - "裂空": "set_community_26", - "棱彩2号": "set_community_25", - "CS20": "set_community_24", - "裂网大行动": "set_community_23", - "棱彩": "set_community_22", - "头号特训": "set_community_21", - "地平线": "set_community_20", - "命悬一线": "set_community_19", - "光谱2号": "set_community_18", - "九头蛇大行动": "set_community_17", - "光谱": "set_community_16", - "手套": "set_community_15", - "伽玛2号": "set_gamma_2", - "伽玛": "set_community_13", - "幻彩3号": "set_community_12", - "野火大行动": "set_community_11", - "左轮": "set_community_10", - "暗影": "set_community_9", - "弯曲猎手": "set_community_8", - "幻彩2号": "set_community_7", - "幻彩": "set_community_6", - "先锋": "set_community_5", - "电竞2014夏季": "set_esports_iii", - "突围大行动": "set_community_4", - "猎杀者": "set_community_3", - "凤凰": "set_community_2", - "电竞2013冬季": "set_esports_ii", - "冬季攻势": "set_community_1", - "军火交易3号": "set_weapons_iii", - "英勇": "set_bravo_i", - "电竞2013": "set_esports", - "军火交易2号": "set_weapons_ii", - "军火交易": "set_weapons_i", -} - - -def get_wear(rand: float) -> str: - """判断磨损度 - - Args: - rand (float): 随机rand - - Returns: - str: 磨损名称 - """ - if rand <= FACTORY_NEW_E: - return "崭新出厂" - if MINIMAL_WEAR_S <= rand <= MINIMAL_WEAR_E: - return "略有磨损" - if FIELD_TESTED_S <= rand <= FIELD_TESTED_E: - return "久经沙场" - if WELL_WORN_S <= rand <= WELL_WORN_E: - return "破损不堪" - return "战痕累累" - - -def random_color_and_st(rand: float) -> tuple[str, bool]: - """获取皮肤品质及是否暗金 - - 参数: - rand (float): 随机rand - - 返回: - tuple[str, bool]: 品质,是否暗金 - """ - if rand <= KNIFE: - if random.random() <= KNIFE_ST: - return ("KNIFE", True) - return ("KNIFE", False) - elif KNIFE < rand <= RED: - if random.random() <= RED_ST: - return ("RED", True) - return ("RED", False) - elif RED < rand <= PINK: - if random.random() <= PINK_ST: - return ("PINK", True) - return ("PINK", False) - elif PINK < rand <= PURPLE: - if random.random() <= PURPLE_ST: - return ("PURPLE", True) - return ("PURPLE", False) - else: - if random.random() <= BLUE_ST: - return ("BLUE", True) - return ("BLUE", False) - - -async def random_skin(num: int, case_name: str) -> list[tuple[BuffSkin, float]]: - """ - 随机抽取皮肤 - """ - case_name = case_name.replace("武器箱", "").replace(" ", "") - color_map = {} - for _ in range(num): - rand = random.random() - # 尝试降低磨损 - if rand > MINIMAL_WEAR_E: - for _ in range(2): - if random.random() < 0.5: - logger.debug(f"[START]开箱随机磨损触发降低磨损条件: {rand}") - if random.random() < 0.2: - rand /= 3 - else: - rand /= 2 - logger.debug(f"[END]开箱随机磨损触发降低磨损条件: {rand}") - break - abrasion = get_wear(rand) - logger.debug(f"开箱随机磨损: {rand} | {abrasion}") - color, is_stattrak = random_color_and_st(rand) - if not color_map.get(color): - color_map[color] = {} - if is_stattrak: - if not color_map[color].get(f"{abrasion}_st"): - color_map[color][f"{abrasion}_st"] = [] - color_map[color][f"{abrasion}_st"].append(rand) - else: - if not color_map[color].get(abrasion): - color_map[color][f"{abrasion}"] = [] - color_map[color][f"{abrasion}"].append(rand) - skin_list = [] - for color in color_map: - for abrasion in color_map[color]: - rand_list = color_map[color][abrasion] - is_stattrak = "_st" in abrasion - abrasion = abrasion.replace("_st", "") - skin_list_ = await BuffSkin.random_skin( - len(rand_list), color, abrasion, is_stattrak, case_name - ) - skin_list += [(skin, rand) for skin, rand in zip(skin_list_, rand_list)] - return skin_list - - -# M249(StatTrak™) | 等高线 diff --git a/zhenxun/plugins/open_cases/models/buff_prices.py b/zhenxun/plugins/open_cases/models/buff_prices.py deleted file mode 100644 index 9f53de0e..00000000 --- a/zhenxun/plugins/open_cases/models/buff_prices.py +++ /dev/null @@ -1,22 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - -# 1.狂牙武器箱 - - -class BuffPrice(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - case_id = fields.IntField() - """箱子id""" - skin_name = fields.CharField(255, unique=True) - """皮肤名称""" - skin_price = fields.FloatField() - """皮肤价格""" - update_date = fields.DatetimeField() - - class Meta: - table = "buff_prices" - table_description = "Buff价格数据表" diff --git a/zhenxun/plugins/open_cases/models/buff_skin.py b/zhenxun/plugins/open_cases/models/buff_skin.py deleted file mode 100644 index 7f51221a..00000000 --- a/zhenxun/plugins/open_cases/models/buff_skin.py +++ /dev/null @@ -1,113 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class BuffSkin(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - case_name: str = fields.CharField(255) # type: ignore - """箱子名称""" - name: str = fields.CharField(255) # type: ignore - """武器/手套/刀名称""" - skin_name: str = fields.CharField(255) # type: ignore - """皮肤名称""" - is_stattrak = fields.BooleanField(default=False) - """是否暗金(计数)""" - abrasion = fields.CharField(255) - """磨损度""" - color = fields.CharField(255) - """颜色(品质)""" - skin_id = fields.CharField(255, null=True, unique=True) - """皮肤id""" - - img_url = fields.CharField(255) - """图片url""" - steam_price = fields.FloatField(default=0) - """steam价格""" - weapon_type = fields.CharField(255) - """枪械类型""" - buy_max_price = fields.FloatField(default=0) - """最大求购价格""" - buy_num = fields.IntField(default=0) - """求购数量""" - sell_min_price = fields.FloatField(default=0) - """售卖最低价格""" - sell_num = fields.IntField(default=0) - """出售个数""" - sell_reference_price = fields.FloatField(default=0) - """参考价格""" - - create_time = fields.DatetimeField(auto_add_now=True) - """创建日期""" - update_time = fields.DatetimeField(auto_add=True) - """更新日期""" - - class Meta: - table = "buff_skin" - table_description = "Buff皮肤数据表" - # unique_together = ("case_name", "name", "skin_name", "abrasion", "is_stattrak") - - def __eq__(self, other: "BuffSkin"): - - return self.skin_id == other.skin_id - - def __hash__(self): - - return hash(self.case_name + self.name + self.skin_name + str(self.is_stattrak)) - - @classmethod - async def random_skin( - cls, - num: int, - color: str, - abrasion: str, - is_stattrak: bool = False, - case_name: str | None = None, - ) -> list["BuffSkin"]: # type: ignore - """随机皮肤 - - 参数: - num: 数量 - color: 品质 - abrasion: 磨损度 - is_stattrak: 是否暗金 - case_name: 箱子名称 - - 返回: - list["BuffSkin"]: 皮肤列表 - """ - query = cls - if case_name: - query = query.filter(case_name__contains=case_name) - query = query.filter(abrasion=abrasion, is_stattrak=is_stattrak, color=color) - skin_list = await query.annotate(rand=Random()).limit(num) # type:ignore - num_ = num - cnt = 0 - while len(skin_list) < num: - cnt += 1 - num_ = num - len(skin_list) - skin_list += await query.annotate(rand=Random()).limit(num_) - if cnt > 10: - break - return skin_list # type: ignore - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE buff_skin ADD img_url varchar(255);", # 新增img_url - "ALTER TABLE buff_skin ADD skin_id varchar(255);", # 新增skin_id - "ALTER TABLE buff_skin ADD steam_price float DEFAULT 0;", # 新增steam_price - "ALTER TABLE buff_skin ADD weapon_type varchar(255);", # 新增type - "ALTER TABLE buff_skin ADD buy_max_price float DEFAULT 0;", # 新增buy_max_price - "ALTER TABLE buff_skin ADD buy_num Integer DEFAULT 0;", # 新增buy_max_price - "ALTER TABLE buff_skin ADD sell_min_price float DEFAULT 0;", # 新增sell_min_price - "ALTER TABLE buff_skin ADD sell_num Integer DEFAULT 0;", # 新增sell_num - "ALTER TABLE buff_skin ADD sell_reference_price float DEFAULT 0;", # 新增sell_reference_price - "ALTER TABLE buff_skin DROP COLUMN skin_price;", # 删除skin_price - "alter table buff_skin drop constraint if EXISTS uid_buff_skin_case_na_c35c93;", # 删除唯一约束 - "UPDATE buff_skin set case_name='手套' where case_name='手套武器箱'", - "UPDATE buff_skin set case_name='左轮' where case_name='左轮武器箱'", - ] diff --git a/zhenxun/plugins/open_cases/models/buff_skin_log.py b/zhenxun/plugins/open_cases/models/buff_skin_log.py deleted file mode 100644 index ac9fec95..00000000 --- a/zhenxun/plugins/open_cases/models/buff_skin_log.py +++ /dev/null @@ -1,50 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class BuffSkinLog(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - case_name = fields.CharField(255) - """箱子名称""" - name = fields.CharField(255) - """武器/手套/刀名称""" - skin_name = fields.CharField(255) - """皮肤名称""" - is_stattrak = fields.BooleanField(default=False) - """是否暗金(计数)""" - abrasion = fields.CharField(255) - """磨损度""" - color = fields.CharField(255) - """颜色(品质)""" - - steam_price = fields.FloatField(default=0) - """steam价格""" - weapon_type = fields.CharField(255) - """枪械类型""" - buy_max_price = fields.FloatField(default=0) - """最大求购价格""" - buy_num = fields.IntField(default=0) - """求购数量""" - sell_min_price = fields.FloatField(default=0) - """售卖最低价格""" - sell_num = fields.IntField(default=0) - """出售个数""" - sell_reference_price = fields.FloatField(default=0) - """参考价格""" - - create_time = fields.DatetimeField(auto_add_now=True) - """创建日期""" - - class Meta: - table = "buff_skin_log" - table_description = "Buff皮肤更新日志表" - - @classmethod - async def _run_script(cls): - return [ - "UPDATE buff_skin_log set case_name='手套' where case_name='手套武器箱'", - "UPDATE buff_skin_log set case_name='左轮' where case_name='左轮武器箱'", - ] diff --git a/zhenxun/plugins/open_cases/models/open_cases_log.py b/zhenxun/plugins/open_cases/models/open_cases_log.py deleted file mode 100644 index 0c4f87bb..00000000 --- a/zhenxun/plugins/open_cases/models/open_cases_log.py +++ /dev/null @@ -1,44 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class OpenCasesLog(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - case_name = fields.CharField(255) - """箱子名称""" - name = fields.CharField(255) - """武器/手套/刀名称""" - skin_name = fields.CharField(255) - """皮肤名称""" - is_stattrak = fields.BooleanField(default=False) - """是否暗金(计数)""" - abrasion = fields.CharField(255) - """磨损度""" - abrasion_value = fields.FloatField() - """磨损数值""" - color = fields.CharField(255) - """颜色(品质)""" - price = fields.FloatField(default=0) - """价格""" - create_time = fields.DatetimeField(auto_add_now=True) - """创建日期""" - - class Meta: - table = "open_cases_log" - table_description = "开箱日志表" - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE open_cases_log RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE open_cases_log ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE open_cases_log ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/open_cases/models/open_cases_user.py b/zhenxun/plugins/open_cases/models/open_cases_user.py deleted file mode 100644 index 3ed43937..00000000 --- a/zhenxun/plugins/open_cases/models/open_cases_user.py +++ /dev/null @@ -1,60 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class OpenCasesUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - total_count = fields.IntField(default=0) - """总开启次数""" - blue_count = fields.IntField(default=0) - """蓝色""" - blue_st_count = fields.IntField(default=0) - """蓝色暗金""" - purple_count = fields.IntField(default=0) - """紫色""" - purple_st_count = fields.IntField(default=0) - """紫色暗金""" - pink_count = fields.IntField(default=0) - """粉色""" - pink_st_count = fields.IntField(default=0) - """粉色暗金""" - red_count = fields.IntField(default=0) - """紫色""" - red_st_count = fields.IntField(default=0) - """紫色暗金""" - knife_count = fields.IntField(default=0) - """金色""" - knife_st_count = fields.IntField(default=0) - """金色暗金""" - spend_money = fields.IntField(default=0) - """花费金币""" - make_money = fields.FloatField(default=0) - """赚取金币""" - today_open_total = fields.IntField(default=0) - """今日开箱数量""" - open_cases_time_last = fields.DatetimeField() - """最后开箱日期""" - knifes_name = fields.TextField(default="") - """已获取金色""" - - class Meta: - table = "open_cases_users" - table_description = "开箱统计数据表" - unique_together = ("user_id", "group_id") - - @classmethod - async def _run_script(cls): - return [ - "alter table open_cases_users alter COLUMN make_money type float;", # 将make_money字段改为float - "alter table open_cases_users alter COLUMN spend_money type float;", # 将spend_money字段改为float - "ALTER TABLE open_cases_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE open_cases_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE open_cases_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/open_cases/open_cases_c.py b/zhenxun/plugins/open_cases/open_cases_c.py deleted file mode 100644 index f56aa9fe..00000000 --- a/zhenxun/plugins/open_cases/open_cases_c.py +++ /dev/null @@ -1,481 +0,0 @@ -import asyncio -import random -import re -from datetime import datetime - -from nonebot_plugin_alconna import UniMessage -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.models.sign_user import SignUser -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import cn2py - -from .build_image import draw_card -from .config import * -from .models.open_cases_log import OpenCasesLog -from .models.open_cases_user import OpenCasesUser -from .utils import CaseManager, update_skin_data - -RESULT_MESSAGE = { - "BLUE": ["这样看着才舒服", "是自己人,大伙把刀收好", "非常舒适~"], - "PURPLE": ["还行吧,勉强接受一下下", "居然不是蓝色,太假了", "运气-1-1-1-1-1..."], - "PINK": ["开始不适....", "你妈妈买菜必涨价!涨三倍!", "你最近不适合出门,真的"], - "RED": [ - "已经非常不适", - "好兄弟你开的什么箱子啊,一般箱子不是只有蓝色的吗", - "开始拿阳寿开箱子了?", - ], - "KNIFE": [ - "你的好运我收到了,你可以去喂鲨鱼了", - "最近该吃啥就迟点啥吧,哎,好好的一个人怎么就....哎", - "众所周知,欧皇寿命极短.", - ], -} - -COLOR2NAME = { - "BLUE": "军规", - "PURPLE": "受限", - "PINK": "保密", - "RED": "隐秘", - "KNIFE": "罕见", -} - -COLOR2CN = {"BLUE": "蓝", "PURPLE": "紫", "PINK": "粉", "RED": "红", "KNIFE": "金"} - - -def add_count(user: OpenCasesUser, skin: BuffSkin, case_price: float): - if skin.color == "BLUE": - if skin.is_stattrak: - user.blue_st_count += 1 - else: - user.blue_count += 1 - elif skin.color == "PURPLE": - if skin.is_stattrak: - user.purple_st_count += 1 - else: - user.purple_count += 1 - elif skin.color == "PINK": - if skin.is_stattrak: - user.pink_st_count += 1 - else: - user.pink_count += 1 - elif skin.color == "RED": - if skin.is_stattrak: - user.red_st_count += 1 - else: - user.red_count += 1 - elif skin.color == "KNIFE": - if skin.is_stattrak: - user.knife_st_count += 1 - else: - user.knife_count += 1 - user.make_money += skin.sell_min_price - user.spend_money += int(17 + case_price) - - -async def get_user_max_count(user_id: str) -> int: - """获取用户每日最大开箱次数 - - 参数: - user_id: 用户id - - 返回: - int: 最大开箱次数 - """ - user, _ = await SignUser.get_or_create(user_id=user_id) - impression = int(user.impression) - initial_open_case_count = Config.get_config("open_cases", "INITIAL_OPEN_CASE_COUNT") - each_impression_add_count = Config.get_config( - "open_cases", "EACH_IMPRESSION_ADD_COUNT" - ) - return int(initial_open_case_count + impression / each_impression_add_count) # type: ignore - - -async def open_case( - user_id: str, group_id: str, case_name: str | None, session: EventSession -) -> UniMessage: - """开箱 - - 参数: - user_id: 用户id - group_id : 群号 - case_name: 武器箱名称. Defaults to "狂牙大行动". - session: EventSession - - 返回: - Union[str, Message]: 回复消息 - """ - user_id = str(user_id) - group_id = str(group_id) - if not CaseManager.CURRENT_CASES: - return MessageUtils.build_message("未收录任何武器箱") - if not case_name: - case_name = random.choice(CaseManager.CURRENT_CASES) # type: ignore - if case_name not in CaseManager.CURRENT_CASES: - return "武器箱未收录, 当前可用武器箱:\n" + ", ".join(CaseManager.CURRENT_CASES) # type: ignore - logger.debug( - f"尝试开启武器箱: {case_name}", "开箱", session=user_id, group_id=group_id - ) - case = cn2py(case_name) # type: ignore - user = await OpenCasesUser.get_or_none(user_id=user_id, group_id=group_id) - if not user: - user = await OpenCasesUser.create( - user_id=user_id, group_id=group_id, open_cases_time_last=datetime.now() - ) - max_count = await get_user_max_count(user_id) - # 一天次数上限 - if user.today_open_total >= max_count: - return MessageUtils.build_message( - f"今天已达开箱上限了喔,明天再来吧\n(提升好感度可以增加每日开箱数 #疯狂暗示)" - ) - skin_list = await random_skin(1, case_name) # type: ignore - if not skin_list: - return MessageUtils.build_message("未抽取到任何皮肤") - skin, rand = skin_list[0] - rand = str(rand)[:11] - case_price = 0 - if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"): - case_price = case_skin.sell_min_price - user.today_open_total += 1 - user.total_count += 1 - user.open_cases_time_last = datetime.now() - await user.save( - update_fields=["today_open_total", "total_count", "open_cases_time_last"] - ) - add_count(user, skin, case_price) - ridicule_result = random.choice(RESULT_MESSAGE[skin.color]) - price_result = skin.sell_min_price - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - img_path = IMAGE_PATH / "csgo_cases" / case / f"{cn2py(name)}.jpg" - logger.info( - f"开启{case_name}武器箱获得 {skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion}) 磨损: [{rand}] 价格: {skin.sell_min_price}", - "开箱", - session=session, - ) - await user.save() - await OpenCasesLog.create( - user_id=user_id, - group_id=group_id, - case_name=case_name, - name=skin.name, - skin_name=skin.skin_name, - is_stattrak=skin.is_stattrak, - abrasion=skin.abrasion, - color=skin.color, - price=skin.sell_min_price, - abrasion_value=rand, - create_time=datetime.now(), - ) - logger.debug(f"添加 1 条开箱日志", "开箱", session=session) - over_count = max_count - user.today_open_total - img = await draw_card(skin, rand) - return MessageUtils.build_message( - [ - f"开启{case_name}武器箱.\n剩余开箱次数:{over_count}.\n", - img, - f"\n箱子单价:{case_price}\n花费:{17 + case_price:.2f}\n:{ridicule_result}", - ] - ) - - -async def open_multiple_case( - user_id: str, - group_id: str, - case_name: str | None, - num: int = 10, - session: EventSession | None = None, -) -> UniMessage: - """多连开箱 - - 参数: - user_id (int): 用户id - group_id (int): 群号 - case_name (str): 箱子名称 - num (int, optional): 数量. Defaults to 10. - session: EventSession - - 返回: - _type_: _description_ - """ - user_id = str(user_id) - group_id = str(group_id) - if not CaseManager.CURRENT_CASES: - return MessageUtils.build_message("未收录任何武器箱") - if not case_name: - case_name = random.choice(CaseManager.CURRENT_CASES) # type: ignore - if case_name not in CaseManager.CURRENT_CASES: - return MessageUtils.build_message( - "武器箱未收录, 当前可用武器箱:\n" + ", ".join(CaseManager.CURRENT_CASES) - ) - user, _ = await OpenCasesUser.get_or_create( - user_id=user_id, - group_id=group_id, - defaults={"open_cases_time_last": datetime.now()}, - ) - max_count = await get_user_max_count(user_id) - if user.today_open_total >= max_count: - return MessageUtils.build_message( - f"今天已达开箱上限了喔,明天再来吧\n(提升好感度可以增加每日开箱数 #疯狂暗示)" - ) - if max_count - user.today_open_total < num: - return MessageUtils.build_message( - f"今天开箱次数不足{num}次噢,请单抽试试看(也许单抽运气更好?)" - f"\n剩余开箱次数:{max_count - user.today_open_total}" - ) - logger.debug(f"尝试开启武器箱: {case_name}", "开箱", session=session) - case = cn2py(case_name) # type: ignore - skin_count = {} - img_list = [] - skin_list = await random_skin(num, case_name) # type: ignore - if not skin_list: - return MessageUtils.build_message("未抽取到任何皮肤...") - total_price = 0 - log_list = [] - now = datetime.now() - user.today_open_total += num - user.total_count += num - user.open_cases_time_last = datetime.now() - await user.save( - update_fields=["today_open_total", "total_count", "open_cases_time_last"] - ) - case_price = 0 - if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"): - case_price = case_skin.sell_min_price - img_w, img_h = 0, 0 - for skin, rand in skin_list: - img = await draw_card(skin, str(rand)[:11]) - img_w, img_h = img.size - total_price += skin.sell_min_price - color_name = COLOR2CN[skin.color] - if not skin_count.get(color_name): - skin_count[color_name] = 0 - skin_count[color_name] += 1 - add_count(user, skin, case_price) - img_list.append(img) - logger.info( - f"开启{case_name}武器箱获得 {skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion}) 磨损: [{rand:.11f}] 价格: {skin.sell_min_price}", - "开箱", - session=session, - ) - log_list.append( - OpenCasesLog( - user_id=user_id, - group_id=group_id, - case_name=case_name, - name=skin.name, - skin_name=skin.skin_name, - is_stattrak=skin.is_stattrak, - abrasion=skin.abrasion, - color=skin.color, - price=skin.sell_min_price, - abrasion_value=rand, - create_time=now, - ) - ) - await user.save() - if log_list: - await OpenCasesLog.bulk_create(log_list, 10) - logger.debug(f"添加 {len(log_list)} 条开箱日志", "开箱", session=session) - img_w += 10 - img_h += 10 - w = img_w * 5 - if num < 5: - h = img_h - 10 - w = img_w * num - elif not num % 5: - h = img_h * int(num / 5) - else: - h = img_h * int(num / 5) + img_h - mark_image = BuildImage(w - 10, h - 10, color=(255, 255, 255)) - mark_image = await mark_image.auto_paste(img_list, 5, padding=20) - over_count = max_count - user.today_open_total - result = "" - for color_name in skin_count: - result += f"[{color_name}:{skin_count[color_name]}] " - return MessageUtils.build_message( - [ - f"开启{case_name}武器箱\n剩余开箱次数:{over_count}\n", - mark_image, - f"\n{result[:-1]}\n箱子单价:{case_price}\n总获取金额:{total_price:.2f}\n总花费:{(17 + case_price) * num:.2f}", - ] - ) - - -async def total_open_statistics(user_id: str, group_id: str) -> str: - user, _ = await OpenCasesUser.get_or_create(user_id=user_id, group_id=group_id) - return ( - f"开箱总数:{user.total_count}\n" - f"今日开箱:{user.today_open_total}\n" - f"蓝色军规:{user.blue_count}\n" - f"蓝色暗金:{user.blue_st_count}\n" - f"紫色受限:{user.purple_count}\n" - f"紫色暗金:{user.purple_st_count}\n" - f"粉色保密:{user.pink_count}\n" - f"粉色暗金:{user.pink_st_count}\n" - f"红色隐秘:{user.red_count}\n" - f"红色暗金:{user.red_st_count}\n" - f"金色罕见:{user.knife_count}\n" - f"金色暗金:{user.knife_st_count}\n" - f"花费金额:{user.spend_money}\n" - f"获取金额:{user.make_money:.2f}\n" - f"最后开箱日期:{user.open_cases_time_last.date()}" - ) - - -async def group_statistics(group_id: str): - user_list = await OpenCasesUser.filter(group_id=str(group_id)).all() - # lan zi fen hong jin pricei - uplist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0] - for user in user_list: - uplist[0] += user.blue_count - uplist[1] += user.blue_st_count - uplist[2] += user.purple_count - uplist[3] += user.purple_st_count - uplist[4] += user.pink_count - uplist[5] += user.pink_st_count - uplist[6] += user.red_count - uplist[7] += user.red_st_count - uplist[8] += user.knife_count - uplist[9] += user.knife_st_count - uplist[10] += user.make_money - uplist[11] += user.total_count - uplist[12] += user.today_open_total - return ( - f"群开箱总数:{uplist[11]}\n" - f"群今日开箱:{uplist[12]}\n" - f"蓝色军规:{uplist[0]}\n" - f"蓝色暗金:{uplist[1]}\n" - f"紫色受限:{uplist[2]}\n" - f"紫色暗金:{uplist[3]}\n" - f"粉色保密:{uplist[4]}\n" - f"粉色暗金:{uplist[5]}\n" - f"红色隐秘:{uplist[6]}\n" - f"红色暗金:{uplist[7]}\n" - f"金色罕见:{uplist[8]}\n" - f"金色暗金:{uplist[9]}\n" - f"花费金额:{uplist[11] * 17}\n" - f"获取金额:{uplist[10]:.2f}" - ) - - -async def get_my_knifes(user_id: str, group_id: str) -> UniMessage: - """获取我的金色 - - 参数: - user_id (str): 用户id - group_id (str): 群号 - - 返回: - MessageFactory: 回复消息或图片 - """ - data_list = await get_old_knife(str(user_id), str(group_id)) - data_list += await OpenCasesLog.filter( - user_id=user_id, group_id=group_id, color="KNIFE" - ).all() - if not data_list: - return MessageUtils.build_message("您木有开出金色级别的皮肤喔...") - length = len(data_list) - if length < 5: - h = 600 - w = length * 540 - elif length % 5 == 0: - h = 600 * int(length / 5) - w = 540 * 5 - else: - h = 600 * int(length / 5) + 600 - w = 540 * 5 - A = BuildImage(w, h) - image_list = [] - for skin in data_list: - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - img_path = ( - IMAGE_PATH / "csgo_cases" / cn2py(skin.case_name) / f"{cn2py(name)}.jpg" - ) - knife_img = BuildImage(470, 600, font_size=20) - await knife_img.paste( - BuildImage(470, 470, background=img_path if img_path.exists() else None), - (0, 0), - ) - await knife_img.text( - (5, 500), f"\t{skin.name}|{skin.skin_name}({skin.abrasion})" - ) - await knife_img.text((5, 530), f"\t磨损:{skin.abrasion_value}") - await knife_img.text((5, 560), f"\t价格:{skin.price}") - image_list.append(knife_img) - A = await A.auto_paste(image_list, 5) - return MessageUtils.build_message(A) - - -async def get_old_knife(user_id: str, group_id: str) -> list[OpenCasesLog]: - """获取旧数据字段 - - 参数: - user_id (str): 用户id - group_id (str): 群号 - - 返回: - list[OpenCasesLog]: 旧数据兼容 - """ - user, _ = await OpenCasesUser.get_or_create(user_id=user_id, group_id=group_id) - knifes_name = user.knifes_name - data_list = [] - if knifes_name: - knifes_list = knifes_name[:-1].split(",") - for knife in knifes_list: - try: - if r := re.search( - "(.*)\|\|(.*) \| (.*)\((.*)\) 磨损:(.*), 价格:(.*)", knife - ): - case_name_py = r.group(1) - name = r.group(2) - skin_name = r.group(3) - abrasion = r.group(4) - abrasion_value = r.group(5) - price = r.group(6) - name = name.replace("(StatTrak™)", "") - data_list.append( - OpenCasesLog( - user_id=user_id, - group_id=group_id, - name=name.strip(), - case_name=case_name_py.strip(), - skin_name=skin_name.strip(), - abrasion=abrasion.strip(), - abrasion_value=abrasion_value, - price=price, - ) - ) - except Exception as e: - logger.error( - f"获取兼容旧数据错误: {knife}", - "我的金色", - session=user_id, - group_id=group_id, - e=e, - ) - return data_list - - -async def auto_update(): - """自动更新武器箱""" - if case_list := Config.get_config("open_cases", "DAILY_UPDATE"): - logger.debug("尝试自动更新武器箱", "更新武器箱") - if "ALL" in case_list: - case_list = CASE2ID.keys() - logger.debug(f"预计自动更新武器箱 {len(case_list)} 个", "更新武器箱") - for case_name in case_list: - logger.debug(f"开始自动更新武器箱: {case_name}", "更新武器箱") - try: - await update_skin_data(case_name) - rand = random.randint(300, 500) - logger.info( - f"成功自动更新武器箱: {case_name}, 将在 {rand} 秒后再次更新下一武器箱", - "更新武器箱", - ) - await asyncio.sleep(rand) - except Exception as e: - logger.error(f"自动更新武器箱: {case_name}", e=e) diff --git a/zhenxun/plugins/open_cases/utils.py b/zhenxun/plugins/open_cases/utils.py deleted file mode 100644 index 6fda2265..00000000 --- a/zhenxun/plugins/open_cases/utils.py +++ /dev/null @@ -1,657 +0,0 @@ -import asyncio -import os -import random -import re -import time -from datetime import datetime, timedelta - -import nonebot -from tortoise.functions import Count - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType -from zhenxun.utils.utils import cn2py - -from .build_image import generate_skin -from .config import ( - CASE2ID, - CASE_BACKGROUND, - COLOR2NAME, - KNIFE2ID, - NAME2COLOR, - UpdateType, -) -from .models.buff_skin import BuffSkin -from .models.buff_skin_log import BuffSkinLog -from .models.open_cases_user import OpenCasesUser - -# from zhenxun.utils.utils import broadcast_group, cn2py - - -URL = "https://buff.163.com/api/market/goods" - -SELL_URL = "https://buff.163.com/goods" - - -driver = nonebot.get_driver() - -BASE_PATH = IMAGE_PATH / "csgo_cases" - - -class CaseManager: - - CURRENT_CASES = [] - - @classmethod - async def reload(cls): - cls.CURRENT_CASES = [] - case_list = await BuffSkin.filter(color="CASE").values_list( - "case_name", flat=True - ) - for case_name in ( - await BuffSkin.filter(case_name__not="未知武器箱") - .annotate() - .distinct() - .values_list("case_name", flat=True) - ): - for name in case_name.split(","): # type: ignore - if name not in cls.CURRENT_CASES and name in case_list: - cls.CURRENT_CASES.append(name) - - -async def update_skin_data(name: str, is_update_case_name: bool = False) -> str: - """更新箱子内皮肤数据 - - 参数: - name (str): 箱子名称 - is_update_case_name (bool): 是否必定更新所属箱子 - - 返回: - str: 回复内容 - """ - type_ = None - if name in CASE2ID: - type_ = UpdateType.CASE - if name in KNIFE2ID: - type_ = UpdateType.WEAPON_TYPE - if not type_: - return "未在指定武器箱或指定武器类型内" - session = Config.get_config("open_cases", "COOKIE") - if not session: - return "BUFF COOKIE为空捏!" - weapon2case = {} - if type_ == UpdateType.WEAPON_TYPE: - db_data = await BuffSkin.filter(name__contains=name).all() - weapon2case = { - item.name + item.skin_name: item.case_name - for item in db_data - if item.case_name != "未知武器箱" - } - data_list, total = await search_skin_page(name, 1, type_) - if isinstance(data_list, str): - return data_list - for page in range(2, total + 1): - rand_time = random.randint(20, 50) - logger.debug(f"访问随机等待时间: {rand_time}", "开箱更新") - await asyncio.sleep(rand_time) - data_list_, total = await search_skin_page(name, page, type_) - if isinstance(data_list_, list): - data_list += data_list_ - create_list: list[BuffSkin] = [] - update_list: list[BuffSkin] = [] - log_list = [] - now = datetime.now() - exists_id_list = [] - new_weapon2case = {} - for skin in data_list: - if skin.skin_id in exists_id_list: - continue - if skin.case_name: - skin.case_name = ( - skin.case_name.replace("”", "") - .replace("“", "") - .replace("武器箱", "") - .replace(" ", "") - ) - skin.name = skin.name.replace("(★ StatTrak™)", "").replace("(★)", "") - exists_id_list.append(skin.skin_id) - key = skin.name + skin.skin_name - name_ = skin.name + skin.skin_name + skin.abrasion - skin.create_time = now - skin.update_time = now - if UpdateType.WEAPON_TYPE and not skin.case_name: - if is_update_case_name: - case_name = new_weapon2case.get(key) - else: - case_name = weapon2case.get(key) - if not case_name: - if case_list := await get_skin_case(skin.skin_id): - case_name = ",".join(case_list) - rand = random.randint(10, 20) - logger.debug( - f"获取 {skin.name} | {skin.skin_name} 皮肤所属武器箱: {case_name}, 访问随机等待时间: {rand}", - "开箱更新", - ) - await asyncio.sleep(rand) - if not case_name: - case_name = "未知武器箱" - else: - weapon2case[key] = case_name - new_weapon2case[key] = case_name - if skin.case_name == "反恐精英20周年": - skin.case_name = "CS20" - skin.case_name = case_name - if await BuffSkin.exists(skin_id=skin.skin_id): - update_list.append(skin) - else: - create_list.append(skin) - log_list.append( - BuffSkinLog( - name=skin.name, - case_name=skin.case_name, - skin_name=skin.skin_name, - is_stattrak=skin.is_stattrak, - abrasion=skin.abrasion, - color=skin.color, - steam_price=skin.steam_price, - weapon_type=skin.weapon_type, - buy_max_price=skin.buy_max_price, - buy_num=skin.buy_num, - sell_min_price=skin.sell_min_price, - sell_num=skin.sell_num, - sell_reference_price=skin.sell_reference_price, - create_time=now, - ) - ) - name_ = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - for c_name_ in skin.case_name.split(","): - file_path = BASE_PATH / cn2py(c_name_) / f"{cn2py(name_)}.jpg" - if not file_path.exists(): - logger.debug(f"下载皮肤 {name} 图片: {skin.img_url}...", "开箱更新") - await AsyncHttpx.download_file(skin.img_url, file_path) - rand_time = random.randint(1, 10) - await asyncio.sleep(rand_time) - logger.debug(f"图片下载随机等待时间: {rand_time}", "开箱更新") - else: - logger.debug(f"皮肤 {name_} 图片已存在...", "开箱更新") - if create_list: - logger.debug( - f"更新武器箱/皮肤: [{name}], 创建 {len(create_list)} 个皮肤!" - ) - await BuffSkin.bulk_create(set(create_list), 10) - if update_list: - abrasion_list = [] - name_list = [] - skin_name_list = [] - for skin in update_list: - if skin.abrasion not in abrasion_list: - abrasion_list.append(skin.abrasion) - if skin.name not in name_list: - name_list.append(skin.name) - if skin.skin_name not in skin_name_list: - skin_name_list.append(skin.skin_name) - db_data = await BuffSkin.filter( - case_name__contains=name, - skin_name__in=skin_name_list, - name__in=name_list, - abrasion__in=abrasion_list, - ).all() - _update_list = [] - for data in db_data: - for skin in update_list: - if ( - data.name == skin.name - and data.skin_name == skin.skin_name - and data.abrasion == skin.abrasion - ): - data.steam_price = skin.steam_price - data.buy_max_price = skin.buy_max_price - data.buy_num = skin.buy_num - data.sell_min_price = skin.sell_min_price - data.sell_num = skin.sell_num - data.sell_reference_price = skin.sell_reference_price - data.update_time = skin.update_time - _update_list.append(data) - logger.debug( - f"更新武器箱/皮肤: [{name}], 更新 {len(create_list)} 个皮肤!" - ) - await BuffSkin.bulk_update( - _update_list, - [ - "steam_price", - "buy_max_price", - "buy_num", - "sell_min_price", - "sell_num", - "sell_reference_price", - "update_time", - ], - 10, - ) - if log_list: - logger.debug( - f"更新武器箱/皮肤: [{name}], 新增 {len(log_list)} 条皮肤日志!" - ) - await BuffSkinLog.bulk_create(log_list) - if name not in CaseManager.CURRENT_CASES: - CaseManager.CURRENT_CASES.append(name) # type: ignore - return f"更新武器箱/皮肤: [{name}] 成功, 共更新 {len(update_list)} 个皮肤, 新创建 {len(create_list)} 个皮肤!" - - -async def search_skin_page( - s_name: str, page_index: int, type_: UpdateType -) -> tuple[list[BuffSkin] | str, int]: - """查询箱子皮肤 - - 参数: - s_name (str): 箱子/皮肤名称 - page_index (int): 页数 - - 返回: - tuple[list[BuffSkin] | str, int]: BuffSkin - """ - logger.debug( - f"尝试访问武器箱/皮肤: [{s_name}] 页数: [{page_index}]", - "开箱更新", - ) - cookie = {"session": Config.get_config("open_cases", "COOKIE")} - params = { - "game": "csgo", - "page_num": page_index, - "page_size": 80, - "_": time.time(), - "use_suggestio": 0, - } - if type_ == UpdateType.CASE: - params["itemset"] = CASE2ID[s_name] - elif type_ == UpdateType.WEAPON_TYPE: - params["category"] = KNIFE2ID[s_name] - proxy = None - if ip := Config.get_config("open_cases", "BUFF_PROXY"): - proxy = {"http://": ip, "https://": ip} - response = None - error = "" - for i in range(3): - try: - response = await AsyncHttpx.get( - URL, - proxy=proxy, - params=params, - cookies=cookie, # type: ignore - ) - if response.status_code == 200: - break - rand = random.randint(3, 7) - logger.debug( - f"尝试访问武器箱/皮肤第 {i+1} 次访问异常, code: {response.status_code}", - "开箱更新", - ) - await asyncio.sleep(rand) - except Exception as e: - logger.debug( - f"尝试访问武器箱/皮肤第 {i+1} 次访问发生错误 {type(e)}: {e}", "开箱更新" - ) - error = f"{type(e)}: {e}" - if not response: - return f"访问发生异常: {error}", -1 - if response.status_code == 200: - # logger.debug(f"访问BUFF API: {response.text}", "开箱更新") - json_data = response.json() - update_data = [] - if json_data["code"] == "OK": - data_list = json_data["data"]["items"] - for data in data_list: - obj = {} - if type_ == UpdateType.CASE: - obj["case_name"] = s_name - name = data["name"] - try: - logger.debug( - f"武器箱: [{s_name}] 页数: [{page_index}] 正在收录皮肤: [{name}]...", - "开箱更新", - ) - obj["skin_id"] = str(data["id"]) - obj["buy_max_price"] = data["buy_max_price"] # 求购最大金额 - obj["buy_num"] = data["buy_num"] # 当前求购 - goods_info = data["goods_info"] - info = goods_info["info"] - tags = info["tags"] - obj["weapon_type"] = tags["type"]["localized_name"] # 枪械类型 - if obj["weapon_type"] in ["音乐盒", "印花", "探员"]: - continue - elif obj["weapon_type"] in ["匕首", "手套"]: - obj["color"] = "KNIFE" - obj["name"] = data["short_name"].split("(")[0].strip() # 名称 - elif obj["weapon_type"] in ["武器箱"]: - obj["color"] = "CASE" - obj["name"] = data["short_name"] - else: - obj["color"] = NAME2COLOR[tags["rarity"]["localized_name"]] - obj["name"] = tags["weapon"]["localized_name"] # 名称 - if obj["weapon_type"] not in ["武器箱"]: - obj["abrasion"] = tags["exterior"]["localized_name"] # 磨损 - obj["is_stattrak"] = "StatTrak" in tags["quality"]["localized_name"] # type: ignore # 是否暗金 - if not obj["color"]: - obj["color"] = NAME2COLOR[ - tags["rarity"]["localized_name"] - ] # 品质颜色 - else: - obj["abrasion"] = "CASE" - obj["skin_name"] = ( - data["short_name"].split("|")[-1].strip() - ) # 皮肤名称 - obj["img_url"] = goods_info["original_icon_url"] # 图片url - obj["steam_price"] = goods_info["steam_price_cny"] # steam价格 - obj["sell_min_price"] = data["sell_min_price"] # 售卖最低价格 - obj["sell_num"] = data["sell_num"] # 售卖数量 - obj["sell_reference_price"] = data[ - "sell_reference_price" - ] # 参考价格 - update_data.append(BuffSkin(**obj)) - except Exception as e: - logger.error( - f"更新武器箱: [{s_name}] 皮肤: [{s_name}] 错误", - e=e, - ) - logger.debug( - f"访问武器箱: [{s_name}] 页数: [{page_index}] 成功并收录完成", - "开箱更新", - ) - return update_data, json_data["data"]["total_page"] - else: - logger.warning(f'访问BUFF失败: {json_data["error"]}') - return f'访问失败: {json_data["error"]}', -1 - return f"访问失败, 状态码: {response.status_code}", -1 - - -async def build_case_image(case_name: str | None) -> BuildImage | str: - """构造武器箱图片 - - 参数: - case_name (str): 名称 - - 返回: - BuildImage | str: 图片 - """ - background = random.choice(os.listdir(CASE_BACKGROUND)) - background_img = BuildImage(0, 0, background=CASE_BACKGROUND / background) - if case_name: - log_list = ( - await BuffSkinLog.filter(case_name__contains=case_name) - .annotate(count=Count("id")) - .group_by("skin_name") - .values_list("skin_name", "count") - ) - skin_list_ = await BuffSkin.filter(case_name__contains=case_name).all() - skin2count = {item[0]: item[1] for item in log_list} - case = None - skin_list: list[BuffSkin] = [] - exists_name = [] - for skin in skin_list_: - if skin.color == "CASE": - case = skin - else: - name = skin.name + skin.skin_name - if name not in exists_name: - skin_list.append(skin) - exists_name.append(name) - generate_img = {} - for skin in skin_list: - skin_img = await generate_skin(skin, skin2count.get(skin.skin_name, 0)) - if skin_img: - if not generate_img.get(skin.color): - generate_img[skin.color] = [] - generate_img[skin.color].append(skin_img) - skin_image_list = [] - for color in COLOR2NAME: - if generate_img.get(color): - skin_image_list = skin_image_list + generate_img[color] - img = skin_image_list[0] - img_w, img_h = img.size - total_size = (img_w + 25) * (img_h + 10) * len(skin_image_list) # 总面积 - new_size = get_bk_image_size(total_size, background_img.size, img.size, 250) - A = BuildImage( - new_size[0] + 50, new_size[1], background=CASE_BACKGROUND / background - ) - await A.filter("GaussianBlur", 2) - if case: - case_img = await generate_skin( - case, skin2count.get(f"{case_name}武器箱", 0) - ) - if case_img: - await A.paste(case_img, (25, 25)) - w = 25 - h = 230 - skin_image_list.reverse() - for image in skin_image_list: - await A.paste(image, (w, h)) - w += image.width + 20 - if w + image.width - 25 > A.width: - h += image.height + 10 - w = 25 - if h + img_h + 100 < A.height: - await A.crop((0, 0, A.width, h + img_h + 100)) - return A - else: - log_list = ( - await BuffSkinLog.filter(color="CASE") - .annotate(count=Count("id")) - .group_by("case_name") - .values_list("case_name", "count") - ) - name2count = {item[0]: item[1] for item in log_list} - skin_list = await BuffSkin.filter(color="CASE").all() - image_list: list[BuildImage] = [] - for skin in skin_list: - if img := await generate_skin(skin, name2count[skin.case_name]): - image_list.append(img) - if not image_list: - return "未收录武器箱" - w = 25 - h = 150 - img = image_list[0] - img_w, img_h = img.size - total_size = (img_w + 25) * (img_h + 10) * len(image_list) # 总面积 - - new_size = get_bk_image_size(total_size, background_img.size, img.size, 155) - A = BuildImage( - new_size[0] + 50, new_size[1], background=CASE_BACKGROUND / background - ) - await A.filter("GaussianBlur", 2) - bk_img = BuildImage( - img_w, 120, color=(25, 25, 25, 100), font_size=60, font="CJGaoDeGuo.otf" - ) - await bk_img.text( - (0, 0), - f"已收录 {len(image_list)} 个武器箱", - (255, 255, 255), - center_type="center", - ) - await A.paste(bk_img, (10, 10), "width") - for image in image_list: - await A.paste(image, (w, h)) - w += image.width + 20 - if w + image.width - 25 > A.width: - h += image.height + 10 - w = 25 - if h + img_h + 100 < A.height: - await A.crop((0, 0, A.width, h + img_h + 100)) - return A - - -def get_bk_image_size( - total_size: int, - base_size: tuple[int, int], - img_size: tuple[int, int], - extra_height: int = 0, -) -> tuple[int, int]: - """获取所需背景大小且不改变图片长宽比 - - 参数: - total_size (int): 总面积 - base_size (Tuple[int, int]): 初始背景大小 - img_size (Tuple[int, int]): 贴图大小 - - 返回: - tuple[int, int]: 满足所有贴图大小 - """ - bk_w, bk_h = base_size - img_w, img_h = img_size - is_add_title_size = False - left_dis = 0 - right_dis = 0 - old_size = (0, 0) - new_size = (0, 0) - ratio = 1.1 - while 1: - w_ = int(ratio * bk_w) - h_ = int(ratio * bk_h) - size = w_ * h_ - if size < total_size: - left_dis = size - else: - right_dis = size - r = w_ / (img_w + 25) - if right_dis and r - int(r) < 0.1: - if not is_add_title_size and extra_height: - total_size = int(total_size + w_ * extra_height) - is_add_title_size = True - right_dis = 0 - continue - if total_size - left_dis > right_dis - total_size: - new_size = (w_, h_) - else: - new_size = old_size - break - old_size = (w_, h_) - ratio += 0.1 - return new_size - - -async def get_skin_case(id_: str) -> list[str] | None: - """获取皮肤所在箱子 - - 参数: - id_ (str): 皮肤id - - 返回: - list[str] | None: 武器箱名称 - """ - url = f"{SELL_URL}/{id_}" - proxy = None - if ip := Config.get_config("open_cases", "BUFF_PROXY"): - proxy = {"http://": ip, "https://": ip} - response = await AsyncHttpx.get( - url, - proxy=proxy, - ) - if response.status_code == 200: - text = response.text - if r := re.search('', text): - case_list = [] - for s in r.group(1).split(","): - if "武器箱" in s: - case_list.append( - s.replace("”", "") - .replace("“", "") - .replace('"', "") - .replace("'", "") - .replace("武器箱", "") - .replace(" ", "") - ) - return case_list - else: - logger.debug(f"访问皮肤所属武器箱异常 url: {url} code: {response.status_code}") - return None - - -async def init_skin_trends( - name: str, skin: str, abrasion: str, day: int = 7 -) -> BuildImage | None: - date = datetime.now() - timedelta(days=day) - log_list = ( - await BuffSkinLog.filter( - name__contains=name.upper(), - skin_name=skin, - abrasion__contains=abrasion, - create_time__gt=date, - is_stattrak=False, - ) - .order_by("create_time") - .limit(day * 5) - .all() - ) - if not log_list: - return None - date_list = [] - price_list = [] - for log in log_list: - date = str(log.create_time.date()) - if date not in date_list: - date_list.append(date) - price_list.append(log.sell_min_price) - graph = BuildMat(MatType.LINE) - graph.data = price_list - graph.title = f"{name}({skin})价格趋势({day})" - graph.x_index = date_list - return await graph.build() - - -async def reset_count_daily(): - """ - 重置每日开箱 - """ - try: - await OpenCasesUser.all().update(today_open_total=0) - # await broadcast_group( - # "[[_task|open_case_reset_remind]]今日开箱次数重置成功", - # log_cmd="开箱重置提醒", - # ) - except Exception as e: - logger.error(f"开箱重置错误", e=e) - - -async def download_image(case_name: str | None = None): - """下载皮肤图片 - - 参数: - case_name: 箱子名称. - """ - skin_list = ( - await BuffSkin.filter(case_name=case_name).all() - if case_name - else await BuffSkin.all() - ) - for skin in skin_list: - name_ = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - for c_name_ in skin.case_name.split(","): - try: - pass - # file_path = BASE_PATH / cn2py(c_name_) / f"{cn2py(name_)}.jpg" - # if not file_path.exists(): - # logger.debug( - # f"下载皮肤 {c_name_}/{skin.name} 图片: {skin.img_url}...", - # "开箱图片更新", - # ) - # await AsyncHttpx.download_file(skin.img_url, file_path) - # rand_time = random.randint(1, 5) - # await asyncio.sleep(rand_time) - # logger.debug(f"图片下载随机等待时间: {rand_time}", "开箱图片更新") - # else: - # logger.debug( - # f"皮肤 {c_name_}/{skin.name} 图片已存在...", "开箱图片更新" - # ) - except Exception as e: - logger.error( - f"下载皮肤 {c_name_}/{skin.name} 图片: {skin.img_url}", - "开箱图片更新", - e=e, - ) - - -@driver.on_startup -async def _(): - await CaseManager.reload() diff --git a/zhenxun/plugins/parse_bilibili/__init__.py b/zhenxun/plugins/parse_bilibili/__init__.py deleted file mode 100644 index 791c56f0..00000000 --- a/zhenxun/plugins/parse_bilibili/__init__.py +++ /dev/null @@ -1,186 +0,0 @@ -import re -import time - -import ujson as json -from nonebot import on_message -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Hyper, Image, UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -from .information_container import InformationContainer -from .parse_url import parse_bili_url - -__plugin_meta__ = PluginMetadata( - name="B站内容解析", - description="B站内容解析", - usage=""" - usage: - 被动监听插件,解析B站视频、直播、专栏,支持小程序卡片及文本链接,5分钟内不解析相同内容 - """.strip(), - extra=PluginExtraData( - author="leekooyo", - version="0.1", - plugin_type=PluginType.DEPENDANT, - menu_type="其他", - configs=[ - RegisterConfig( - module="_task", - key="DEFAULT_BILIBILI_PARSE", - value=True, - default_value=True, - help="被动 B站转发解析 进群默认开关状态", - type=bool, - ) - ], - tasks=[Task(module="bilibili_parse", name="b站转发解析")], - ).dict(), -) - - -async def _rule(session: EventSession) -> bool: - return not await TaskInfo.is_block("bilibili_parse", session.id3 or session.id2) - - -_matcher = on_message(priority=1, block=False, rule=_rule) - -_tmp = {} - - -@_matcher.handle() -async def _(session: EventSession, message: UniMsg): - information_container = InformationContainer() - # 判断文本消息内容是否相关 - match = None - # 判断文本消息和小程序的内容是否指向一个b站链接 - get_url = None - # 判断文本消息是否包含视频相关内容 - vd_flag = False - # 设定时间阈值,阈值之下不会解析重复内容 - repet_second = 300 - # 尝试解析小程序消息 - data = message[0] - if isinstance(data, Hyper) and data.raw: - try: - data = json.loads(data.raw) - except (IndexError, KeyError): - data = None - if data: - # 获取相关数据 - meta_data = data.get("meta", {}) - news_value = meta_data.get("news", {}) - detail_1_value = meta_data.get("detail_1", {}) - qqdocurl_value = detail_1_value.get("qqdocurl", {}) - jumpUrl_value = news_value.get("jumpUrl", {}) - get_url = (qqdocurl_value if qqdocurl_value else jumpUrl_value).split("?")[ - 0 - ] - # 解析文本消息 - elif msg := message.extract_plain_text(): - # 消息中含有视频号 - if "bv" in msg.lower() or "av" in msg.lower(): - match = re.search(r"((?=(?:bv|av))([A-Za-z0-9]+))", msg, re.IGNORECASE) - vd_flag = True - - # 消息中含有b23的链接,包括视频、专栏、动态、直播 - elif "https://b23.tv" in msg: - match = re.search(r"https://b23\.tv/[^?\s]+", msg, re.IGNORECASE) - - # 检查消息中是否含有直播、专栏、动态链接 - elif any( - keyword in msg - for keyword in [ - "https://live.bilibili.com/", - "https://www.bilibili.com/read/", - "https://www.bilibili.com/opus/", - "https://t.bilibili.com/", - ] - ): - pattern = r"https://(live|www\.bilibili\.com/read|www\.bilibili\.com/opus|t\.bilibili\.com)/[^?\s]+" - match = re.search(pattern, msg) - - # 匹配成功,则获取链接 - if match: - if vd_flag: - number = match.group(1) - get_url = f"https://www.bilibili.com/video/{number}" - else: - get_url = match.group() - - if get_url: - # 将链接统一发送给处理函数 - data = await parse_bili_url(get_url, information_container) - if data.vd_info: - # 判断一定时间内是否解析重复内容,或者是第一次解析 - if ( - data.vd_url in _tmp.keys() - and time.time() - _tmp[data.vd_url] > repet_second - ) or data.vd_url not in _tmp.keys(): - pic = data.vd_info.get("pic", "") # 封面 - aid = data.vd_info.get("aid", "") # av号 - title = data.vd_info.get("title", "") # 标题 - author = data.vd_info.get("owner", {}).get("name", "") # UP主 - reply = data.vd_info.get("stat", {}).get("reply", "") # 回复 - favorite = data.vd_info.get("stat", {}).get("favorite", "") # 收藏 - coin = data.vd_info.get("stat", {}).get("coin", "") # 投币 - like = data.vd_info.get("stat", {}).get("like", "") # 点赞 - danmuku = data.vd_info.get("stat", {}).get("danmaku", "") # 弹幕 - ctime = data.vd_info["ctime"] - date = time.strftime("%Y-%m-%d", time.localtime(ctime)) - logger.info( - f"解析bilibili转发 {data.vd_url}", "b站解析", session=session - ) - _tmp[data.vd_url] = time.time() - _path = TEMP_PATH / f"{aid}.jpg" - await AsyncHttpx.download_file(pic, _path) - await MessageUtils.build_message( - [ - _path, - f"av{aid}\n标题:{title}\nUP:{author}\n上传日期:{date}\n回复:{reply},收藏:{favorite},投币:{coin}\n点赞:{like},弹幕:{danmuku}\n{data.vd_url}", - ] - ).send() - - elif data.live_info: - if ( - data.live_url in _tmp.keys() - and time.time() - _tmp[data.live_url] > repet_second - ) or data.live_url not in _tmp.keys(): - uid = data.live_info.get("uid", "") # 主播uid - title = data.live_info.get("title", "") # 直播间标题 - description = data.live_info.get( - "description", "" - ) # 简介,可能会出现标签 - user_cover = data.live_info.get("user_cover", "") # 封面 - keyframe = data.live_info.get("keyframe", "") # 关键帧画面 - live_time = data.live_info.get("live_time", "") # 开播时间 - area_name = data.live_info.get("area_name", "") # 分区 - parent_area_name = data.live_info.get("parent_area_name", "") # 父分区 - logger.info( - f"解析bilibili转发 {data.live_url}", "b站解析", session=session - ) - _tmp[data.live_url] = time.time() - await MessageUtils.build_message( - [ - Image(url=user_cover), - f"开播用户:https://space.bilibili.com/{uid}\n开播时间:{live_time}\n直播分区:{parent_area_name}——>{area_name}\n标题:{title}\n简介:{description}\n直播截图:\n", - Image(url=keyframe), - f"{data.live_url}", - ] - ).send() - elif data.image_info: - if ( - data.image_url in _tmp.keys() - and time.time() - _tmp[data.image_url] > repet_second - ) or data.image_url not in _tmp.keys(): - logger.info( - f"解析bilibili转发 {data.image_url}", "b站解析", session=session - ) - _tmp[data.image_url] = time.time() - await data.image_info.send() diff --git a/zhenxun/plugins/parse_bilibili/get_image.py b/zhenxun/plugins/parse_bilibili/get_image.py deleted file mode 100644 index 3b8c70c8..00000000 --- a/zhenxun/plugins/parse_bilibili/get_image.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import re -from pathlib import Path - -from nonebot_plugin_alconna import UniMessage - -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncPlaywright -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.user_agent import get_user_agent_str - - -async def resize(path: Path): - """调整图像大小的异步函数 - - 参数: - path (str): 图像文件路径 - """ - A = BuildImage.open(path) - await A.resize(0.8) - await A.save(path) - - -async def get_image(url) -> UniMessage | None: - """获取Bilibili链接的截图,并返回base64格式的图片 - - 参数: - url (str): Bilibili链接 - - 返回: - Image: Image - """ - cv_match = None - opus_match = None - t_opus_match = None - - cv_number = None - opus_number = None - t_opus_number = None - - # 提取cv、opus、t_opus的编号 - url = url.split("?")[0] - cv_match = re.search(r"read/cv([A-Za-z0-9]+)", url, re.IGNORECASE) - opus_match = re.search(r"opus/([A-Za-z0-9]+)", url, re.IGNORECASE) - t_opus_match = re.search(r"https://t\.bilibili\.com/(\d+)", url, re.IGNORECASE) - - if cv_match: - cv_number = cv_match.group(1) - elif opus_match: - opus_number = opus_match.group(1) - elif t_opus_match: - t_opus_number = t_opus_match.group(1) - - screenshot_path = None - - # 根据编号构建保存路径 - if cv_number: - screenshot_path = TEMP_PATH / "bilibili_cv_{cv_number}.png" - elif opus_number: - screenshot_path = TEMP_PATH / "bilibili_opus_{opus_number}.png" - elif t_opus_number: - screenshot_path = TEMP_PATH / "bilibili_opus_{t_opus_number}.png" - # t.bilibili.com和https://www.bilibili.com/opus在内容上是一样的,为便于维护,调整url至https://www.bilibili.com/opus/ - url = f"https://www.bilibili.com/opus/{t_opus_number}" - if screenshot_path: - try: - # 如果文件不存在,进行截图 - if not screenshot_path.exists(): - # 创建页面 - try: - async with AsyncPlaywright.new_page() as page: - await page.set_viewport_size({"width": 5120, "height": 2560}) - # 设置请求拦截器 - await page.route( - re.compile(r"(\.png$)|(\.jpg$)"), - lambda route: route.abort(), - ) - # 访问链接 - await page.goto(url, wait_until="networkidle", timeout=10000) - # 根据不同的链接结构,设置对应的CSS选择器 - if cv_number: - css = "#app > div" - elif opus_number or t_opus_number: - css = "#app > div.opus-detail > div.bili-opus-view" - # 点击对应的元素 - await page.click(css) - # 查询目标元素 - div = await page.query_selector(css) - # 对目标元素进行截图 - await div.screenshot( # type: ignore - path=screenshot_path, - timeout=100000, - animations="disabled", - type="png", - ) - # 异步执行调整截图大小的操作 - await resize(screenshot_path) - except Exception as e: - logger.warning(f"尝试解析bilibili转发失败", e=e) - return None - return MessageUtils.build_message(screenshot_path) - except Exception as e: - logger.error(f"尝试解析bilibili转发失败", e=e) - return None diff --git a/zhenxun/plugins/parse_bilibili/information_container.py b/zhenxun/plugins/parse_bilibili/information_container.py deleted file mode 100644 index ddb685f8..00000000 --- a/zhenxun/plugins/parse_bilibili/information_container.py +++ /dev/null @@ -1,53 +0,0 @@ -class InformationContainer: - def __init__( - self, - vd_info=None, - live_info=None, - vd_url=None, - live_url=None, - image_info=None, - image_url=None, - ): - self._vd_info = vd_info - self._live_info = live_info - self._vd_url = vd_url - self._live_url = live_url - self._image_info = image_info - self._image_url = image_url - - @property - def vd_info(self): - return self._vd_info - - @property - def live_info(self): - return self._live_info - - @property - def vd_url(self): - return self._vd_url - - @property - def live_url(self): - return self._live_url - - @property - def image_info(self): - return self._image_info - - @property - def image_url(self): - return self._image_url - - def update(self, updates): - """ - 更新多个信息的通用方法 - Args: - updates (dict): 包含信息类型和对应新值的字典 - """ - for info_type, new_value in updates.items(): - if hasattr(self, f"_{info_type}"): - setattr(self, f"_{info_type}", new_value) - - def get_information(self): - return self diff --git a/zhenxun/plugins/parse_bilibili/parse_url.py b/zhenxun/plugins/parse_bilibili/parse_url.py deleted file mode 100644 index b4e2a1fe..00000000 --- a/zhenxun/plugins/parse_bilibili/parse_url.py +++ /dev/null @@ -1,65 +0,0 @@ -import aiohttp -from bilireq import live, video - -from zhenxun.utils.user_agent import get_user_agent - -from .get_image import get_image -from .information_container import InformationContainer - - -async def parse_bili_url(get_url: str, information_container: InformationContainer): - """解析Bilibili链接,获取相关信息 - - 参数: - get_url (str): 待解析的Bilibili链接 - information_container (InformationContainer): 信息容器 - - 返回: - dict: 包含解析得到的信息的字典 - """ - response_url = "" - - # 去除链接末尾的斜杠 - if get_url[-1] == "/": - get_url = get_url[:-1] - - # 发起HTTP请求,获取重定向后的链接 - async with aiohttp.ClientSession(headers=get_user_agent()) as session: - async with session.get( - get_url, - timeout=7, - ) as response: - response_url = str(response.url).split("?")[0] - - # 去除重定向后链接末尾的斜杠 - if response_url[-1] == "/": - response_url = response_url[:-1] - - # 根据不同类型的链接进行处理 - if response_url.startswith( - ("https://www.bilibili.com/video", "https://m.bilibili.com/video/") - ): - vd_url = response_url - vid = vd_url.split("/")[-1] - vd_info = await video.get_video_base_info(vid) - information_container.update({"vd_info": vd_info, "vd_url": vd_url}) - - elif response_url.startswith("https://live.bilibili.com"): - live_url = response_url - liveid = live_url.split("/")[-1] - live_info = await live.get_room_info_by_id(liveid) - information_container.update({"live_info": live_info, "live_url": live_url}) - - elif response_url.startswith("https://www.bilibili.com/read"): - cv_url = response_url - image_info = await get_image(cv_url) - information_container.update({"image_info": image_info, "image_url": cv_url}) - - elif response_url.startswith( - ("https://www.bilibili.com/opus", "https://t.bilibili.com") - ): - opus_url = response_url - image_info = await get_image(opus_url) - information_container.update({"image_info": image_info, "image_url": opus_url}) - - return information_container.get_information() diff --git a/zhenxun/plugins/pid_search.py b/zhenxun/plugins/pid_search.py deleted file mode 100644 index 97fc4d40..00000000 --- a/zhenxun/plugins/pid_search.py +++ /dev/null @@ -1,125 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import change_pixiv_image_links -from zhenxun.utils.withdraw_manage import WithdrawManager - -__plugin_meta__ = PluginMetadata( - name="pid搜索", - description="通过 pid 搜索图片", - usage=""" - usage: - 通过 pid 搜索图片 - 指令: - p搜 [pid] - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1").dict(), -) - - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net", -} - -_matcher = on_alconna( - Alconna("p搜", Args["pid", str]), aliases={"P搜"}, priority=5, block=True -) - - -@_matcher.handle() -async def _(pid: Match[int]): - if pid.available: - _matcher.set_path_arg("pid", pid.result) - - -@_matcher.got_path("pid", prompt="需要查询的图片PID是?或发送'取消'结束搜索") -async def _(bot: Bot, session: EventSession, arparma: Arparma, pid: str): - url = Config.get_config("hibiapi", "HIBIAPI") + "/api/pixiv/illust" - if pid in ["取消", "算了"]: - await Text("已取消操作...").finish() - if not pid.isdigit(): - await Text("pid必须为数字...").finish() - for _ in range(3): - try: - data = ( - await AsyncHttpx.get( - url, - params={"id": pid}, - timeout=5, - ) - ).json() - except TimeoutError: - pass - except Exception as e: - logger.error( - f"pixiv pid 搜索发生了一些错误...", - arparma.header_result, - session=session, - e=e, - ) - await MessageUtils.build_message(f"发生了一些错误..{type(e)}:{e}").finish() - else: - if data.get("error"): - await MessageUtils.build_message(data["error"]["user_message"]).finish( - reply_to=True - ) - data = data["illust"] - if not data["width"] and not data["height"]: - await MessageUtils.build_message( - f"没有搜索到 PID:{pid} 的图片" - ).finish(reply_to=True) - pid = data["id"] - title = data["title"] - author = data["user"]["name"] - author_id = data["user"]["id"] - image_list = [] - try: - image_list.append(data["meta_single_page"]["original_image_url"]) - except KeyError: - for image_url in data["meta_pages"]: - image_list.append(image_url["image_urls"]["original"]) - for i, img_url in enumerate(image_list): - img_url = change_pixiv_image_links(img_url) - if not await AsyncHttpx.download_file( - img_url, - TEMP_PATH / f"pid_search_{session.id1}_{i}.png", - headers=headers, - ): - await MessageUtils.build_message("图片下载失败了...").finish( - reply_to=True - ) - tmp = "" - if session.id3 or session.id2: - tmp = "\n【注】将在30后撤回......" - receipt = await MessageUtils.build_message( - [ - f"title:{title}\n" - f"pid:{pid}\n" - f"author:{author}\n" - f"author_id:{author_id}\n", - TEMP_PATH / f"pid_search_{session.id1}_{i}.png", - f"{tmp}", - ] - ).send() - logger.info( - f" 查询图片 PID:{pid}", arparma.header_result, session=session - ) - if session.id3 or session.id2: - await WithdrawManager.withdraw_message( - bot, receipt.msg_ids[0]["message_id"], 30 # type: ignore - ) - break - else: - await Text("图片下载失败了...").send(reply_to=True) diff --git a/zhenxun/plugins/pix_gallery/__init__.py b/zhenxun/plugins/pix_gallery/__init__.py deleted file mode 100644 index d549a249..00000000 --- a/zhenxun/plugins/pix_gallery/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -from pathlib import Path -from typing import Tuple - -import nonebot - -from zhenxun.configs.config import Config - -Config.add_plugin_config( - "hibiapi", - "HIBIAPI", - "https://api.obfs.dev", - help="如果没有自建或其他hibiapi请不要修改", - default_value="https://api.obfs.dev", -) -Config.add_plugin_config("pixiv", "PIXIV_NGINX_URL", "i.pximg.cf", help="Pixiv反向代理") -Config.add_plugin_config( - "pix", - "PIX_IMAGE_SIZE", - "master", - help="PIX图库下载的画质 可能的值:original:原图,master:缩略图(加快发送速度)", - default_value="master", -) -Config.add_plugin_config( - "pix", - "SEARCH_HIBIAPI_BOOKMARKS", - 5000, - help="最低收藏,PIX使用HIBIAPI搜索图片时达到最低收藏才会添加至图库", - default_value=5000, - type=int, -) -Config.add_plugin_config( - "pix", - "WITHDRAW_PIX_MESSAGE", - (0, 1), - help="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], -) -Config.add_plugin_config( - "pix", - "PIX_OMEGA_PIXIV_RATIO", - (10, 0), - help="PIX图库 与 额外图库OmegaPixivIllusts 混合搜索的比例 参1:PIX图库 参2:OmegaPixivIllusts扩展图库(没有此图库请设置为0)", - default_value=(10, 0), - type=Tuple[int, int], -) -Config.add_plugin_config( - "pix", "TIMEOUT", 10, help="下载图片超时限制(秒)", default_value=10, type=int -) - -Config.add_plugin_config( - "pix", - "SHOW_INFO", - True, - help="是否显示图片的基本信息,如PID等", - default_value=True, - type=bool, -) - -Config.set_name("pix", "PIX图库") - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/pix_gallery/_data_source.py b/zhenxun/plugins/pix_gallery/_data_source.py deleted file mode 100644 index 7e9db221..00000000 --- a/zhenxun/plugins/pix_gallery/_data_source.py +++ /dev/null @@ -1,426 +0,0 @@ -import asyncio -import math -from asyncio.exceptions import TimeoutError -from asyncio.locks import Semaphore -from copy import deepcopy -from pathlib import Path - -import aiofiles -from httpx import ConnectError - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.utils import change_img_md5, change_pixiv_image_links - -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net/", -} - -HIBIAPI = Config.get_config("hibiapi", "HIBIAPI") -if not HIBIAPI: - HIBIAPI = "https://api.obfs.dev" -HIBIAPI = HIBIAPI[:-1] if HIBIAPI[-1] == "/" else HIBIAPI - - -async def start_update_image_url( - current_keyword: list[str], black_pid: list[str], is_pid: bool -) -> tuple[int, int]: - """开始更新图片url - - 参数: - current_keyword: 关键词 - black_pid: 黑名单pid - is_pid: pid强制更新不受限制 - - 返回: - tuple[int, int]: pid数量和图片数量 - """ - global HIBIAPI - pid_count = 0 - pic_count = 0 - tasks = [] - semaphore = asyncio.Semaphore(10) - for keyword in current_keyword: - for page in range(1, 110): - if keyword.startswith("uid:"): - url = f"{HIBIAPI}/api/pixiv/member_illust" - params = {"id": keyword[4:], "page": page} - if page == 30: - break - elif keyword.startswith("pid:"): - url = f"{HIBIAPI}/api/pixiv/illust" - params = {"id": keyword[4:]} - else: - url = f"{HIBIAPI}/api/pixiv/search" - params = {"word": keyword, "page": page} - tasks.append( - asyncio.ensure_future( - search_image( - url, keyword, params, semaphore, page, black_pid, is_pid - ) - ) - ) - if keyword.startswith("pid:"): - break - result = await asyncio.gather(*tasks) - for x in result: - pid_count += x[0] - pic_count += x[1] - return pid_count, pic_count - - -async def search_image( - url: str, - keyword: str, - params: dict, - semaphore: Semaphore, - page: int = 1, - black: list[str] = [], - is_pid: bool = False, -) -> tuple[int, int]: - """搜索图片 - - 参数: - url: 搜索url - keyword: 关键词 - params: params参数 - semaphore: semaphore - page: 页面 - black: pid黑名单 - is_pid: pid强制更新不受限制 - - 返回: - tuple[int, int]: pid数量和图片数量 - """ - tmp_pid = [] - pic_count = 0 - pid_count = 0 - async with semaphore: - # try: - data = (await AsyncHttpx.get(url, params=params)).json() - if ( - not data - or data.get("error") - or (not data.get("illusts") and not data.get("illust")) - ): - return 0, 0 - if url != f"{HIBIAPI}/api/pixiv/illust": - logger.info(f'{keyword}: 获取数据成功...数据总量:{len(data["illusts"])}') - data = data["illusts"] - else: - logger.info(f'获取数据成功...PID:{params.get("id")}') - data = [data["illust"]] - img_data = {} - for x in data: - pid = x["id"] - title = x["title"] - width = x["width"] - height = x["height"] - view = x["total_view"] - bookmarks = x["total_bookmarks"] - uid = x["user"]["id"] - author = x["user"]["name"] - tags = [] - for tag in x["tags"]: - for i in tag: - if tag[i]: - tags.append(tag[i]) - img_urls = [] - if x["page_count"] == 1: - img_urls.append(x["meta_single_page"]["original_image_url"]) - else: - for urls in x["meta_pages"]: - img_urls.append(urls["image_urls"]["original"]) - if ( - ( - bookmarks >= Config.get_config("pix", "SEARCH_HIBIAPI_BOOKMARKS") - or ( - url == f"{HIBIAPI}/api/pixiv/member_illust" - and bookmarks >= 1500 - ) - or (url == f"{HIBIAPI}/api/pixiv/illust") - ) - and len(img_urls) < 10 - and _check_black(img_urls, black) - ) or is_pid: - img_data[pid] = { - "pid": pid, - "title": title, - "width": width, - "height": height, - "view": view, - "bookmarks": bookmarks, - "img_urls": img_urls, - "uid": uid, - "author": author, - "tags": tags, - } - else: - continue - for x in img_data.keys(): - data = img_data[x] - data_copy = deepcopy(data) - del data_copy["img_urls"] - for img_url in data["img_urls"]: - img_p = img_url[img_url.rfind("_") + 1 : img_url.rfind(".")] - data_copy["img_url"] = img_url - data_copy["img_p"] = img_p - data_copy["is_r18"] = "R-18" in data["tags"] - if not await Pixiv.exists( - pid=data["pid"], img_url=img_url, img_p=img_p - ): - data_copy["img_url"] = img_url - await Pixiv.create(**data_copy) - if data["pid"] not in tmp_pid: - pid_count += 1 - tmp_pid.append(data["pid"]) - pic_count += 1 - logger.info(f'存储图片PID:{data["pid"]} IMG_P:{img_p}') - else: - logger.warning(f'{data["pid"]} | {img_url} 已存在...') - # except Exception as e: - # logger.warning(f"PIX在线搜索图片错误,已再次调用 {type(e)}:{e}") - # await search_image(url, keyword, params, semaphore, page, black) - return pid_count, pic_count - - -async def get_image(img_url: str, user_id: str) -> Path | None: - """下载图片 - - 参数: - img_url: 图片url - user_id: 用户id - - 返回: - Path | None: 图片名称 - """ - if "https://www.pixiv.net/artworks" in img_url: - pid = img_url.rsplit("/", maxsplit=1)[-1] - params = {"id": pid} - for _ in range(3): - try: - response = await AsyncHttpx.get( - f"{HIBIAPI}/api/pixiv/illust", params=params - ) - if response.status_code == 200: - data = response.json() - if data.get("illust"): - if data["illust"]["page_count"] == 1: - img_url = data["illust"]["meta_single_page"][ - "original_image_url" - ] - else: - img_url = data["illust"]["meta_pages"][0]["image_urls"][ - "original" - ] - break - except TimeoutError: - pass - old_img_url = img_url - img_url = change_pixiv_image_links( - img_url, - Config.get_config("pix", "PIX_IMAGE_SIZE"), - Config.get_config("pixiv", "PIXIV_NGINX_URL"), - ) - old_img_url = change_pixiv_image_links( - old_img_url, None, Config.get_config("pixiv", "PIXIV_NGINX_URL") - ) - for _ in range(3): - try: - response = await AsyncHttpx.get( - img_url, - headers=headers, - timeout=Config.get_config("pix", "TIMEOUT"), - ) - if response.status_code == 404: - img_url = old_img_url - continue - async with aiofiles.open( - TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg", "wb" - ) as f: - await f.write(response.content) - change_img_md5( - TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg" - ) - return TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg" - except TimeoutError: - logger.warning(f"PIX:{img_url} 图片下载超时...") - except ConnectError: - logger.warning(f"PIX:{img_url} 图片下载连接失败...") - return None - - -async def uid_pid_exists(id_: str) -> bool: - """检测 pid/uid 是否有效 - - 参数: - id_: pid/uid - - 返回: - bool: 是否有效 - """ - if id_.startswith("uid:"): - url = f"{HIBIAPI}/api/pixiv/member" - elif id_.startswith("pid:"): - url = f"{HIBIAPI}/api/pixiv/illust" - else: - return False - params = {"id": int(id_[4:])} - data = (await AsyncHttpx.get(url, params=params)).json() - if data.get("error"): - return False - return True - - -async def get_keyword_num(keyword: str) -> tuple[int, int, int, int, int]: - """查看图片相关 tag 数量 - - 参数: - keyword: 关键词tag - - 返回: - tuple[int, int, int, int, int]: 总数, r18数, Omg图库总数, Omg图库色图数, Omg图库r18数 - """ - count, r18_count = await Pixiv.get_keyword_num(keyword.split()) - count_, setu_count, r18_count_ = await OmegaPixivIllusts.get_keyword_num( - keyword.split() - ) - return count, r18_count, count_, setu_count, r18_count_ - - -async def remove_image(pid: int, img_p: str | None): - """删除置顶图片 - - 参数: - pid: pid - img_p: 图片 p 如 p0,p1 等 - """ - if img_p: - if "p" not in img_p: - img_p = f"p{img_p}" - if img_p: - await Pixiv.filter(pid=pid, img_p=img_p).delete() - else: - await Pixiv.filter(pid=pid).delete() - - -async def gen_keyword_pic( - _pass_keyword: list[str], not_pass_keyword: list[str], is_superuser: bool -) -> BuildImage: - """已通过或未通过的所有关键词/uid/pid - - 参数: - _pass_keyword: 通过列表 - not_pass_keyword: 未通过列表 - is_superuser: 是否超级用户 - - 返回: - BuildImage: 数据图片 - """ - _keyword = [ - x - for x in _pass_keyword - if not x.startswith("uid:") - and not x.startswith("pid:") - and not x.startswith("black:") - ] - _uid = [x for x in _pass_keyword if x.startswith("uid:")] - _pid = [x for x in _pass_keyword if x.startswith("pid:")] - _n_keyword = [ - x - for x in not_pass_keyword - if not x.startswith("uid:") - and not x.startswith("pid:") - and not x.startswith("black:") - ] - _n_uid = [ - x - for x in not_pass_keyword - if x.startswith("uid:") and not x.startswith("black:") - ] - _n_pid = [ - x - for x in not_pass_keyword - if x.startswith("pid:") and not x.startswith("black:") - ] - img_width = 0 - img_data = { - "_keyword": {"width": 0, "data": _keyword}, - "_uid": {"width": 0, "data": _uid}, - "_pid": {"width": 0, "data": _pid}, - "_n_keyword": {"width": 0, "data": _n_keyword}, - "_n_uid": {"width": 0, "data": _n_uid}, - "_n_pid": {"width": 0, "data": _n_pid}, - } - for x in list(img_data.keys()): - img_data[x]["width"] = math.ceil(len(img_data[x]["data"]) / 40) - img_width += img_data[x]["width"] * 200 - if not is_superuser: - img_width = ( - img_width - - ( - img_data["_n_keyword"]["width"] - + img_data["_n_uid"]["width"] - + img_data["_n_pid"]["width"] - ) - * 200 - ) - del img_data["_n_keyword"] - del img_data["_n_pid"] - del img_data["_n_uid"] - current_width = 0 - A = BuildImage(img_width, 1100) - for x in list(img_data.keys()): - if img_data[x]["data"]: - # img = BuildImage(img_data[x]["width"] * 200, 1100, 200, 1100, font_size=40) - img = BuildImage(img_data[x]["width"] * 200, 1100, font_size=40) - start_index = 0 - end_index = 40 - total_index = img_data[x]["width"] * 40 - for _ in range(img_data[x]["width"]): - tmp = BuildImage(198, 1100, font_size=20) - text_img = BuildImage(198, 100, font_size=50) - key_str = "\n".join( - [key for key in img_data[x]["data"][start_index:end_index]] - ) - await tmp.text((10, 100), key_str) - if x.find("_n") == -1: - await text_img.text((24, 24), "已收录") - else: - await text_img.text((24, 24), "待收录") - await tmp.paste(text_img, (0, 0)) - start_index += 40 - end_index = ( - end_index + 40 if end_index + 40 <= total_index else total_index - ) - background_img = BuildImage(200, 1100, color="#FFE4C4") - await background_img.paste(tmp, (1, 1)) - await img.paste(background_img) - await A.paste(img, (current_width, 0)) - current_width += img_data[x]["width"] * 200 - return A - - -def _check_black(img_urls: list[str], black: list[str]) -> bool: - """检测pid是否在黑名单中 - - 参数: - img_urls: 图片img列表 - black: 黑名单 - - 返回: - bool: 是否在黑名单中 - """ - for b in black: - for img_url in img_urls: - if b in img_url: - return False - return True diff --git a/zhenxun/plugins/pix_gallery/_model/__init__.py b/zhenxun/plugins/pix_gallery/_model/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/zhenxun/plugins/pix_gallery/_model/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py b/zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py deleted file mode 100644 index 17e2156c..00000000 --- a/zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py +++ /dev/null @@ -1,89 +0,0 @@ - -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class OmegaPixivIllusts(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - pid = fields.BigIntField() - """pid""" - uid = fields.BigIntField() - """uid""" - title = fields.CharField(255) - """标题""" - uname = fields.CharField(255) - """画师名称""" - classified = fields.IntField() - """标记标签, 0=未标记, 1=已人工标记或从可信已标记来源获取""" - nsfw_tag = fields.IntField() - """nsfw标签,-1=未标记, 0=safe, 1=setu. 2=r18""" - width = fields.IntField() - """宽度""" - height = fields.IntField() - """高度""" - tags = fields.TextField() - """tags""" - url = fields.CharField(255) - """pixiv url链接""" - - class Meta: - table = "omega_pixiv_illusts" - table_description = "omega图库数据表" - unique_together = ("pid", "url") - - @classmethod - async def query_images( - cls, - keywords: list[str] | None = None, - uid: int | None = None, - pid: int | None = None, - nsfw_tag: int | None = 0, - num: int = 100, - ) -> list["OmegaPixivIllusts"]: - """查找符合条件的图片 - - 参数: - keywords: 关键词 - uid: 画师uid - pid: 图片pid - nsfw_tag: nsfw标签, 0=safe, 1=setu. 2=r18 - num: 获取图片数量 - """ - if not num: - return [] - query = cls - if nsfw_tag is not None: - query = cls.filter(nsfw_tag=nsfw_tag) - if keywords: - for keyword in keywords: - query = query.filter(tags__contains=keyword) - elif uid: - query = query.filter(uid=uid) - elif pid: - query = query.filter(pid=pid) - query = query.annotate(rand=Random()).limit(num) - return await query.all() # type: ignore - - @classmethod - async def get_keyword_num( - cls, tags: list[str] | None = None - ) -> tuple[int, int, int]: - """获取相关关键词(keyword, tag)在图库中的数量 - - 参数: - tags: 关键词/Tag - """ - query = cls - if tags: - for tag in tags: - query = query.filter(tags__contains=tag) - else: - query = query.all() - count = await query.filter(nsfw_tag=0).count() - setu_count = await query.filter(nsfw_tag=1).count() - r18_count = await query.filter(nsfw_tag=2).count() - return count, setu_count, r18_count diff --git a/zhenxun/plugins/pix_gallery/_model/pixiv.py b/zhenxun/plugins/pix_gallery/_model/pixiv.py deleted file mode 100644 index 3451781d..00000000 --- a/zhenxun/plugins/pix_gallery/_model/pixiv.py +++ /dev/null @@ -1,91 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class Pixiv(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - pid = fields.BigIntField() - """pid""" - uid = fields.BigIntField() - """uid""" - author = fields.CharField(255) - """作者""" - title = fields.CharField(255) - """标题""" - width = fields.IntField() - """宽度""" - height = fields.IntField() - """高度""" - view = fields.IntField() - """pixiv查看数""" - bookmarks = fields.IntField() - """收藏数""" - tags = fields.TextField() - """tags""" - img_url = fields.CharField(255) - """pixiv url链接""" - img_p = fields.CharField(255) - """图片pN""" - is_r18 = fields.BooleanField() - - class Meta: - table = "pixiv" - table_description = "pix图库数据表" - unique_together = ("pid", "img_url", "img_p") - - # 0:非r18 1:r18 2:混合 - @classmethod - async def query_images( - cls, - keywords: list[str] | None = None, - uid: int | None = None, - pid: int | None = None, - r18: int | None = 0, - num: int = 100, - ) -> list["Pixiv"]: - """查找符合条件的图片 - - 参数: - keywords: 关键词 - uid: 画师uid - pid: 图片pid - r18: 是否r18,0:非r18 1:r18 2:混合 - num: 查找图片的数量 - """ - if not num: - return [] - query = cls - if r18 == 0: - query = query.filter(is_r18=False) - elif r18 == 1: - query = query.filter(is_r18=True) - if keywords: - for keyword in keywords: - query = query.filter(tags__contains=keyword) - elif uid: - query = query.filter(uid=uid) - elif pid: - query = query.filter(pid=pid) - query = query.annotate(rand=Random()).limit(num) - return await query.all() # type: ignore - - @classmethod - async def get_keyword_num(cls, tags: list[str] | None = None) -> tuple[int, int]: - """获取相关关键词(keyword, tag)在图库中的数量 - - 参数: - tags: 关键词/Tag - """ - query = cls - if tags: - for tag in tags: - query = query.filter(tags__contains=tag) - else: - query = query.all() - count = await query.filter(is_r18=False).count() - r18_count = await query.filter(is_r18=True).count() - return count, r18_count diff --git a/zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py b/zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py deleted file mode 100644 index 5de544a5..00000000 --- a/zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py +++ /dev/null @@ -1,52 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class PixivKeywordUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - keyword = fields.CharField(255, unique=True) - """关键词""" - is_pass = fields.BooleanField() - """是否通过""" - - class Meta: - table = "pixiv_keyword_users" - table_description = "pixiv关键词数据表" - - @classmethod - async def get_current_keyword(cls) -> tuple[list[str], list[str]]: - """获取当前通过与未通过的关键词""" - pass_keyword = [] - not_pass_keyword = [] - for data in await cls.all().values_list("keyword", "is_pass"): - if data[1]: - pass_keyword.append(data[0]) - else: - not_pass_keyword.append(data[0]) - return pass_keyword, not_pass_keyword - - @classmethod - async def get_black_pid(cls) -> list[str]: - """获取黑名单PID""" - black_pid = [] - keyword_list = await cls.filter(user_id="114514").values_list( - "keyword", flat=True - ) - for image in keyword_list: - black_pid.append(image[6:]) - return black_pid - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE pixiv_keyword_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE pixiv_keyword_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE pixiv_keyword_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/pix_gallery/pix.py b/zhenxun/plugins/pix_gallery/pix.py deleted file mode 100644 index 2f8d25c3..00000000 --- a/zhenxun/plugins/pix_gallery/pix.py +++ /dev/null @@ -1,247 +0,0 @@ -import random - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.withdraw_manage import WithdrawManager - -from ._data_source import get_image -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv - -__plugin_meta__ = PluginMetadata( - name="PIX", - description="这里是PIX图库!", - usage=""" - 指令: - pix ?*[tags]: 通过 tag 获取相似图片,不含tag时随机抽取 - pid [uid]: 通过uid获取图片 - pix pid[pid]: 查看图库中指定pid图片 - 示例:pix 萝莉 白丝 - 示例:pix 萝莉 白丝 10 (10为数量) - 示例:pix #02 (当tag只有1个tag且为数字时,使用#标记,否则将被判定为数量) - 示例:pix 34582394 (查询指定uid图片) - 示例:pix pid:12323423 (查询指定pid图片) - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - superuser_help=""" - 指令: - pix -s ?*[tags]: 通过tag获取色图,不含tag时随机 - pix -r ?*[tags]: 通过tag获取r18图,不含tag时随机 - """, - menu_type="来点好康的", - limits=[BaseBlock(result="您有PIX图片正在处理,请稍等...")], - configs=[ - RegisterConfig( - key="MAX_ONCE_NUM2FORWARD", - value=None, - help="单次发送的图片数量达到指定值时转发为合并消息", - default_value=None, - type=int, - ), - RegisterConfig( - key="ALLOW_GROUP_SETU", - value=False, - help="允许非超级用户使用-s参数", - default_value=False, - type=bool, - ), - RegisterConfig( - key="ALLOW_GROUP_R18", - value=False, - help="允许非超级用户使用-r参数", - default_value=False, - type=bool, - ), - ], - ).dict(), -) - -# pix = on_command("pix", aliases={"PIX", "Pix"}, priority=5, block=True) - -_matcher = on_alconna( - Alconna( - "pix", - Args["tags?", str] / "\n", - Option("-s", action=store_true, help_text="色图"), - Option("-r", action=store_true, help_text="r18"), - ), - priority=5, - block=True, -) - -PIX_RATIO = None -OMEGA_RATIO = None - - -@_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, tags: Match[str]): - global PIX_RATIO, OMEGA_RATIO - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if PIX_RATIO is None: - pix_omega_pixiv_ratio = Config.get_config("pix", "PIX_OMEGA_PIXIV_RATIO") - PIX_RATIO = pix_omega_pixiv_ratio[0] / ( - pix_omega_pixiv_ratio[0] + pix_omega_pixiv_ratio[1] - ) - OMEGA_RATIO = 1 - PIX_RATIO - num = 1 - # keyword = arg.extract_plain_text().strip() - keyword = "" - spt = tags.result.split() if tags.available else [] - if arparma.find("s"): - nsfw_tag = 1 - elif arparma.find("r"): - nsfw_tag = 2 - else: - nsfw_tag = 0 - if session.id1 not in bot.config.superusers: - if (nsfw_tag == 1 and not Config.get_config("pix", "ALLOW_GROUP_SETU")) or ( - nsfw_tag == 2 and not Config.get_config("pix", "ALLOW_GROUP_R18") - ): - await MessageUtils.build_message( - "你不能看这些噢,这些都是是留给管理员看的..." - ).finish() - if (n := len(spt)) == 1: - if str(spt[0]).isdigit() and int(spt[0]) < 100: - num = int(spt[0]) - keyword = "" - elif spt[0].startswith("#"): - keyword = spt[0][1:] - elif n > 1: - if str(spt[-1]).isdigit(): - num = int(spt[-1]) - if num > 10: - if session.id1 not in bot.config.superusers or ( - session.id1 in bot.config.superusers and num > 30 - ): - num = random.randint(1, 10) - await MessageUtils.build_message( - f"太贪心了,就给你发 {num}张 好了" - ).send() - spt = spt[:-1] - keyword = " ".join(spt) - pix_num = int(num * PIX_RATIO) + 15 if PIX_RATIO != 0 else 0 - omega_num = num - pix_num + 15 - if str(keyword).isdigit(): - if num == 1: - pix_num = 15 - omega_num = 15 - all_image = await Pixiv.query_images( - uid=int(keyword), num=pix_num, r18=1 if nsfw_tag == 2 else 0 - ) + await OmegaPixivIllusts.query_images( - uid=int(keyword), num=omega_num, nsfw_tag=nsfw_tag - ) - elif keyword.lower().startswith("pid"): - pid = keyword.replace("pid", "").replace(":", "").replace(":", "") - if not str(pid).isdigit(): - await MessageUtils.build_message("PID必须是数字...").finish(reply_to=True) - all_image = await Pixiv.query_images( - pid=int(pid), r18=1 if nsfw_tag == 2 else 0 - ) - if not all_image: - all_image = await OmegaPixivIllusts.query_images( - pid=int(pid), nsfw_tag=nsfw_tag - ) - num = len(all_image) - else: - tmp = await Pixiv.query_images( - spt, r18=1 if nsfw_tag == 2 else 0, num=pix_num - ) + await OmegaPixivIllusts.query_images(spt, nsfw_tag=nsfw_tag, num=omega_num) - tmp_ = [] - all_image = [] - for x in tmp: - if x.pid not in tmp_: - all_image.append(x) - tmp_.append(x.pid) - if not all_image: - await MessageUtils.build_message( - f"未在图库中找到与 {keyword} 相关Tag/UID/PID的图片..." - ).finish(reply_to=True) - msg_list = [] - for _ in range(num): - img_url = None - author = None - if not all_image: - await MessageUtils.build_message("坏了...发完了,没图了...").finish() - img = random.choice(all_image) - all_image.remove(img) # type: ignore - if isinstance(img, OmegaPixivIllusts): - img_url = img.url - author = img.uname - elif isinstance(img, Pixiv): - img_url = img.img_url - author = img.author - pid = img.pid - title = img.title - uid = img.uid - if img_url: - _img = await get_image(img_url, session.id1) - if _img: - if Config.get_config("pix", "SHOW_INFO"): - msg_list.append( - MessageUtils.build_message( - [ - f"title:{title}\n" - f"author:{author}\n" - f"PID:{pid}\nUID:{uid}\n", - _img, - ] - ) - ) - else: - msg_list.append(_img) - logger.info( - f" 查看PIX图库PID: {pid}", arparma.header_result, session=session - ) - else: - msg_list.append(MessageUtils.build_message("这张图似乎下载失败了")) - logger.info( - f" 查看PIX图库PID: {pid},下载图片出错", - arparma.header_result, - session=session, - ) - if ( - Config.get_config("pix", "MAX_ONCE_NUM2FORWARD") - and num >= Config.get_config("pix", "MAX_ONCE_NUM2FORWARD") - and gid - ): - for msg in msg_list: - receipt = await msg.send() - if receipt: - message_id = receipt.msg_ids[0]["message_id"] - await WithdrawManager.withdraw_message( - bot, - str(message_id), - Config.get_config("pix", "WITHDRAW_PIX_MESSAGE"), - session, - ) - else: - for msg in msg_list: - receipt = await msg.send() - if receipt: - message_id = receipt.msg_ids[0]["message_id"] - await WithdrawManager.withdraw_message( - bot, - message_id, - Config.get_config("pix", "WITHDRAW_PIX_MESSAGE"), - session, - ) diff --git a/zhenxun/plugins/pix_gallery/pix_add_keyword.py b/zhenxun/plugins/pix_gallery/pix_add_keyword.py deleted file mode 100644 index 452213e3..00000000 --- a/zhenxun/plugins/pix_gallery/pix_add_keyword.py +++ /dev/null @@ -1,135 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import uid_pid_exists -from ._model.pixiv import Pixiv -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="PIX添加", - description="PIX关键词/UID/PID添加管理", - usage=""" - 指令: - 添加pix关键词 [Tag]: 添加一个pix搜索收录Tag - pix添加 uid [uid]: 添加一个pix搜索收录uid - pix添加 pid [pid]: 添加一个pix收录pid - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_add_matcher = on_alconna( - Alconna("添加pix关键词", Args["keyword", str]), priority=5, block=True -) - -_uid_matcher = on_alconna( - Alconna( - "pix添加", - Args["add_type", ["uid", "pid"]]["id", str], - Option("-f", action=store_true, help_text="强制收录不检查是否存在"), - ), - priority=5, - block=True, -) - -_black_matcher = on_alconna( - Alconna("添加pix黑名单", Args["pid", str]), priority=5, block=True -) - - -@_add_matcher.handle() -async def _(bot: Bot, session: EventSession, keyword: str, arparma: Arparma): - group_id = session.id3 or session.id2 or -1 - if not await PixivKeywordUser.exists(keyword=keyword): - await PixivKeywordUser.create( - user_id=str(session.id1), - group_id=str(group_id), - keyword=keyword, - is_pass=str(session.id1) in bot.config.superusers, - ) - text = f"已成功添加pixiv搜图关键词:{keyword}" - if session.id1 not in bot.config.superusers: - text += ",请等待管理员通过该关键词!" - await MessageUtils.build_message(text).send(reply_to=True) - logger.info( - f"添加了pixiv搜图关键词: {keyword}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message(f"该关键词 {keyword} 已存在...").send() - - -@_uid_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, add_type: str, id: str): - group_id = session.id3 or session.id2 or -1 - exists_flag = True - if arparma.find("f") and session.id1 in bot.config.superusers: - exists_flag = False - word = None - if add_type == "uid": - word = f"uid:{id}" - else: - word = f"pid:{id}" - if await Pixiv.get_or_none(pid=int(id), img_p="p0"): - await MessageUtils.build_message(f"该PID:{id}已存在...").finish( - reply_to=True - ) - if not await uid_pid_exists(word) and exists_flag: - await MessageUtils.build_message( - "画师或作品不存在或搜索正在CD,请稍等..." - ).finish(reply_to=True) - if not await PixivKeywordUser.exists(keyword=word): - await PixivKeywordUser.create( - user_id=session.id1, - group_id=str(group_id), - keyword=word, - is_pass=session.id1 in bot.config.superusers, - ) - text = f"已成功添加pixiv搜图UID/PID:{id}" - if session.id1 not in bot.config.superusers: - text += ",请等待管理员通过该关键词!" - await MessageUtils.build_message(text).send(reply_to=True) - else: - await MessageUtils.build_message(f"该UID/PID:{id} 已存在...").send() - - -@_black_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, pid: str): - img_p = "" - if "p" in pid: - img_p = pid.split("p")[-1] - pid = pid.replace("_", "") - pid = pid[: pid.find("p")] - if not pid.isdigit: - await MessageUtils.build_message("PID必须全部是数字!").finish(reply_to=True) - if not await PixivKeywordUser.exists( - keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}" - ): - await PixivKeywordUser.create( - user_id=114514, - group_id=114514, - keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}", - is_pass=session.id1 in bot.config.superusers, - ) - await MessageUtils.build_message(f"已添加PID:{pid} 至黑名单中...").send() - logger.info( - f" 添加了pixiv搜图黑名单 PID:{pid}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message( - f"PID:{pid} 已添加黑名单中,添加失败..." - ).send() diff --git a/zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py b/zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py deleted file mode 100644 index 9a8f2ea7..00000000 --- a/zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py +++ /dev/null @@ -1,218 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - At, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from ._data_source import remove_image -from ._model.pixiv import Pixiv -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="PIX删除", - description="PIX关键词/UID/PID添加管理", - usage=""" - 指令: - pix关键词 [y/n] [关键词/pid/uid] - 删除pix关键词 ['pid'/'uid'/'keyword'] [关键词/pid/uid] - 删除pix图片 *[pid] - 示例:pix关键词 y 萝莉 - 示例:pix关键词 y 12312312 uid - 示例:pix关键词 n 12312312 pid - 示例:删除pix关键词 keyword 萝莉 - 示例:删除pix关键词 uid 123123123 - 示例:删除pix关键词 pid 123123 - 示例:删除pix图片 4223442 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -_pass_matcher = on_alconna( - Alconna( - "pix关键词", Args["status", ["y", "n"]]["keyword", str]["type?", ["uid", "pid"]] - ), - permission=SUPERUSER, - priority=1, - block=True, -) - -_del_matcher = on_alconna( - Alconna("删除pix关键词", Args["type", ["pid", "uid", "keyword"]]["keyword", str]), - permission=SUPERUSER, - priority=1, - block=True, -) - -_del_pic_matcher = on_alconna( - Alconna( - "删除pix图片", - Args["pid", str], - Option("-b|--black", action=store_true, help_text=""), - ), - permission=SUPERUSER, - priority=1, - block=True, -) - - -@_pass_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - status: str, - keyword: str, - type: Match[str], -): - tmp = {"group": {}, "private": {}} - flag = status == "y" - if type.available: - if type.result == "uid": - keyword = f"uid:{keyword}" - else: - keyword = f"pid:{keyword}" - if not keyword[4:].isdigit(): - await MessageUtils.build_message(f"{keyword} 非全数字...").finish( - reply_to=True - ) - data = await PixivKeywordUser.get_or_none(keyword=keyword) - user_id = 0 - group_id = 0 - if data: - data.is_pass = flag - await data.save(update_fields=["is_pass"]) - user_id, group_id = data.user_id, data.group_id - if not user_id: - await MessageUtils.build_message( - f"未找到关键词/UID:{keyword},请检查关键词/UID是否存在..." - ).finish(reply_to=True) - if flag: - if group_id == -1: - if not tmp["private"].get(user_id): - tmp["private"][user_id] = {"keyword": [keyword]} - else: - tmp["private"][user_id]["keyword"].append(keyword) - else: - if not tmp["group"].get(group_id): - tmp["group"][group_id] = {} - if not tmp["group"][group_id].get(user_id): - tmp["group"][group_id][user_id] = {"keyword": [keyword]} - else: - tmp["group"][group_id][user_id]["keyword"].append(keyword) - await MessageUtils.build_message( - f"已成功{'通过' if flag else '拒绝'}搜图关键词:{keyword}..." - ).send() - for user in tmp["private"]: - text = ",".join(tmp["private"][user]["keyword"]) - await PlatformUtils.send_message( - bot, - user, - None, - f"你的关键词/UID/PID {text} 已被管理员通过,将在下一次进行更新...", - ) - # await bot.send_private_msg( - # user_id=user, - # message=f"你的关键词/UID/PID {x} 已被管理员通过,将在下一次进行更新...", - # ) - for group in tmp["group"]: - for user in tmp["group"][group]: - text = ",".join(tmp["group"][group][user]["keyword"]) - await PlatformUtils.send_message( - bot, - None, - group_id=group, - message=MessageUtils.build_message( - [ - At(flag="user", target=user), - "你的关键词/UID/PID {x} 已被管理员通过,将在下一次进行更新...", - ] - ), - ) - logger.info( - f" 通过了pixiv搜图关键词/UID: {keyword}", arparma.header_result, session=session - ) - - -@_del_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, type: str, keyword: str): - if type != "keyword": - keyword = f"{type}:{keyword}" - if data := await PixivKeywordUser.get_or_none(keyword=keyword): - await data.delete() - await MessageUtils.build_message( - f"删除搜图关键词/UID:{keyword} 成功..." - ).send() - logger.info( - f" 删除了pixiv搜图关键词: {keyword}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message( - f"未查询到搜索关键词/UID/PID:{keyword},删除失败!" - ).send() - - -@_del_pic_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, keyword: str): - msg = "" - black_pid = "" - flag = arparma.find("black") - img_p = None - if "p" in keyword: - img_p = keyword.split("p")[-1] - keyword = keyword.replace("_", "") - keyword = keyword[: keyword.find("p")] - elif "ugoira" in keyword: - img_p = keyword.split("ugoira")[-1] - keyword = keyword.replace("_", "") - keyword = keyword[: keyword.find("ugoira")] - if keyword.isdigit(): - if await Pixiv.query_images(pid=int(keyword), r18=2): - if await remove_image(int(keyword), img_p): - msg += f'{keyword}{f"_p{img_p}" if img_p else ""},' - if flag: - if await PixivKeywordUser.exists( - keyword=f"black:{keyword}{f'_p{img_p}' if img_p else ''}" - ): - await PixivKeywordUser.create( - user_id="114514", - group_id="114514", - keyword=f"black:{keyword}{f'_p{img_p}' if img_p else ''}", - is_pass=False, - ) - black_pid += f'{keyword}{f"_p{img_p}" if img_p else ""},' - logger.info( - f" 删除了PIX图片 PID:{keyword}{f'_p{img_p}' if img_p else ''}", - arparma.header_result, - session=session, - ) - else: - await MessageUtils.build_message( - f"PIX:图片pix:{keyword}{f'_p{img_p}' if img_p else ''} 不存在...无法删除.." - ).send() - else: - await MessageUtils.build_message(f"PID必须为数字!pid:{keyword}").send( - reply_to=True - ) - await MessageUtils.build_message(f"PIX:成功删除图片:{msg[:-1]}").send() - if flag: - await MessageUtils.build_message( - f"成功图片PID加入黑名单:{black_pid[:-1]}" - ).send() diff --git a/zhenxun/plugins/pix_gallery/pix_show_info.py b/zhenxun/plugins/pix_gallery/pix_show_info.py deleted file mode 100644 index cb1cbf2a..00000000 --- a/zhenxun/plugins/pix_gallery/pix_show_info.py +++ /dev/null @@ -1,85 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import gen_keyword_pic, get_keyword_num -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="查看pix图库", - description="让我看看管理员私藏了多少货", - usage=""" - 指令: - 我的pix关键词 - 显示pix关键词 - 查看pix图库 ?[tag]: 查看指定tag图片数量,为空时查看整个图库 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_my_matcher = on_alconna(Alconna("我的pix关键词"), priority=5, block=True) - -_show_matcher = on_alconna(Alconna("显示pix关键词"), priority=5, block=True) - -_pix_matcher = on_alconna( - Alconna("查看pix图库", Args["keyword?", str]), priority=5, block=True -) - - -@_my_matcher.handle() -async def _(arparma: Arparma, session: EventSession): - data = await PixivKeywordUser.filter(user_id=session.id1).values_list( - "keyword", flat=True - ) - if not data: - await MessageUtils.build_message("您目前没有提供任何Pixiv搜图关键字...").finish( - reply_to=True - ) - await MessageUtils.build_message(f"您目前提供的如下关键字:\n\t" + ",".join(data)).send() # type: ignore - logger.info("查看我的pix关键词", arparma.header_result, session=session) - - -@_show_matcher.handle() -async def _(bot: Bot, arparma: Arparma, session: EventSession): - _pass_keyword, not_pass_keyword = await PixivKeywordUser.get_current_keyword() - if _pass_keyword or not_pass_keyword: - image = await gen_keyword_pic( - _pass_keyword, not_pass_keyword, session.id1 in bot.config.superusers - ) - await MessageUtils.build_message(image).send() # type: ignore - else: - if session.id1 in bot.config.superusers: - await MessageUtils.build_message( - f"目前没有已收录或待收录的搜索关键词..." - ).send() - else: - await MessageUtils.build_message(f"目前没有已收录的搜索关键词...").send() - - -@_pix_matcher.handle() -async def _(bot: Bot, arparma: Arparma, session: EventSession, keyword: Match[str]): - _keyword = "" - if keyword.available: - _keyword = keyword.result - count, r18_count, count_, setu_count, r18_count_ = await get_keyword_num(_keyword) - await MessageUtils.build_message( - f"PIX图库:{_keyword}\n" - f"总数:{count + r18_count}\n" - f"美图:{count}\n" - f"R18:{r18_count}\n" - f"---------------\n" - f"Omega图库:{_keyword}\n" - f"总数:{count_ + setu_count + r18_count_}\n" - f"美图:{count_}\n" - f"色图:{setu_count}\n" - f"R18:{r18_count_}" - ).send() - logger.info("查看pix图库", arparma.header_result, session=session) diff --git a/zhenxun/plugins/pix_gallery/pix_update.py b/zhenxun/plugins/pix_gallery/pix_update.py deleted file mode 100644 index b0f209dc..00000000 --- a/zhenxun/plugins/pix_gallery/pix_update.py +++ /dev/null @@ -1,225 +0,0 @@ -import asyncio -import os -import re -import time -from pathlib import Path - -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from ._data_source import start_update_image_url -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="pix检查更新", - description="pix图库收录数据检查更新", - usage=""" - 指令: - 更新pix关键词 *[keyword/uid/pid] [num=max]: 更新仅keyword/uid/pid或全部 - pix检测更新:检测从未更新过的uid和pid - 示例:更新pix关键词keyword - 示例:更新pix关键词uid 10 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -_update_matcher = on_alconna( - Alconna("更新pix关键词", Args["type", ["uid", "pid", "keyword"]]["num?", int]), - permission=SUPERUSER, - priority=1, - block=True, -) - -_check_matcher = on_alconna( - Alconna( - "pix检测更新", Option("-u|--update", action=store_true, help_text="是否更新") - ), - permission=SUPERUSER, - priority=1, - block=True, -) - -_omega_matcher = on_alconna( - Alconna("检测omega图库"), permission=SUPERUSER, priority=1, block=True -) - - -@_update_matcher.handle() -async def _(arparma: Arparma, session: EventSession, type: str, num: Match[int]): - _pass_keyword, _ = await PixivKeywordUser.get_current_keyword() - _pass_keyword.reverse() - black_pid = await PixivKeywordUser.get_black_pid() - _keyword = [ - x - for x in _pass_keyword - if not x.startswith("uid:") - and not x.startswith("pid:") - and not x.startswith("black:") - ] - _uid = [x for x in _pass_keyword if x.startswith("uid:")] - _pid = [x for x in _pass_keyword if x.startswith("pid:")] - _num = num.result if num.available else 9999 - if _num < 10000: - keyword_str = ",".join( - _keyword[: _num if _num < len(_keyword) else len(_keyword)] - ) - uid_str = ",".join(_uid[: _num if _num < len(_uid) else len(_uid)]) - pid_str = ",".join(_pid[: _num if _num < len(_pid) else len(_pid)]) - if type == "pid": - update_lst = _pid - info = f"开始更新Pixiv搜图PID:\n{pid_str}" - elif type == "uid": - update_lst = _uid - info = f"开始更新Pixiv搜图UID:\n{uid_str}" - elif type == "keyword": - update_lst = _keyword - info = f"开始更新Pixiv搜图关键词:\n{keyword_str}" - else: - update_lst = _pass_keyword - info = f"开始更新Pixiv搜图关键词:\n{keyword_str}\n更新UID:{uid_str}\n更新PID:{pid_str}" - _num = _num if _num < len(update_lst) else len(update_lst) - else: - if type == "pid": - update_lst = [f"pid:{_num}"] - info = f"开始更新Pixiv搜图UID:\npid:{_num}" - else: - update_lst = [f"uid:{_num}"] - info = f"开始更新Pixiv搜图UID:\nuid:{_num}" - await MessageUtils.build_message(info).send() - start_time = time.time() - pid_count, pic_count = await start_update_image_url( - update_lst[:_num], black_pid, type == "pid" - ) - await MessageUtils.build_message( - f"Pixiv搜图关键词搜图更新完成...\n" - f"累计更新PID {pid_count} 个\n" - f"累计更新图片 {pic_count} 张" - + "\n耗时:{:.2f}秒".format((time.time() - start_time)) - ).send() - logger.info("更新pix关键词", arparma.header_result, session=session) - - -@_check_matcher.handle() -async def _(bot: Bot, arparma: Arparma, session: EventSession): - _pass_keyword, _ = await PixivKeywordUser.get_current_keyword() - x_uid = [] - x_pid = [] - _uid = [int(x[4:]) for x in _pass_keyword if x.startswith("uid:")] - _pid = [int(x[4:]) for x in _pass_keyword if x.startswith("pid:")] - all_images = await Pixiv.query_images(r18=2) - for img in all_images: - if img.pid not in x_pid: - x_pid.append(img.pid) - if img.uid not in x_uid: - x_uid.append(img.uid) - await MessageUtils.build_message( - "从未更新过的UID:" - + ",".join([f"uid:{x}" for x in _uid if x not in x_uid]) - + "\n" - + "从未更新过的PID:" - + ",".join([f"pid:{x}" for x in _pid if x not in x_pid]) - ).send() - if arparma.find("update"): - await MessageUtils.build_message("开始自动自动更新PID....").send() - update_lst = [f"pid:{x}" for x in _uid if x not in x_uid] - black_pid = await PixivKeywordUser.get_black_pid() - start_time = time.time() - pid_count, pic_count = await start_update_image_url( - update_lst, black_pid, False - ) - await MessageUtils.build_message( - f"Pixiv搜图关键词搜图更新完成...\n" - f"累计更新PID {pid_count} 个\n" - f"累计更新图片 {pic_count} 张" - + "\n耗时:{:.2f}秒".format((time.time() - start_time)) - ).send() - logger.info( - f"pix检测更新, 是否更新: {arparma.find('update')}", - arparma.header_result, - session=session, - ) - - -@_omega_matcher.handle() -async def _(): - async def _tasks(line: str, all_pid: list[int], length: int, index: int): - data = line.split("VALUES", maxsplit=1)[-1].strip()[1:-2] - num_list = re.findall(r"(\d+)", data) - pid = int(num_list[1]) - uid = int(num_list[2]) - id_ = 3 - while num_list[id_] not in ["0", "1"]: - id_ += 1 - classified = int(num_list[id_]) - nsfw_tag = int(num_list[id_ + 1]) - width = int(num_list[id_ + 2]) - height = int(num_list[id_ + 3]) - str_list = re.findall(r"'(.*?)',", data) - title = str_list[0] - uname = str_list[1] - tags = str_list[2] - url = str_list[3] - if pid in all_pid: - logger.info(f"添加OmegaPixivIllusts图库数据已存在 ---> pid:{pid}") - return - _, is_create = await OmegaPixivIllusts.get_or_create( - pid=pid, - title=title, - width=width, - height=height, - url=url, - uid=uid, - nsfw_tag=nsfw_tag, - tags=tags, - uname=uname, - classified=classified, - ) - if is_create: - logger.info( - f"成功添加OmegaPixivIllusts图库数据 pid:{pid} 本次预计存储 {length} 张,已更新第 {index} 张" - ) - else: - logger.info(f"添加OmegaPixivIllusts图库数据已存在 ---> pid:{pid}") - - omega_pixiv_illusts = None - for file in os.listdir("."): - if "omega_pixiv_artwork" in file and ".sql" in file: - omega_pixiv_illusts = Path() / file - if omega_pixiv_illusts: - with open(omega_pixiv_illusts, "r", encoding="utf8") as f: - lines = f.readlines() - tasks = [] - length = len([x for x in lines if "INSERT INTO" in x.upper()]) - all_pid = await OmegaPixivIllusts.all().values_list("pid", flat=True) - index = 0 - logger.info("检测到OmegaPixivIllusts数据库,准备开始更新....") - for line in lines: - if "INSERT INTO" in line.upper(): - index += 1 - logger.info(f"line: {line} 加入更新计划") - tasks.append( - asyncio.create_task(_tasks(line, all_pid, length, index)) # type: ignore - ) - await asyncio.gather(*tasks) - omega_pixiv_illusts.unlink() diff --git a/zhenxun/plugins/pixiv_rank_search/__init__.py b/zhenxun/plugins/pixiv_rank_search/__init__.py deleted file mode 100644 index 01945cd8..00000000 --- a/zhenxun/plugins/pixiv_rank_search/__init__.py +++ /dev/null @@ -1,225 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from httpx import NetworkError -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import is_valid_date - -from .data_source import download_pixiv_imgs, get_pixiv_urls, search_pixiv_urls - -__plugin_meta__ = PluginMetadata( - name="P站排行/搜图", - description="P站排行榜直接冲,P站搜图跟着冲", - usage=""" - P站排行: - 可选参数: - 类型: - 1. 日排行 - 2. 周排行 - 3. 月排行 - 4. 原创排行 - 5. 新人排行 - 6. R18日排行 - 7. R18周排行 - 8. R18受男性欢迎排行 - 9. R18重口排行【慎重!】 - 【使用时选择参数序号即可,R18仅可私聊】 - p站排行 ?[参数] ?[数量] ?[日期] - 示例: - p站排行 [无参数默认为日榜] - p站排行 1 - p站排行 1 5 - p站排行 1 5 2018-4-25 - 【注意空格!!】【在线搜索会较慢】 - --------------------------------- - P站搜图: - 搜图 [关键词] ?[数量] ?[页数=1] ?[r18](不屏蔽R-18) - 示例: - 搜图 樱岛麻衣 - 搜图 樱岛麻衣 5 - 搜图 樱岛麻衣 5 r18 - 搜图 樱岛麻衣#1000users 5 - 【多个关键词用#分割】 - 【默认为 热度排序】 - 【注意空格!!】【在线搜索会较慢】【数量可能不符?可能该页数量不够,也可能被R-18屏蔽】 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - aliases={"P站排行", "搜图"}, - menu_type="来点好康的", - limits=[BaseBlock(result="P站排行榜或搜图正在搜索,请不要重复触发命令...")], - configs=[ - RegisterConfig( - key="TIMEOUT", - value=10, - help="图片下载超时限制", - default_value=10, - type=int, - ), - RegisterConfig( - key="MAX_PAGE_LIMIT", - value=20, - help="作品最大页数限制,超过的作品会被略过", - default_value=20, - type=int, - ), - RegisterConfig( - key="ALLOW_GROUP_R18", - value=False, - help="图允许群聊中使用 r18 参数", - default_value=False, - type=bool, - ), - RegisterConfig( - module="hibiapi", - key="HIBIAPI", - value="https://api.obfs.dev", - help="如果没有自建或其他hibiapi请不要修改", - default_value="https://api.obfs.dev", - ), - RegisterConfig( - module="pixiv", - key="PIXIV_NGINX_URL", - value="i.pixiv.re", - help="Pixiv反向代理", - ), - ], - ).dict(), -) - - -rank_dict = { - "1": "day", - "2": "week", - "3": "month", - "4": "week_original", - "5": "week_rookie", - "6": "day_r18", - "7": "week_r18", - "8": "day_male_r18", - "9": "week_r18g", -} - -_rank_matcher = on_alconna( - Alconna("p站排行", Args["rank_type", int, 1]["num", int, 10]["datetime?", str]), - aliases={"p站排行榜"}, - priority=5, - block=True, - rule=to_me(), -) - -_keyword_matcher = on_alconna( - Alconna( - "搜图", - Args["keyword", str]["num", int, 10]["page", int, 1], - Option("-r", action=store_true, help_text="是否屏蔽r18"), - ), - priority=5, - block=True, - rule=to_me(), -) - - -@_rank_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - rank_type: int, - num: int, - datetime: Match[str], -): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - code = 0 - info_list = [] - _datetime = None - if datetime.available: - _datetime = datetime.result - if not is_valid_date(_datetime): - await MessageUtils.build_message("日期不合法,示例: 2018-4-25").finish( - reply_to=True - ) - if rank_type in [6, 7, 8, 9]: - if gid: - await MessageUtils.build_message("羞羞脸!私聊里自己看!").finish( - at_sender=True - ) - info_list, code = await get_pixiv_urls( - rank_dict[str(rank_type)], num, date=_datetime - ) - if code != 200 and info_list: - if isinstance(info_list[0], str): - await MessageUtils.build_message(info_list[0]).finish() - if not info_list: - await MessageUtils.build_message("没有找到啊,等等再试试吧~V").send( - at_sender=True - ) - for title, author, urls in info_list: - try: - images = await download_pixiv_imgs(urls, session.id1) # type: ignore - await MessageUtils.build_message( - [f"title: {title}\nauthor: {author}\n"] + images # type: ignore - ).send() - - except (NetworkError, TimeoutError): - await MessageUtils.build_message("这张图网络直接炸掉了!").send() - logger.info( - f" 查看了P站排行榜 rank_type{rank_type}", arparma.header_result, session=session - ) - - -@_keyword_matcher.handle() -async def _( - bot: Bot, session: EventSession, arparma: Arparma, keyword: str, num: int, page: int -): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if gid: - if arparma.find("r") and not Config.get_config( - "pixiv_rank_search", "ALLOW_GROUP_R18" - ): - await MessageUtils.build_message("(脸红#) 你不会害羞的 八嘎!").finish( - at_sender=True - ) - r18 = 0 if arparma.find("r") else 1 - info_list = None - keyword = keyword.replace("#", " ") - info_list, code = await search_pixiv_urls(keyword, num, page, r18) - if code != 200 and isinstance(info_list[0], str): - await MessageUtils.build_message(info_list[0]).finish() - if not info_list: - await MessageUtils.build_message("没有找到啊,等等再试试吧~V").finish( - at_sender=True - ) - for title, author, urls in info_list: - try: - images = await download_pixiv_imgs(urls, session.id1) # type: ignore - await MessageUtils.build_message( - [f"title: {title}\nauthor: {author}\n"] + images # type: ignore - ).send() - - except (NetworkError, TimeoutError): - await MessageUtils.build_message("这张图网络直接炸掉了!").send() - logger.info( - f" 查看了搜索 {keyword} R18:{r18}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/pixiv_rank_search/data_source.py b/zhenxun/plugins/pixiv_rank_search/data_source.py deleted file mode 100644 index 761a93f2..00000000 --- a/zhenxun/plugins/pixiv_rank_search/data_source.py +++ /dev/null @@ -1,171 +0,0 @@ -from asyncio.exceptions import TimeoutError -from pathlib import Path - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.utils import change_img_md5 - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net/", -} - - -async def get_pixiv_urls( - mode: str, num: int = 10, page: int = 1, date: str | None = None -) -> tuple[list[tuple[str, str, list[str]] | str], int]: - """获取排行榜图片url - - 参数: - mode: 模式类型 - num: 数量. - page: 页数. - date: 日期. - - 返回: - tuple[list[tuple[str, str, list[str]] | str], int]: 图片标题作者url数据,请求状态 - """ - - params = {"mode": mode, "page": page} - if date: - params["date"] = date - hibiapi = Config.get_config("hibiapi", "HIBIAPI") - hibiapi = hibiapi[:-1] if hibiapi[-1] == "/" else hibiapi - rank_url = f"{hibiapi}/api/pixiv/rank" - return await parser_data(rank_url, num, params, "rank") - - -async def search_pixiv_urls( - keyword: str, num: int, page: int, r18: int -) -> tuple[list[tuple[str, str, list[str]] | str], int]: - """搜图图片url - - 参数: - keyword: 关键词 - num: 数量 - page: 页数 - r18: 是否r18 - - 返回: - tuple[list[tuple[str, str, list[str]] | str], int]: 图片标题作者url数据,请求状态 - """ - params = {"word": keyword, "page": page} - hibiapi = Config.get_config("hibiapi", "HIBIAPI") - hibiapi = hibiapi[:-1] if hibiapi[-1] == "/" else hibiapi - search_url = f"{hibiapi}/api/pixiv/search" - return await parser_data(search_url, num, params, "search", r18) - - -async def parser_data( - url: str, num: int, params: dict, type_: str, r18: int = 0 -) -> tuple[list[tuple[str, str, list[str]] | str], int]: - """解析数据搜索 - - 参数: - url: 访问URL - num: 数量 - params: 请求参数 - type_: 类型,rank或search - r18: 是否r18. - - 返回: - tuple[list[tuple[str, str, list[str]] | str], int]: 图片标题作者url数据,请求状态 - """ - info_list = [] - for _ in range(3): - try: - response = await AsyncHttpx.get( - url, - params=params, - timeout=Config.get_config("pixiv_rank_search", "TIMEOUT"), - ) - if response.status_code == 200: - data = response.json() - if data.get("illusts"): - data = data["illusts"] - break - except TimeoutError: - pass - except Exception as e: - logger.error(f"P站排行/搜图解析数据发生错误", e=e) - return ["发生了一些些错误..."], 995 - else: - return ["网络不太好?没有该页数?也许过一会就好了..."], 998 - num = num if num < 30 else 30 - _data = [] - for x in data: - if x["page_count"] < Config.get_config("pixiv_rank_search", "MAX_PAGE_LIMIT"): - if type_ == "search" and r18 == 1: - if "R-18" in str(x["tags"]): - continue - _data.append(x) - if len(_data) == num: - break - for x in _data: - title = x["title"] - author = x["user"]["name"] - urls = [] - if x["page_count"] == 1: - urls.append(x["image_urls"]["large"]) - else: - for j in x["meta_pages"]: - urls.append(j["image_urls"]["large"]) - info_list.append((title, author, urls)) - return info_list, 200 - - -async def download_pixiv_imgs( - urls: list[str], user_id: str, forward_msg_index: int | None = None -) -> list[Path]: - """下载图片 - - 参数: - urls: 图片链接 - user_id: 用户id - forward_msg_index: 转发消息中的图片排序. - - 返回: - MessageFactory: 图片 - """ - result_list = [] - index = 0 - for url in urls: - ws_url = Config.get_config("pixiv", "PIXIV_NGINX_URL") - url = url.replace("_webp", "") - if ws_url: - url = url.replace("i.pximg.net", ws_url).replace("i.pixiv.cat", ws_url) - try: - file = ( - TEMP_PATH / f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg" - if forward_msg_index is not None - else TEMP_PATH / f"{user_id}_{index}_pixiv.jpg" - ) - file = Path(file) - try: - if await AsyncHttpx.download_file( - url, - file, - timeout=Config.get_config("pixiv_rank_search", "TIMEOUT"), - headers=headers, - ): - change_img_md5(file) - image = None - if forward_msg_index is not None: - image = ( - TEMP_PATH - / f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg" - ) - else: - image = TEMP_PATH / f"{user_id}_{index}_pixiv.jpg" - if image: - result_list.append(image) - index += 1 - except OSError: - if file.exists(): - file.unlink() - except Exception as e: - logger.error(f"P站排行/搜图下载图片错误", e=e) - return result_list diff --git a/zhenxun/plugins/poke/__init__.py b/zhenxun/plugins/poke/__init__.py deleted file mode 100644 index 38dafc82..00000000 --- a/zhenxun/plugins/poke/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import random - -from nonebot import on_notice -from nonebot.adapters.onebot.v11 import PokeNotifyEvent -from nonebot.adapters.onebot.v11.message import MessageSegment -from nonebot.plugin import PluginMetadata - -from zhenxun.configs.path_config import IMAGE_PATH, RECORD_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.ban_console import BanConsole -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import CountLimiter - -__plugin_meta__ = PluginMetadata( - name="戳一戳", - description="戳一戳发送语音美图萝莉图不美哉?", - usage=""" - 戳一戳随机掉落语音或美图萝莉图 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - ).dict(), -) - -REPLY_MESSAGE = [ - "lsp你再戳?", - "连个可爱美少女都要戳的肥宅真恶心啊。", - "你再戳!", - "?再戳试试?", - "别戳了别戳了再戳就坏了555", - "我爪巴爪巴,球球别再戳了", - "你戳你🐎呢?!", - "那...那里...那里不能戳...绝对...", - "(。´・ω・)ん?", - "有事恁叫我,别天天一个劲戳戳戳!", - "欸很烦欸!你戳🔨呢", - "?", - "再戳一下试试?", - "???", - "正在关闭对您的所有服务...关闭成功", - "啊呜,太舒服刚刚竟然睡着了。什么事?", - "正在定位您的真实地址...定位成功。轰炸机已起飞", -] - - -_clmt = CountLimiter(3) - -poke_ = on_notice(priority=5, block=False) - - -@poke_.handle() -async def _(event: PokeNotifyEvent): - uid = str(event.user_id) if event.user_id else None - gid = str(event.group_id) if event.group_id else None - if event.self_id == event.target_id: - _clmt.increase(event.user_id) - if _clmt.check(event.user_id) or random.random() < 0.3: - rst = "" - if random.random() < 0.15: - await BanConsole.ban(uid, gid, 1, 60) - rst = "气死我了!" - await poke_.finish(rst + random.choice(REPLY_MESSAGE), at_sender=True) - rand = random.random() - path = random.choice(["luoli", "meitu"]) - if rand <= 0.3 and len(os.listdir(IMAGE_PATH / "image_management" / path)) > 0: - index = random.randint( - 0, len(os.listdir(IMAGE_PATH / "image_management" / path)) - 1 - ) - await MessageUtils.build_message( - [ - f"id: {index}", - IMAGE_PATH / "image_management" / path / f"{index}.jpg", - ] - ).send() - logger.info(f"USER {event.user_id} 戳了戳我") - elif 0.3 < rand < 0.6: - voice = random.choice(os.listdir(RECORD_PATH / "dinggong")) - result = MessageSegment.record(RECORD_PATH / "dinggong" / voice) - await poke_.send(result) - await poke_.send(voice.split("_")[1]) - logger.info( - f'USER {event.user_id} 戳了戳我 回复: {result} \n {voice.split("_")[1]}', - "戳一戳", - ) - else: - await poke_.send(MessageSegment("poke", {"qq": event.user_id})) diff --git a/zhenxun/plugins/quotations.py b/zhenxun/plugins/quotations.py deleted file mode 100644 index e213ee04..00000000 --- a/zhenxun/plugins/quotations.py +++ /dev/null @@ -1,32 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="一言二次元语录", - description="二次元语录给你力量", - usage=""" - usage: - 一言二次元语录 - 指令: - 语录/二次元 - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1").dict(), -) - -URL = "https://international.v1.hitokoto.cn/?c=a" - -_matcher = on_alconna(Alconna("语录"), aliases={"二次元"}, priority=5, block=True) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - data = (await AsyncHttpx.get(URL, timeout=5)).json() - result = f'{data["hitokoto"]}\t——{data["from"]}' - await MessageUtils.build_message(result).send() - logger.info(f" 发送语录:" + result, arparma.header_result, session=session) diff --git a/zhenxun/plugins/restart/__init__.py b/zhenxun/plugins/restart/__init__.py deleted file mode 100644 index eed6fa17..00000000 --- a/zhenxun/plugins/restart/__init__.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import platform -from pathlib import Path - -import nonebot -from nonebot import on_command -from nonebot.adapters import Bot -from nonebot.params import ArgStr -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -__plugin_meta__ = PluginMetadata( - name="重启", - description="执行脚本重启真寻", - usage=f""" - 重启 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -_matcher = on_command( - "重启", - permission=SUPERUSER, - rule=to_me(), - priority=1, - block=True, -) - -driver = nonebot.get_driver() - - -RESTART_MARK = Path() / "is_restart" - -RESTART_FILE = Path() / "restart.sh" - - -@_matcher.got( - "flag", - prompt=f"确定是否重启{NICKNAME}?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)", -) -async def _(bot: Bot, session: EventSession, flag: str = ArgStr("flag")): - if flag.lower() in ["true", "是", "好", "确定", "确定是"]: - await MessageUtils.build_message(f"开始重启{NICKNAME}..请稍等...").send() - with open(RESTART_MARK, "w", encoding="utf8") as f: - f.write(f"{bot.self_id} {session.id1}") - logger.info("开始重启真寻...", "重启", session=session) - if str(platform.system()).lower() == "windows": - import sys - - python = sys.executable - os.execl(python, python, *sys.argv) - else: - os.system("./restart.sh") - else: - await MessageUtils.build_message("已取消操作...").send() - - -@driver.on_bot_connect -async def _(bot: Bot): - if str(platform.system()).lower() != "windows": - if not RESTART_FILE.exists(): - with open(RESTART_FILE, "w", encoding="utf8") as f: - f.write( - f"pid=$(netstat -tunlp | grep " - + str(bot.config.port) - + " | awk '{print $7}')\n" - "pid=${pid%/*}\n" - "kill -9 $pid\n" - "sleep 3\n" - "python3 bot.py" - ) - os.system("chmod +x ./restart.sh") - logger.info( - "已自动生成 restart.sh(重启) 文件,请检查脚本是否与本地指令符合..." - ) - if RESTART_MARK.exists(): - with open(RESTART_MARK, "r", encoding="utf8") as f: - bot_id, session_id = f.read().split() - if bot := nonebot.get_bot(bot_id): - if target := PlatformUtils.get_target(bot, session_id): - await MessageUtils.build_message(f"{NICKNAME}已成功重启!").send( - target, bot=bot - ) - RESTART_MARK.unlink() diff --git a/zhenxun/plugins/roll.py b/zhenxun/plugins/roll.py deleted file mode 100644 index 7c953496..00000000 --- a/zhenxun/plugins/roll.py +++ /dev/null @@ -1,66 +0,0 @@ -import asyncio -import random - -from nonebot import on_command -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.depends import UserName -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="roll", - description="犹豫不决吗?那就让我帮你决定吧", - usage=""" - usage: - 随机数字 或 随机选择事件 - 指令: - roll: 随机 0-100 的数字 - roll *[文本]: 随机事件 - 示例:roll 吃饭 睡觉 打游戏 - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1").dict(), -) - - -_matcher = on_command("roll", priority=5, block=True) - - -@_matcher.handle() -async def _( - session: EventSession, - message: UniMsg, - user_name: str = UserName(), -): - text = message.extract_plain_text().strip().replace("roll", "", 1).split() - if not text: - await MessageUtils.build_message(f"roll: {random.randint(0, 100)}").finish( - reply_to=True - ) - await MessageUtils.build_message( - random.choice( - [ - "转动命运的齿轮,拨开眼前迷雾...", - f"启动吧,命运的水晶球,为{user_name}指引方向!", - "嗯哼,在此刻转动吧!命运!", - f"在此祈愿,请为{user_name}降下指引...", - ] - ) - ).send() - await asyncio.sleep(1) - random_text = random.choice(text) - await MessageUtils.build_message( - random.choice( - [ - f"让{NICKNAME}看看是什么结果!答案是:‘{random_text}’", - f"根据命运的指引,接下来{user_name} ‘{random_text}’ 会比较好", - f"祈愿被回应了!是 ‘{random_text}’!", - f"结束了,{user_name},命运之轮停在了 ‘{random_text}’!", - ] - ) - ).send(reply_to=True) - logger.info(f"发送roll:{text}", "roll", session=session) diff --git a/zhenxun/plugins/russian/__init__.py b/zhenxun/plugins/russian/__init__.py deleted file mode 100644 index ee25fdfd..00000000 --- a/zhenxun/plugins/russian/__init__.py +++ /dev/null @@ -1,201 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Arparma -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Match, UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.depends import UserName -from zhenxun.utils.message import MessageUtils - -from .command import ( - _accept_matcher, - _rank_matcher, - _record_matcher, - _refuse_matcher, - _russian_matcher, - _settlement_matcher, - _shoot_matcher, -) -from .data_source import Russian, russian_manage -from .model import RussianUser - -__plugin_meta__ = PluginMetadata( - name="俄罗斯轮盘", - description="虽然是运气游戏,但这可是战场啊少年", - usage=""" - 又到了决斗时刻 - 指令: - 装弹 [金额] [子弹数] ?[at]: 开启游戏,装填子弹,可选自定义金额,或邀请决斗对象 - 接受对决: 接受当前存在的对决 - 拒绝对决: 拒绝邀请的对决 - 开枪: 开出未知的一枪 - 结算: 强行结束当前比赛 (仅当一方未开枪超过30秒时可使用) - 我的战绩: 对,你的战绩 - 轮盘胜场排行/轮盘败场排行/轮盘欧洲人排行/轮盘慈善家排行/轮盘最高连胜排行/轮盘最高连败排行: 各种排行榜 - 示例:装弹 100 3 @sdd - * 注:同一时间群内只能有一场对决 * - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="群内小游戏", - configs=[ - RegisterConfig( - key="MAX_RUSSIAN_BET_GOLD", - value=1000, - help="俄罗斯轮盘最大赌注金额", - default_value=1000, - type=int, - ) - ], - ).dict(), -) - - -@_russian_matcher.handle() -async def _(money: int, num: Match[str], at_user: Match[alcAt]): - _russian_matcher.set_path_arg("money", money) - if num.available: - _russian_matcher.set_path_arg("num", num.result) - if at_user.available: - _russian_matcher.set_path_arg("at_user", at_user.result.target) - - -@_russian_matcher.got_path( - "num", prompt="请输入装填子弹的数量!(最多6颗,输入取消来取消装弹)" -) -async def _( - bot: Bot, - session: EventSession, - message: UniMsg, - arparma: Arparma, - money: int, - num: str, - at_user: Match[alcAt], - uname: str = UserName(), -): - gid = session.id2 - if message.extract_plain_text() == "取消": - await MessageUtils.build_message("已取消装弹...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if money <= 0: - await MessageUtils.build_message("赌注金额必须大于0!").finish(reply_to=True) - if num in ["取消", "算了"]: - await MessageUtils.build_message("已取消装弹...").finish() - if not num.isdigit(): - await MessageUtils.build_message("输入的子弹数必须是数字!").finish( - reply_to=True - ) - b_num = int(num) - if b_num < 0 or b_num > 6: - await MessageUtils.build_message("子弹数量必须在1-6之间!").finish(reply_to=True) - _at_user = at_user.result.target if at_user.available else None - rus = Russian( - at_user=_at_user, player1=(session.id1, uname), money=money, bullet_num=b_num - ) - result = await russian_manage.add_russian(bot, gid, rus) - await result.send() - logger.info( - f"添加俄罗斯轮盘 装弹: {b_num}, 金额: {money}", - arparma.header_result, - session=session, - ) - - -@_accept_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, uname: str = UserName()): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await russian_manage.accept(bot, gid, session.id1, uname) - await result.send() - logger.info(f"俄罗斯轮盘接受对决", arparma.header_result, session=session) - - -@_refuse_matcher.handle() -async def _(session: EventSession, arparma: Arparma, uname: str = UserName()): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = russian_manage.refuse(gid, session.id1, uname) - await result.send() - logger.info(f"俄罗斯轮盘拒绝对决", arparma.header_result, session=session) - - -@_settlement_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await russian_manage.settlement(gid, session.id1, session.platform) - await result.send() - logger.info(f"俄罗斯轮盘结算", arparma.header_result, session=session) - - -@_shoot_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, uname: str = UserName()): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result, settle = await russian_manage.shoot( - bot, gid, session.id1, uname, session.platform - ) - await result.send() - if settle: - await settle.send() - logger.info(f"俄罗斯轮盘开枪", arparma.header_result, session=session) - - -@_record_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - user, _ = await RussianUser.get_or_create(user_id=session.id1, group_id=gid) - await MessageUtils.build_message( - f"俄罗斯轮盘\n" - f"总胜利场次:{user.win_count}\n" - f"当前连胜:{user.winning_streak}\n" - f"最高连胜:{user.max_winning_streak}\n" - f"总失败场次:{user.fail_count}\n" - f"当前连败:{user.losing_streak}\n" - f"最高连败:{user.max_losing_streak}\n" - f"赚取金币:{user.make_money}\n" - f"输掉金币:{user.lose_money}", - ).send(reply_to=True) - logger.info(f"俄罗斯轮盘查看战绩", arparma.header_result, session=session) - - -@_rank_matcher.handle() -async def _(session: EventSession, arparma: Arparma, rank_type: str, num: int): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if 51 < num or num < 10: - num = 10 - result = await russian_manage.rank(session.id1, gid, rank_type, num) - if isinstance(result, str): - await MessageUtils.build_message(result).finish(reply_to=True) - result.show() - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"查看轮盘排行: {rank_type} 数量: {num}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/russian/command.py b/zhenxun/plugins/russian/command.py deleted file mode 100644 index a20dd2af..00000000 --- a/zhenxun/plugins/russian/command.py +++ /dev/null @@ -1,108 +0,0 @@ -from nonebot_plugin_alconna import Alconna, Args -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import on_alconna - -from zhenxun.utils.rules import ensure_group - -_russian_matcher = on_alconna( - Alconna( - "俄罗斯轮盘", - Args["money", int]["num?", str]["at_user?", alcAt], - ), - aliases={"装弹", "俄罗斯转盘"}, - rule=ensure_group, - priority=5, - block=True, -) - -_accept_matcher = on_alconna( - Alconna("接受对决"), - aliases={"接受决斗", "接受挑战"}, - rule=ensure_group, - priority=5, - block=True, -) - -_refuse_matcher = on_alconna( - Alconna("拒绝对决"), - aliases={"拒绝决斗", "拒绝挑战"}, - rule=ensure_group, - priority=5, - block=True, -) - -_shoot_matcher = on_alconna( - Alconna("开枪"), - aliases={"咔", "嘭", "嘣"}, - rule=ensure_group, - priority=5, - block=True, -) - -_settlement_matcher = on_alconna( - Alconna("结算"), - rule=ensure_group, - priority=5, - block=True, -) - -_record_matcher = on_alconna( - Alconna("我的战绩"), - rule=ensure_group, - priority=5, - block=True, -) - -_rank_matcher = on_alconna( - Alconna( - "russian-rank", - Args["rank_type", ["win", "lose", "a", "b", "max_win", "max_lose"]][ - "num?", int, 10 - ], - ), - rule=ensure_group, - priority=5, - block=True, -) - -_rank_matcher.shortcut( - r"轮盘胜场排行(?P\d*)", - command="russian-rank", - arguments=["win", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘败场排行(?P\d*)", - command="russian-rank", - arguments=["lose", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘欧洲人排行(?P\d*)", - command="russian-rank", - arguments=["a", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘慈善家排行(?P\d*)", - command="russian-rank", - arguments=["b", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘最高连胜排行(?P\d*)", - command="russian-rank", - arguments=["max_win", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘最高连败排行(?P\d*)", - command="russian-rank", - arguments=["max_lose", "{num}"], - prefix=True, -) diff --git a/zhenxun/plugins/russian/data_source.py b/zhenxun/plugins/russian/data_source.py deleted file mode 100644 index eb8381cb..00000000 --- a/zhenxun/plugins/russian/data_source.py +++ /dev/null @@ -1,535 +0,0 @@ -import random -import time -from datetime import datetime, timedelta - -from apscheduler.jobstores.base import JobLookupError -from nonebot.adapters import Bot -from nonebot_plugin_alconna import At, UniMessage -from nonebot_plugin_apscheduler import scheduler -from pydantic import BaseModel - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.models.user_console import UserConsole -from zhenxun.utils.enum import GoldHandle -from zhenxun.utils.exception import InsufficientGold -from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType, text2image -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from .model import RussianUser - -base_config = Config.get("russian") - - -class Russian(BaseModel): - - at_user: str | None - """指定决斗对象""" - player1: tuple[str, str] - """玩家1id, 昵称""" - player2: tuple[str, str] | None = None - """玩家2id, 昵称""" - money: int - """金额""" - bullet_num: int - """子弹数""" - bullet_arr: list[int] = [] - """子弹排列""" - bullet_index: int = 0 - """当前子弹下标""" - next_user: str = "" - """下一个开枪用户""" - time: float = time.time() - """创建时间""" - win_user: str | None = None - """胜利者""" - - -class RussianManage: - - def __init__(self) -> None: - self._data: dict[str, Russian] = {} - - def __check_is_timeout(self, group_id: str) -> bool: - """检查决斗是否超时 - - 参数: - group_id: 群组id - - 返回: - bool: 是否超时 - """ - if russian := self._data.get(group_id): - if russian.time + 30 < time.time(): - return True - return False - - def __random_bullet(self, num: int) -> list[int]: - """随机排列子弹 - - 参数: - num: 子弹数量 - - 返回: - list[int]: 子弹排列数组 - """ - bullet_list = [0, 0, 0, 0, 0, 0, 0] - for i in random.sample([0, 1, 2, 3, 4, 5, 6], num): - bullet_list[i] = 1 - return bullet_list - - def __remove_job(self, group_id: str): - """移除定时任务 - - 参数: - group_id: 群组id - """ - try: - scheduler.remove_job(f"russian_job_{group_id}") - except JobLookupError: - pass - - def __build_job( - self, bot: Bot, group_id: str, is_add: bool = False, platform: str | None = None - ): - """移除定时任务和构建新定时任务 - - 参数: - bot: Bot - group_id: 群组id - is_add: 是否添加新定时任务. - platform: 平台 - """ - self.__remove_job(group_id) - if is_add: - date = datetime.now() + timedelta(seconds=31) - scheduler.add_job( - self.__auto_end_game, - "date", - run_date=date.replace(microsecond=0), - id=f"russian_job_{group_id}", - args=[bot, group_id, platform], - ) - - async def __auto_end_game(self, bot: Bot, group_id: str, platform: str): - """自动结束对决 - - 参数: - bot: Bot - group_id: 群组id - platform: 平台 - """ - result = await self.settlement(group_id, None, platform) - if result: - await PlatformUtils.send_message(bot, None, group_id, result) - - async def add_russian(self, bot: Bot, group_id: str, rus: Russian) -> UniMessage: - """添加决斗 - - 参数: - bot: Bot - group_id: 群组id - rus: Russian - - 返回: - UniMessage: 返回消息 - """ - russian = self._data.get(group_id) - if russian: - if russian.time + 30 < time.time(): - if not russian.player2: - return MessageUtils.build_message( - f"现在是 {russian.player1[1]} 发起的对决, 请接受对决或等待决斗超时..." - ) - else: - return MessageUtils.build_message( - f"{russian.player1[1]} 和 {russian.player2[1]}的对决还未结束!" - ) - return MessageUtils.build_message( - f"现在是 {russian.player1[1]} 发起的对决\n请等待比赛结束后再开始下一轮..." - ) - max_money = base_config.get("MAX_RUSSIAN_BET_GOLD") - if rus.money > max_money: - return MessageUtils.build_message(f"太多了!单次金额不能超过{max_money}!") - user = await UserConsole.get_user(rus.player1[0]) - if user.gold < rus.money: - return MessageUtils.build_message("你没有足够的钱支撑起这场挑战") - rus.bullet_arr = self.__random_bullet(rus.bullet_num) - self._data[group_id] = rus - message_list: list[str | At] = [] - if rus.at_user: - user = await GroupInfoUser.get_or_none( - user_id=rus.at_user, group_id=group_id - ) - message_list = [ - f"{rus.player1[1]} 向", - At(flag="user", target=rus.at_user), - f"发起了决斗!请 {user.user_name if user else rus.at_user} 在30秒内回复‘接受对决’ or ‘拒绝对决’,超时此次决斗作废!", - ] - else: - message_list = [ - "若30秒内无人接受挑战则此次对决作废【首次游玩请at我发送 ’帮助俄罗斯轮盘‘ 来查看命令】" - ] - result = ( - "咔 " * rus.bullet_num - + f"装填完毕\n挑战金额:{rus.money}\n第一枪的概率为:{float(rus.bullet_num) / 7.0 * 100:.2f}%\n" - ) - - message_list.insert(0, result) - self.__build_job(bot, group_id, True) - return MessageUtils.build_message(message_list) # type: ignore - - async def accept( - self, bot: Bot, group_id: str, user_id: str, uname: str - ) -> UniMessage: - """接受对决 - - 参数: - bot: Bot - group_id: 群组id - user_id: 用户id - uname: 用户名称 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if russian.at_user and russian.at_user != user_id: - return MessageUtils.build_message("又不是找你决斗,你接受什么啊!气!") - if russian.player2: - return MessageUtils.build_message( - "当前决斗已被其他玩家接受!请等待下局对决!" - ) - if russian.player1[0] == user_id: - return MessageUtils.build_message("你发起的对决,你接受什么啊!气!") - user = await UserConsole.get_user(user_id) - if user.gold < russian.money: - return MessageUtils.build_message("你没有足够的钱来接受这场挑战...") - russian.player2 = (user_id, uname) - russian.next_user = russian.player1[0] - self.__build_job(bot, group_id, True) - return MessageUtils.build_message( - [ - "决斗已经开始!请", - At(flag="user", target=russian.player1[0]), - "先开枪!", - ] - ) - return MessageUtils.build_message( - "目前没有进行的决斗,请发送 装弹 开启决斗吧!" - ) - - def refuse(self, group_id: str, user_id: str, uname: str) -> UniMessage: - """拒绝决斗 - - 参数: - group_id: 群组id - user_id: 用户id - uname: 用户名称 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if russian.at_user: - if russian.at_user != user_id: - return MessageUtils.build_message( - "又不是找你决斗,你拒绝什么啊!气!" - ) - del self._data[group_id] - self.__remove_job(group_id) - return MessageUtils.build_message( - [ - At(flag="user", target=russian.player1[0]), - f"{uname}拒绝了你的对决!", - ] - ) - return MessageUtils.build_message("当前决斗并没有指定对手,无法拒绝哦!") - return MessageUtils.build_message( - "目前没有进行的决斗,请发送 装弹 开启决斗吧!" - ) - - async def shoot( - self, bot: Bot, group_id: str, user_id: str, uname: str, platform: str - ) -> tuple[UniMessage, UniMessage | None]: - """开枪 - - 参数: - bot: Bot - group_id: 群组id - user_id: 用户id - uname: 用户名称 - platform: 平台 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if not russian.player2: - return ( - MessageUtils.build_message("当前还没有玩家接受对决,无法开枪..."), - None, - ) - if user_id not in [russian.player1[0], russian.player2[0]]: - """非玩家1和玩家2发送开枪""" - return ( - MessageUtils.build_message( - random.choice( - [ - f"不要打扰 {russian.player1[1]} 和 {russian.player2[1]} 的决斗啊!", - f"给我好好做好一个观众!不然{NICKNAME}就要生气了", - f"不要捣乱啊baka{uname}!", - ] - ) - ), - None, - ) - if user_id != russian.next_user: - """相同玩家连续开枪""" - return ( - MessageUtils.build_message( - f"你的左轮不是连发的!该 {russian.player2[1]} 开枪了!" - ), - None, - ) - if russian.bullet_arr[russian.bullet_index] == 1: - """去世""" - result = MessageUtils.build_message( - random.choice( - [ - '"嘭!",你直接去世了', - "眼前一黑,你直接穿越到了异世界...(死亡)", - "终究还是你先走一步...", - ] - ) - ) - settle = await self.settlement(group_id, user_id, platform) - return result, settle - else: - """存活""" - p = ( - (russian.bullet_index + russian.bullet_num + 1) - / len(russian.bullet_arr) - * 100 - ) - result = ( - random.choice( - [ - "呼呼,没有爆裂的声响,你活了下来", - "虽然黑洞洞的枪口很恐怖,但好在没有子弹射出来,你活下来了", - '"咔",你没死,看来运气不错', - ] - ) - + f"\n下一枪中弹的概率: {p:.2f}%, 轮到 " - ) - next_user = ( - russian.player2[0] - if russian.next_user == russian.player1[0] - else russian.player1[0] - ) - russian.next_user = next_user - russian.bullet_index += 1 - self.__build_job(bot, group_id, True) - return ( - MessageUtils.build_message( - [result, At(flag="user", target=next_user), " 了!"] - ), - None, - ) - return ( - MessageUtils.build_message("目前没有进行的决斗,请发送 装弹 开启决斗吧!"), - None, - ) - - async def settlement( - self, group_id: str, user_id: str | None, platform: str | None = None - ) -> UniMessage: - """结算 - - 参数: - group_id: 群组id - user_id: 用户id - platform: 平台 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if not russian.player2: - if self.__check_is_timeout(group_id): - del self._data[group_id] - return MessageUtils.build_message( - "规定时间内还未有人接受决斗,当前决斗过期..." - ) - return MessageUtils.build_message("决斗还未开始,,无法结算哦...") - if user_id and user_id not in [russian.player1[0], russian.player2[0]]: - return MessageUtils.build_message(f"吃瓜群众不要捣乱!黄牌警告!") - if not self.__check_is_timeout(group_id): - return MessageUtils.build_message( - f"{russian.player1[1]} 和 {russian.player2[1]} 比赛并未超时,请继续比赛..." - ) - win_user = None - lose_user = None - if win_user: - russian.next_user = ( - russian.player1[0] - if win_user == russian.player2[0] - else russian.player2[0] - ) - if russian.next_user != russian.player1[0]: - win_user = russian.player1 - lose_user = russian.player2 - else: - win_user = russian.player2 - lose_user = russian.player1 - if win_user and lose_user: - rand = 0 - if russian.money > 10: - rand = random.randint(0, 5) - fee = int(russian.money * float(rand) / 100) - fee = 1 if fee < 1 and rand != 0 else fee - else: - fee = 0 - winner = await RussianUser.add_count(win_user[0], group_id, "win") - loser = await RussianUser.add_count(lose_user[0], group_id, "lose") - await RussianUser.money( - win_user[0], group_id, "win", russian.money - fee - ) - await RussianUser.money(lose_user[0], group_id, "lose", russian.money) - await UserConsole.add_gold( - win_user[0], russian.money - fee, "russian", platform - ) - try: - await UserConsole.reduce_gold( - lose_user[0], - russian.money, - GoldHandle.PLUGIN, - "russian", - platform, - ) - except InsufficientGold: - if u := await UserConsole.get_user(lose_user[0]): - u.gold = 0 - await u.save(update_fields=["gold"]) - result = [ - "这场决斗是 ", - At(flag="user", target=win_user[0]), - " 胜利了!", - ] - image = await text2image( - f"结算:\n" - f"\t胜者:{win_user[1]}\n" - f"\t赢取金币:{russian.money - fee}\n" - f"\t累计胜场:{winner.win_count}\n" - f"\t累计赚取金币:{winner.make_money}\n" - f"-------------------\n" - f"\t败者:{lose_user[1]}\n" - f"\t输掉金币:{russian.money}\n" - f"\t累计败场:{loser.fail_count}\n" - f"\t累计输掉金币:{loser.lose_money}\n" - f"-------------------\n" - f"哼哼,{NICKNAME}从中收取了 {float(rand)}%({fee}金币) 作为手续费!\n" - f"子弹排列:{russian.bullet_arr}", - padding=10, - color="#f9f6f2", - ) - self.__remove_job(group_id) - result.append(image) - del self._data[group_id] - return MessageUtils.build_message(result) - return MessageUtils.build_message("赢家和输家获取错误...") - return MessageUtils.build_message("比赛并没有开始...无法结算...") - - async def __get_x_index(self, users: list[RussianUser], group_id: str): - uid_list = [u.user_id for u in users] - group_user_list = await GroupInfoUser.filter( - user_id__in=uid_list, group_id=group_id - ).all() - group_user = {gu.user_id: gu.user_name for gu in group_user_list} - data = [] - for uid in uid_list: - if uid in group_user: - data.append(group_user[uid]) - else: - data.append(uid) - return data - - async def rank( - self, user_id: str, group_id: str, rank_type: str, num: int - ) -> BuildImage | str: - x_index = [] - data = [] - title = "" - x_name = "" - if rank_type == "win": - users = ( - await RussianUser.filter(group_id=group_id, win_count__not=0) - .order_by("win_count") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.win_count for u in users] - title = "胜场排行" - x_name = "场次" - if rank_type == "lose": - users = ( - await RussianUser.filter(group_id=group_id, fail_count__not=0) - .order_by("fail_count") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.fail_count for u in users] - title = "败场排行" - x_name = "场次" - if rank_type == "a": - users = ( - await RussianUser.filter(group_id=group_id, make_money__not=0) - .order_by("make_money") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.make_money for u in users] - title = "欧洲人排行" - x_name = "金币" - if rank_type == "b": - users = ( - await RussianUser.filter(group_id=group_id, lose_money__not=0) - .order_by("lose_money") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.lose_money for u in users] - title = "慈善家排行" - x_name = "金币" - if rank_type == "max_win": - users = ( - await RussianUser.filter(group_id=group_id, max_winning_streak__not=0) - .order_by("max_winning_streak") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.max_winning_streak for u in users] - title = "最高连胜排行" - x_name = "场次" - if rank_type == "max_lose": - users = ( - await RussianUser.filter(group_id=group_id, max_losing_streak__not=0) - .order_by("max_losing_streak") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.max_losing_streak for u in users] - title = "最高连败排行" - x_name = "场次" - if not data: - return "当前数据为空..." - mat = BuildMat(MatType.BARH) - mat.x_index = x_index - mat.data = data # type: ignore - mat.title = title - mat.x_name = x_name - return await mat.build() - - -russian_manage = RussianManage() diff --git a/zhenxun/plugins/russian/model.py b/zhenxun/plugins/russian/model.py deleted file mode 100644 index 0fab9298..00000000 --- a/zhenxun/plugins/russian/model.py +++ /dev/null @@ -1,107 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class RussianUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - win_count = fields.IntField(default=0) - """胜利次数""" - fail_count = fields.IntField(default=0) - """失败次数""" - make_money = fields.IntField(default=0) - """赢得金币""" - lose_money = fields.IntField(default=0) - """输得金币""" - winning_streak = fields.IntField(default=0) - """当前连胜""" - losing_streak = fields.IntField(default=0) - """当前连败""" - max_winning_streak = fields.IntField(default=0) - """最大连胜""" - max_losing_streak = fields.IntField(default=0) - """最大连败""" - - class Meta: - table = "russian_users" - table_description = "俄罗斯轮盘数据表" - unique_together = ("user_id", "group_id") - - @classmethod - async def add_count(cls, user_id: str, group_id: str, itype: str): - """添加用户输赢次数 - - 说明: - user_id: 用户id - group_id: 群号 - itype: 输或赢 'win' or 'lose' - """ - user, _ = await cls.get_or_create(user_id=user_id, group_id=group_id) - if itype == "win": - _max = ( - user.max_winning_streak - if user.max_winning_streak > user.winning_streak + 1 - else user.winning_streak + 1 - ) - user.win_count = user.win_count + 1 - user.winning_streak = user.winning_streak + 1 - user.losing_streak = 0 - user.max_winning_streak = _max - await user.save( - update_fields=[ - "win_count", - "winning_streak", - "losing_streak", - "max_winning_streak", - ] - ) - elif itype == "lose": - _max = ( - user.max_losing_streak - if user.max_losing_streak > user.losing_streak + 1 - else user.losing_streak + 1 - ) - user.fail_count = user.fail_count + 1 - user.losing_streak = user.losing_streak + 1 - user.winning_streak = 0 - user.max_losing_streak = _max - await user.save( - update_fields=[ - "fail_count", - "winning_streak", - "losing_streak", - "max_losing_streak", - ] - ) - return user - - @classmethod - async def money(cls, user_id: str, group_id: str, itype: str, count: int): - """添加用户输赢金钱 - - 参数: - user_id: 用户id - group_id: 群号 - itype: 输或赢 'win' or 'lose' - count: 金钱数量 - """ - user, _ = await cls.get_or_create(user_id=str(user_id), group_id=group_id) - if itype == "win": - user.make_money = user.make_money + count - elif itype == "lose": - user.lose_money = user.lose_money + count - await user.save(update_fields=["make_money", "lose_money"]) - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE russian_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE russian_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE russian_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/search_anime/__init__.py b/zhenxun/plugins/search_anime/__init__.py deleted file mode 100644 index d12ad03e..00000000 --- a/zhenxun/plugins/search_anime/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import from_anime_get_info - -__plugin_meta__ = PluginMetadata( - name="搜番", - description="找不到想看的动漫吗?", - usage=""" - 搜索动漫资源 - 指令: - 搜番 [番剧名称或者关键词] - 示例:搜番 刀剑神域 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - limits=[BaseBlock(result="搜索还未完成,不要重复触发!")], - configs=[ - RegisterConfig( - key="SEARCH_ANIME_MAX_INFO", - value=20, - help="搜索动漫返回的最大数量", - default_value=20, - type=int, - ) - ], - ).dict(), -) - -_matcher = on_alconna(Alconna("搜番", Args["name?", str]), priority=5, block=True) - - -@_matcher.handle() -async def _(name: Match[str]): - if name.available: - _matcher.set_path_arg("name", name.result) - - -@_matcher.got_path("name", prompt="是不是少了番名?") -async def _(session: EventSession, arparma: Arparma, name: str): - gid = session.id3 or session.id2 - await MessageUtils.build_message(f"开始搜番 {name}...").send() - anime_report = await from_anime_get_info( - name, - Config.get_config("search_anime", "SEARCH_ANIME_MAX_INFO"), - ) - if anime_report: - if isinstance(anime_report, str): - await MessageUtils.build_message(anime_report).finish() - await MessageUtils.build_message("\n\n".join(anime_report)).send() - logger.info( - f"搜索番剧 {name} 成功: {anime_report}", - arparma.header_result, - session=session, - ) - else: - logger.info(f"未找到番剧 {name}...") - await MessageUtils.build_message( - f"未找到番剧 {name}(也有可能是超时,再尝试一下?)" - ).send() diff --git a/zhenxun/plugins/search_anime/data_source.py b/zhenxun/plugins/search_anime/data_source.py deleted file mode 100644 index 59d0ac61..00000000 --- a/zhenxun/plugins/search_anime/data_source.py +++ /dev/null @@ -1,53 +0,0 @@ -import time -from urllib import parse - -import feedparser -from lxml import etree - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - - -async def from_anime_get_info(key_word: str, max_: int) -> str | list[str]: - s_time = time.time() - url = "https://share.dmhy.org/topics/rss/rss.xml?keyword=" + parse.quote(key_word) - try: - repass = await get_repass(url, max_) - except Exception as e: - logger.error(f"发生了一些错误 {type(e)}", e=e) - return "发生了一些错误!" - repass.insert(0, f"搜索 {key_word} 结果(耗时 {int(time.time() - s_time)} 秒):\n") - return repass - - -async def get_repass(url: str, max_: int) -> list[str]: - put_line = [] - text = (await AsyncHttpx.get(url)).text - d = feedparser.parse(text) - max_ = ( - max_ - if max_ < len([e.link for e in d.entries]) - else len([e.link for e in d.entries]) - ) - url_list = [e.link for e in d.entries][:max_] - for u in url_list: - try: - text = (await AsyncHttpx.get(u)).text - html = etree.HTML(text) # type: ignore - magent = html.xpath('.//a[@id="a_magnet"]/text()')[0] - title = html.xpath(".//h3/text()")[0] - item = html.xpath('//div[@class="info resource-info right"]/ul/li') - class_a = ( - item[0] - .xpath("string(.)")[5:] - .strip() - .replace("\xa0", "") - .replace("\t", "") - ) - size = item[3].xpath("string(.)")[5:].strip() - put_line.append( - "【{}】| {}\n【{}】| {}".format(class_a, title, size, magent) - ) - except Exception as e: - logger.error(f"搜番发生错误", e=e) - return put_line diff --git a/zhenxun/plugins/search_buff_skin_price/__init__.py b/zhenxun/plugins/search_buff_skin_price/__init__.py deleted file mode 100644 index da224aaf..00000000 --- a/zhenxun/plugins/search_buff_skin_price/__init__.py +++ /dev/null @@ -1,104 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import get_price, update_buff_cookie - -__plugin_meta__ = PluginMetadata( - name="BUFF查询皮肤", - description="BUFF皮肤底价查询", - usage=""" - 在线实时获取BUFF指定皮肤所有磨损底价 - 指令: - 查询皮肤 [枪械名] [皮肤名称] - 示例:查询皮肤 ak47 二西莫夫 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - limits=[BaseBlock(result="您有皮肤正在搜索,请稍等...")], - configs=[ - RegisterConfig( - key="BUFF_PROXY", - value=None, - help="BUFF代理,有些厂ip可能被屏蔽", - ), - RegisterConfig( - key="COOKIE", - value=None, - help="BUFF的账号cookie", - ), - ], - ).dict(), -) - - -_matcher = on_alconna( - Alconna("查询皮肤", Args["name", str]["skin", str]), - aliases={"皮肤查询"}, - priority=5, - block=True, -) - -_cookie_matcher = on_alconna( - Alconna("设置cookie", Args["cookie", str]), - rule=to_me(), - permission=SUPERUSER, - priority=1, -) - - -@_matcher.handle() -async def _(name: Match[str], skin: Match[str]): - if name.available: - _matcher.set_path_arg("name", name.result) - if skin.available: - _matcher.set_path_arg("skin", skin.result) - - -@_matcher.got_path("name", prompt="要查询什么武器呢?") -@_matcher.got_path("skin", prompt="要查询该武器的什么皮肤呢?") -async def arg_handle( - session: EventSession, - arparma: Arparma, - name: str, - skin: str, -): - if name in ["算了", "取消"] or skin in ["算了", "取消"]: - await MessageUtils.build_message("已取消操作...").finish() - result = "" - if name in ["ak", "ak47"]: - name = "ak-47" - name = name + " | " + skin - status_code = -1 - try: - result, status_code = await get_price(name) - except FileNotFoundError: - await MessageUtils.build_message( - f'请先对{NICKNAME}说"设置cookie"来设置cookie!' - ).send(at_sender=True) - if status_code in [996, 997, 998]: - await MessageUtils.build_message(result).finish() - if result: - logger.info(f"查询皮肤: {name}", arparma.header_result, session=session) - await MessageUtils.build_message(result).finish() - else: - logger.info( - f" 查询皮肤:{name} 没有查询到", arparma.header_result, session=session - ) - await MessageUtils.build_message("没有查询到哦,请检查格式吧").send() - - -@_cookie_matcher.handle() -async def _(session: EventSession, arparma: Arparma, cookie: str): - result = update_buff_cookie(cookie) - await MessageUtils.build_message(result).send(at_sender=True) - logger.info("更新BUFF COOKIE", arparma.header_result, session=session) diff --git a/zhenxun/plugins/search_buff_skin_price/data_source.py b/zhenxun/plugins/search_buff_skin_price/data_source.py deleted file mode 100644 index 8dbe6a59..00000000 --- a/zhenxun/plugins/search_buff_skin_price/data_source.py +++ /dev/null @@ -1,62 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from zhenxun.configs.config import Config -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - -url = "https://buff.163.com/api/market/goods" - - -async def get_price(d_name: str) -> tuple[str, int]: - """查看皮肤价格 - - 参数: - d_name: 武器皮肤,如:awp 二西莫夫 - - 返回: - tuple[str, int]: 查询数据和状态 - """ - cookie = {"session": Config.get_config("search_buff_skin_price", "COOKIE")} - name_list = [] - price_list = [] - parameter = {"game": "csgo", "page_num": "1", "search": d_name} - try: - response = await AsyncHttpx.get( - url, - proxy=Config.get_config("search_buff_skin_price", "BUFF_PROXY"), - params=parameter, - cookies=cookie, - ) - if response.status_code == 200: - try: - if response.text.find("Login Required") != -1: - return "BUFF登录被重置,请联系管理员重新登入", 996 - data = response.json()["data"] - total_page = data["total_page"] - data = data["items"] - for _ in range(total_page): - for i in range(len(data)): - name = data[i]["name"] - price = data[i]["sell_reference_price"] - name_list.append(name) - price_list.append(price) - except Exception as e: - logger.error(f"BUFF查询皮肤发生错误 {type(e)}:{e}") - return "没有查询到...", 998 - else: - return "访问失败!", response.status_code - except TimeoutError: - return "访问超时! 请重试或稍后再试!", 997 - result = f"皮肤: {d_name}({len(name_list)})\n" - for i in range(len(name_list)): - result += name_list[i] + ": " + price_list[i] + "\n" - return result[:-1], 999 - - -def update_buff_cookie(cookie: str) -> str: - Config.set_config("search_buff_skin_price", "COOKIE", cookie) - return "更新cookie成功" - - -if __name__ == "__main__": - print(get_price("awp 二西莫夫")) diff --git a/zhenxun/plugins/search_image/__init__.py b/zhenxun/plugins/search_image/__init__.py deleted file mode 100644 index 38e86de0..00000000 --- a/zhenxun/plugins/search_image/__init__.py +++ /dev/null @@ -1,94 +0,0 @@ -from pathlib import Path - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma -from nonebot_plugin_alconna import Image as alcImg -from nonebot_plugin_alconna import Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from .saucenao import get_saucenao_image - -__plugin_meta__ = PluginMetadata( - name="识图", - description="以图搜图,看破本源", - usage=""" - 识别图片 [二次元图片] - 指令: - 识图 [图片] - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - configs=[ - RegisterConfig( - key="MAX_FIND_IMAGE_COUNT", - value=3, - help="搜索动漫返回的最大数量", - default_value=3, - type=int, - ), - RegisterConfig( - key="API_KEY", - value=None, - help="Saucenao的API_KEY,通过 https://saucenao.com/user.php?page=search-api 注册获取", - ), - ], - ).dict(), -) - - -_matcher = on_alconna( - Alconna("识图", Args["mode?", str]["image?", alcImg]), block=True, priority=5 -) - - -async def get_image_info(mod: str, url: str) -> str | list[str | Path] | None: - if mod == "saucenao": - return await get_saucenao_image(url) - - -@_matcher.handle() -async def _(mode: Match[str], image: Match[alcImg]): - if mode.available: - _matcher.set_path_arg("mode", mode.result) - else: - _matcher.set_path_arg("mode", "saucenao") - if image.available: - _matcher.set_path_arg("image", image.result) - - -@_matcher.got_path("image", prompt="图来!") -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - mode: str, - image: alcImg, -): - gid = session.id3 or session.id2 - if not image.url: - await MessageUtils.build_message("图片url为空...").finish() - await MessageUtils.build_message("开始处理图片...").send() - info_list = await get_image_info(mode, image.url) - if isinstance(info_list, str): - await MessageUtils.build_message(info_list).finish(at_sender=True) - if not info_list: - await MessageUtils.build_message("未查询到...").finish() - platform = PlatformUtils.get_platform(bot) - if "qq" == platform and gid: - forward = MessageUtils.template2forward(info_list[1:], bot.self_id) # type: ignore - await bot.send_group_forward_msg( - group_id=int(gid), - messages=forward, # type: ignore - ) - else: - for info in info_list[1:]: - await MessageUtils.build_message(info).send() - logger.info(f" 识图: {image.url}", arparma.header_result, session=session) diff --git a/zhenxun/plugins/search_image/saucenao.py b/zhenxun/plugins/search_image/saucenao.py deleted file mode 100644 index eab44fab..00000000 --- a/zhenxun/plugins/search_image/saucenao.py +++ /dev/null @@ -1,62 +0,0 @@ -import random -from pathlib import Path - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services import logger -from zhenxun.utils.http_utils import AsyncHttpx - -API_URL_SAUCENAO = "https://saucenao.com/search.php" -API_URL_ASCII2D = "https://ascii2d.net/search/url/" -API_URL_IQDB = "https://iqdb.org/" - - -async def get_saucenao_image(url: str) -> str | list[str | Path]: - """获取图片源 - - 参数: - url: 图片url - - 返回: - str | list[Image | Text]: 识图数据 - """ - api_key = Config.get_config("search_image", "API_KEY") - if not api_key: - return "Saucenao 缺失API_KEY!" - params = { - "output_type": 2, - "api_key": api_key, - "testmode": 1, - "numres": 6, - "db": 999, - "url": url, - } - data = (await AsyncHttpx.post(API_URL_SAUCENAO, params=params)).json() - if data["header"]["status"] != 0: - return f"Saucenao识图失败..status:{data['header']['status']}" - data = data["results"] - data = ( - data - if len(data) < Config.get_config("search_image", "MAX_FIND_IMAGE_COUNT") - else data[: Config.get_config("search_image", "MAX_FIND_IMAGE_COUNT")] - ) - msg_list = [] - index = random.randint(0, 10000) - if await AsyncHttpx.download_file(url, TEMP_PATH / f"saucenao_search_{index}.jpg"): - msg_list.append(TEMP_PATH / f"saucenao_search_{index}.jpg") - for info in data: - try: - similarity = info["header"]["similarity"] - tmp = f"相似度:{similarity}%\n" - for x in info["data"].keys(): - if x != "ext_urls": - tmp += f"{x}:{info['data'][x]}\n" - try: - if "source" not in info["data"].keys(): - tmp += f'source:{info["data"]["ext_urls"][0]}\n' - except KeyError: - tmp += f'source:{info["header"]["thumbnail"]}\n' - msg_list.append(tmp[:-1]) - except Exception as e: - logger.warning(f"识图获取图片信息发生错误", e=e) - return msg_list diff --git a/zhenxun/plugins/send_setu_/__init__.py b/zhenxun/plugins/send_setu_/__init__.py deleted file mode 100644 index eb35e275..00000000 --- a/zhenxun/plugins/send_setu_/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/send_setu_/_model.py b/zhenxun/plugins/send_setu_/_model.py deleted file mode 100644 index 865af7d1..00000000 --- a/zhenxun/plugins/send_setu_/_model.py +++ /dev/null @@ -1,87 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random -from tortoise.expressions import Q -from typing_extensions import Self - -from zhenxun.services.db_context import Model - - -class Setu(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - local_id = fields.IntField() - """本地存储下标""" - title = fields.CharField(255) - """标题""" - author = fields.CharField(255) - """作者""" - pid = fields.BigIntField() - """pid""" - img_hash = fields.TextField() - """图片hash""" - img_url = fields.CharField(255) - """pixiv url链接""" - is_r18 = fields.BooleanField() - """是否r18""" - tags = fields.TextField() - """tags""" - - class Meta: - table = "setu" - table_description = "色图数据表" - unique_together = ("pid", "img_url") - - @classmethod - async def query_image( - cls, - local_id: int | None = None, - tags: list[str] | None = None, - r18: bool = False, - limit: int = 50, - ) -> list[Self] | Self | None: - """通过tag查找色图 - - 参数: - local_id: 本地色图 id - tags: tags - r18: 是否 r18,0:非r18 1:r18 2:混合 - limit: 获取数量 - - 返回: - list[Self] | Self | None: 色图数据 - """ - if local_id: - return await cls.filter(is_r18=r18, local_id=local_id).first() - query = cls.filter(is_r18=r18) - if tags: - for tag in tags: - query = query.filter( - Q(tags__contains=tag) - | Q(title__contains=tag) - | Q(author__contains=tag) - ) - query = query.annotate(rand=Random()).limit(limit) - return await query.all() - - @classmethod - async def delete_image(cls, pid: int, img_url: str) -> int: - """删除图片并替换 - - 参数: - pid: 图片pid - - 返回: - int: 删除返回的本地id - """ - print(pid) - return_id = -1 - if query := await cls.get_or_none(pid=pid, img_url=img_url): - num = await cls.filter(is_r18=query.is_r18).count() - last_image = await cls.get_or_none(is_r18=query.is_r18, local_id=num - 1) - if last_image: - return_id = last_image.local_id - last_image.local_id = query.local_id - await last_image.save(update_fields=["local_id"]) - await query.delete() - return return_id diff --git a/zhenxun/plugins/send_setu_/send_setu/__init__.py b/zhenxun/plugins/send_setu_/send_setu/__init__.py deleted file mode 100644 index 3dab91f6..00000000 --- a/zhenxun/plugins/send_setu_/send_setu/__init__.py +++ /dev/null @@ -1,243 +0,0 @@ -import random -from typing import Tuple - -from nonebot.adapters import Bot -from nonebot.matcher import Matcher -from nonebot.message import run_postprocessor -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import NICKNAME -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig -from zhenxun.models.sign_user import SignUser -from zhenxun.models.user_console import UserConsole -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.withdraw_manage import WithdrawManager - -from ._data_source import SetuManage, base_config - -__plugin_meta__ = PluginMetadata( - name="色图", - description="不要小看涩图啊混蛋!", - usage=""" - 搜索 lolicon 图库,每日色图time... - 多个tag使用#连接 - 指令: - 色图: 随机色图 - 色图 -r: 随机在线r18涩图 - 色图 -id [id]: 本地指定id色图 - 色图 *[tags]: 在线搜索指定tag色图 - 色图 *[tags] -r: 同上, r18色图 - [1-9]张涩图: 本地随机色图连发 - [1-9]张[tags]的涩图: 在线搜索指定tag色图连发 - 示例:色图 萝莉|少女#白丝|黑丝 - 示例:色图 萝莉#猫娘 - 注: - tag至多取前20项,| 为或,萝莉|少女=萝莉或者少女 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="来点好康的", - limits=[PluginCdBlock(result="您冲的太快了,请稍后再冲.")], - configs=[ - RegisterConfig( - key="WITHDRAW_SETU_MESSAGE", - value=(0, 1), - help="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], - ), - RegisterConfig( - key="ONLY_USE_LOCAL_SETU", - value=False, - help="仅仅使用本地色图,不在线搜索", - default_value=False, - type=bool, - ), - RegisterConfig( - key="INITIAL_SETU_PROBABILITY", - value=0.7, - help="初始色图概率,总概率 = 初始色图概率 + 好感度", - default_value=0.7, - type=float, - ), - RegisterConfig( - key="DOWNLOAD_SETU", - value=True, - help="是否存储下载的色图,使用本地色图可以加快图片发送速度", - default_value=True, - type=float, - ), - RegisterConfig( - key="TIMEOUT", - value=10, - help="色图下载超时限制(秒)", - default_value=10, - type=int, - ), - RegisterConfig( - key="SHOW_INFO", - value=True, - help="是否显示色图的基本信息,如PID等", - default_value=True, - type=bool, - ), - RegisterConfig( - key="ALLOW_GROUP_R18", - value=False, - help="在群聊中启用R18权限", - default_value=False, - type=bool, - ), - RegisterConfig( - key="MAX_ONCE_NUM2FORWARD", - value=None, - help="单次发送的图片数量达到指定值时转发为合并消息", - default_value=None, - type=int, - ), - RegisterConfig( - key="MAX_ONCE_NUM", - value=10, - help="单次发送图片数量限制", - default_value=10, - type=int, - ), - RegisterConfig( - module="pixiv", - key="PIXIV_NGINX_URL", - value="i.pixiv.re", - help="Pixiv反向代理", - default_value="i.pixiv.re", - ), - ], - ).dict(), -) - - -@run_postprocessor -async def _( - matcher: Matcher, - exception: Exception | None, - session: EventSession, -): - if matcher.plugin_name == "send_setu": - # 添加数据至数据库 - try: - await SetuManage.save_to_database() - logger.info("色图数据自动存储数据库成功...") - except Exception: - pass - - -_matcher = on_alconna( - Alconna( - "色图", - Args["tags?", str], - Option("-n", Args["num", int, 1], help_text="数量"), - Option("-id", Args["local_id", int], help_text="本地id"), - Option("-r", action=store_true, help_text="r18"), - ), - aliases={"涩图", "不够色", "来一发", "再来点"}, - priority=5, - block=True, -) - -_matcher.shortcut( - r".*?(?P\d*)[份|发|张|个|次|点](?P.*)[瑟|色|涩]图.*?", - command="色图", - arguments=["{tags}", "-n", "{num}"], - prefix=True, -) - - -@_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - num: Match[int], - tags: Match[str], - local_id: Match[int], -): - _tags = tags.result.split("#") if tags.available else None - if _tags and NICKNAME in _tags: - await MessageUtils.build_message( - "咳咳咳,虽然我很可爱,但是我木有自己的色图~~~有的话记得发我一份呀" - ).finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - gid = session.id3 or session.id2 - user_console = await UserConsole.get_user(session.id1, session.platform) - user, _ = await SignUser.get_or_create( - user_id=session.id1, - defaults={"user_console": user_console, "platform": session.platform}, - ) - if session.id1 not in bot.config.superusers: - """超级用户跳过罗翔""" - if result := SetuManage.get_luo(float(user.impression)): - await result.finish() - is_r18 = arparma.find("r") - _num = num.result if num.available else 1 - if is_r18 and gid: - """群聊中禁止查看r18""" - if not base_config.get("ALLOW_GROUP_R18"): - await MessageUtils.build_message( - random.choice( - [ - "这种不好意思的东西怎么可能给这么多人看啦", - "羞羞脸!给我滚出克私聊!", - "变态变态变态变态大变态!", - ] - ) - ).finish() - if local_id.available: - """指定id""" - result = await SetuManage.get_setu(local_id=local_id.result) - if isinstance(result, str): - await MessageUtils.build_message(result).finish(reply_to=True) - await result[0].finish() - result_list = await SetuManage.get_setu(tags=_tags, num=_num, is_r18=is_r18) - if isinstance(result_list, str): - await MessageUtils.build_message(result_list).finish(reply_to=True) - max_once_num2forward = base_config.get("MAX_ONCE_NUM2FORWARD") - platform = PlatformUtils.get_platform(bot) - if ( - "qq" == platform - and gid - and max_once_num2forward - and len(result_list) >= max_once_num2forward - ): - logger.debug("使用合并转发转发色图数据", arparma.header_result, session=session) - forward = MessageUtils.template2forward(result_list, bot.self_id) # type: ignore - await bot.send_group_forward_msg( - group_id=int(gid), - messages=forward, # type: ignore - ) - else: - for result in result_list: - logger.info(f"发送色图 {result}", arparma.header_result, session=session) - receipt = await result.send() - if receipt: - message_id = receipt.msg_ids[0]["message_id"] - await WithdrawManager.withdraw_message( - bot, - message_id, - base_config.get("WITHDRAW_SETU_MESSAGE"), - session, - ) - logger.info( - f"调用发送 {num}张 色图 tags: {_tags}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/send_setu_/send_setu/_data_source.py b/zhenxun/plugins/send_setu_/send_setu/_data_source.py deleted file mode 100644 index 6bac3d22..00000000 --- a/zhenxun/plugins/send_setu_/send_setu/_data_source.py +++ /dev/null @@ -1,360 +0,0 @@ -import os -import random -from pathlib import Path - -from asyncpg import UniqueViolationError -from nonebot_plugin_alconna import UniMessage - -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import compressed_image -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import change_img_md5, change_pixiv_image_links - -from .._model import Setu - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net", -} - -base_config = Config.get("send_setu") - - -class SetuManage: - - URL = "https://api.lolicon.app/setu/v2" - save_data = [] - - @classmethod - async def get_setu( - cls, - *, - local_id: int | None = None, - num: int = 10, - tags: list[str] | None = None, - is_r18: bool = False, - ) -> list[UniMessage] | str: - """获取色图 - - 参数: - local_id: 指定图片id - num: 数量 - tags: 标签 - is_r18: 是否r18 - - 返回: - list[MessageFactory] | str: 色图数据列表或消息 - - """ - result_list = [] - if local_id: - """本地id""" - data_list = await cls.get_setu_list(local_id=local_id) - if isinstance(data_list, str): - return data_list - file = await cls.get_image(data_list[0]) - if isinstance(file, str): - return file - return [cls.init_image_message(file, data_list[0])] - if base_config.get("ONLY_USE_LOCAL_SETU"): - """仅使用本地色图""" - flag = False - data_list = await cls.get_setu_list(tags=tags, is_r18=is_r18) - if isinstance(data_list, str): - return data_list - cls.save_data = data_list - if num > len(data_list): - num = len(data_list) - flag = True - setu_list = random.sample(data_list, num) - for setu in setu_list: - base_path = None - if setu.is_r18: - base_path = IMAGE_PATH / "_r18" - else: - base_path = IMAGE_PATH / "_setu" - file_path = base_path / f"{setu.local_id}.jpg" - if not file_path.exists(): - return f"本地色图Id: {setu.local_id} 不存在..." - result_list.append(cls.init_image_message(file_path, setu)) - if flag: - result_list.append( - MessageUtils.build_message("坏了,已经没图了,被榨干了!") - ) - return result_list - data_list = await cls.search_lolicon(tags, num, is_r18) - if isinstance(data_list, str): - """搜索失败, 从本地数据库中搜索""" - data_list = await cls.get_setu_list(tags=tags, is_r18=is_r18) - if isinstance(data_list, str): - return data_list - if not data_list: - return "没找到符合条件的色图..." - cls.save_data = data_list - flag = False - if num > len(data_list): - num = len(data_list) - flag = True - for setu in data_list: - file = await cls.get_image(setu) - if isinstance(file, str): - result_list.append(MessageUtils.build_message(file)) - continue - result_list.append(cls.init_image_message(file, setu)) - if not result_list: - return "没找到符合条件的色图..." - if flag: - result_list.append( - MessageUtils.build_message("坏了,已经没图了,被榨干了!") - ) - return result_list - - @classmethod - def init_image_message(cls, file: Path, setu: Setu) -> UniMessage: - """初始化图片发送消息 - - 参数: - file: 图片路径 - setu: Setu - - 返回: - UniMessage: 发送消息内容 - """ - data_list = [] - if base_config.get("SHOW_INFO"): - data_list.append( - f"id:{setu.local_id or ''}\n" - f"title:{setu.title}\n" - f"author:{setu.author}\n" - f"PID:{setu.pid}\n" - ) - data_list.append(file) - return MessageUtils.build_message(data_list) - - @classmethod - async def get_setu_list( - cls, - *, - local_id: int | None = None, - tags: list[str] | None = None, - is_r18: bool = False, - ) -> list[Setu] | str: - """获取数据库中的色图数据 - - 参数: - local_id: 色图本地id. - tags: 标签. - is_r18: 是否r18. - - 返回: - list[Setu] | str: 色图数据列表或消息 - """ - image_list: list[Setu] = [] - if local_id: - image_count = await Setu.filter(is_r18=is_r18).count() - 1 - if local_id < 0 or local_id > image_count: - return f"超过当前上下限!({image_count})" - image_list = [await Setu.query_image(local_id, r18=is_r18)] # type: ignore - elif tags: - image_list = await Setu.query_image(tags=tags, r18=is_r18) # type: ignore - else: - image_list = await Setu.query_image(r18=is_r18) # type: ignore - if not image_list: - return "没找到符合条件的色图..." - return image_list - - @classmethod - def get_luo(cls, impression: float) -> UniMessage | None: - """罗翔 - - 参数: - impression: 好感度 - - 返回: - MessageFactory | None: 返回数据 - """ - if initial_setu_probability := base_config.get("INITIAL_SETU_PROBABILITY"): - probability = float(impression) + initial_setu_probability * 100 - if probability < random.randint(1, 101): - return MessageUtils.build_message( - [ - "我为什么要给你发这个?", - IMAGE_PATH - / "luoxiang" - / random.choice(os.listdir(IMAGE_PATH / "luoxiang")), - f"\n(快向{NICKNAME}签到提升好感度吧!)", - ] - ) - return None - - @classmethod - async def get_image(cls, setu: Setu) -> str | Path: - """下载图片 - - 参数: - setu: Setu - - 返回: - str | Path: 图片路径或返回消息 - """ - url = change_pixiv_image_links(setu.img_url) - index = setu.local_id if setu.local_id else random.randint(1, 100000) - file_name = f"{index}_temp_setu.jpg" - base_path = TEMP_PATH - if setu.local_id: - """本地图片存在直接返回""" - file_name = f"{index}.jpg" - if setu.is_r18: - base_path = IMAGE_PATH / "_r18" - else: - base_path = IMAGE_PATH / "_setu" - local_file = base_path / file_name - if local_file.exists(): - return local_file - file = base_path / file_name - download_success = False - for i in range(3): - logger.debug(f"尝试在线下载第 {i+1} 次", "色图") - try: - if await AsyncHttpx.download_file( - url, - file, - timeout=base_config.get("TIMEOUT"), - ): - download_success = True - if setu.local_id is not None: - if ( - os.path.getsize(base_path / f"{index}.jpg") - > 1024 * 1024 * 1.5 - ): - compressed_image( - base_path / f"{index}.jpg", - ) - change_img_md5(file) - logger.info(f"下载 lolicon 图片 {url} 成功, id:{index}") - break - except TimeoutError as e: - logger.error(f"下载图片超时", "色图", e=e) - except Exception as e: - logger.error(f"下载图片错误", "色图", e=e) - return file if download_success else "图片被小怪兽恰掉啦..!QAQ" - - @classmethod - async def search_lolicon( - cls, tags: list[str] | None, num: int, is_r18: bool - ) -> list[Setu] | str: - """搜索lolicon色图 - - 参数: - tags: 标签 - num: 数量 - is_r18: 是否r18 - - 返回: - list[Setu] | str: 色图数据或返回消息 - """ - params = { - "r18": 1 if is_r18 else 0, # 添加r18参数 0为否,1为是,2为混合 - "tag": tags, # 若指定tag - "num": 20, # 一次返回的结果数量 - "size": ["original"], - } - for count in range(3): - logger.debug(f"尝试获取图片URL第 {count+1} 次", "色图") - try: - response = await AsyncHttpx.get( - cls.URL, timeout=base_config.get("TIMEOUT"), params=params - ) - if response.status_code == 200: - data = response.json() - if not data["error"]: - data = data["data"] - result_list = cls.__handle_data(data) - num = num if num < len(data) else len(data) - random_list = random.sample(result_list, num) - if not random_list: - return "没找到符合条件的色图..." - return random_list - else: - return "没找到符合条件的色图..." - except TimeoutError as e: - logger.error(f"获取图片URL超时", "色图", e=e) - except Exception as e: - logger.error(f"访问页面错误", "色图", e=e) - return "我网线被人拔了..QAQ" - - @classmethod - def __handle_data(cls, data: dict) -> list[Setu]: - """lolicon数据处理 - - 参数: - data: lolicon数据 - - 返回: - list[Setu]: 整理的数据 - """ - result_list = [] - for i in range(len(data)): - img_url = data[i]["urls"]["original"] - img_url = change_pixiv_image_links(img_url) - title = data[i]["title"] - author = data[i]["author"] - pid = data[i]["pid"] - tags = [] - for j in range(len(data[i]["tags"])): - tags.append(data[i]["tags"][j]) - # if command != "色图r": - # if "R-18" in tags: - # tags.remove("R-18") - setu = Setu( - title=title, - author=author, - pid=pid, - img_url=img_url, - tags=",".join(tags), - is_r18="R-18" in tags, - ) - result_list.append(setu) - return result_list - - @classmethod - async def save_to_database(cls): - """存储色图数据到数据库 - - 参数: - data_list: 色图数据列表 - """ - set_list = [] - exists_list = [] - for data in cls.save_data: - if f"{data.pid}:{data.img_url}" not in exists_list: - exists_list.append(f"{data.pid}:{data.img_url}") - set_list.append(data) - if set_list: - create_list = [] - _cnt = 0 - _r18_cnt = 0 - for setu in set_list: - try: - if not await Setu.exists(pid=setu.pid, img_url=setu.img_url): - idx = await Setu.filter(is_r18=setu.is_r18).count() - setu.local_id = idx + (_r18_cnt if setu.is_r18 else _cnt) - setu.img_hash = "" - if setu.is_r18: - _r18_cnt += 1 - else: - _cnt += 1 - create_list.append(setu) - except UniqueViolationError: - pass - cls.save_data = [] - if create_list: - try: - await Setu.bulk_create(create_list, 10) - logger.debug(f"成功保存 {len(create_list)} 条色图数据") - except Exception as e: - logger.error("存储色图数据错误...", e=e) diff --git a/zhenxun/plugins/send_setu_/update_setu/__init__.py b/zhenxun/plugins/send_setu_/update_setu/__init__.py deleted file mode 100644 index 2b5b6ae9..00000000 --- a/zhenxun/plugins/send_setu_/update_setu/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from .data_source import update_setu_img - -__plugin_meta__ = PluginMetadata( - name="更新色图", - description="更新数据库内存在的色图", - usage=""" - 更新数据库内存在的色图 - 指令: - 更新色图 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.SUPERUSER, - limits=[BaseBlock(result="色图正在更新...")], - ).dict(), -) - -_matcher = on_alconna( - Alconna("更新色图"), rule=to_me(), permission=SUPERUSER, priority=1, block=True -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - if Config.get_config("send_setu", "DOWNLOAD_SETU"): - await MessageUtils.build_message("开始更新色图...").send(reply_to=True) - result = await update_setu_img(True) - if result: - await MessageUtils.build_message(result).send() - logger.info("更新色图", arparma.header_result, session=session) - else: - await MessageUtils.build_message("更新色图配置未开启...").send() - - -# 更新色图 -@scheduler.scheduled_job( - "cron", - hour=4, - minute=30, -) -async def _(): - if Config.get_config("send_setu", "DOWNLOAD_SETU"): - result = await update_setu_img() - if result: - logger.info(result, "自动更新色图") diff --git a/zhenxun/plugins/send_setu_/update_setu/data_source.py b/zhenxun/plugins/send_setu_/update_setu/data_source.py deleted file mode 100644 index 07d217d6..00000000 --- a/zhenxun/plugins/send_setu_/update_setu/data_source.py +++ /dev/null @@ -1,187 +0,0 @@ -import os -import shutil -from datetime import datetime - -import nonebot -import ujson as json -from asyncpg.exceptions import UniqueViolationError -from nonebot.drivers import Driver -from PIL import UnidentifiedImageError - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH, TEXT_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import compressed_image -from zhenxun.utils.utils import change_pixiv_image_links - -from .._model import Setu - -driver: Driver = nonebot.get_driver() - -_path = IMAGE_PATH - - -# 替换旧色图数据,修复local_id一直是50的问题 -@driver.on_startup -async def update_old_setu_data(): - path = TEXT_PATH - setu_data_file = path / "setu_data.json" - r18_data_file = path / "r18_setu_data.json" - if setu_data_file.exists() or r18_data_file.exists(): - index = 0 - r18_index = 0 - count = 0 - fail_count = 0 - for file in [setu_data_file, r18_data_file]: - if file.exists(): - data = json.load(open(file, "r", encoding="utf8")) - for x in data: - if file == setu_data_file: - idx = index - if "R-18" in data[x]["tags"]: - data[x]["tags"].remove("R-18") - else: - idx = r18_index - img_url = ( - data[x]["img_url"].replace("i.pixiv.cat", "i.pximg.net") - if "i.pixiv.cat" in data[x]["img_url"] - else data[x]["img_url"] - ) - # idx = r18_index if 'R-18' in data[x]["tags"] else index - try: - if not await Setu.exists(pid=data[x]["pid"], url=img_url): - await Setu.create( - local_id=idx, - title=data[x]["title"], - author=data[x]["author"], - pid=data[x]["pid"], - img_hash=data[x]["img_hash"], - img_url=img_url, - is_r18="R-18" in data[x]["tags"], - tags=",".join(data[x]["tags"]), - ) - count += 1 - if "R-18" in data[x]["tags"]: - r18_index += 1 - else: - index += 1 - logger.info( - f'添加旧色图数据成功 PID:{data[x]["pid"]} index:{idx}....' - ) - except UniqueViolationError: - fail_count += 1 - logger.info( - f'添加旧色图数据失败,色图重复 PID:{data[x]["pid"]} index:{idx}....' - ) - file.unlink() - setu_url_path = path / "setu_url.json" - setu_r18_url_path = path / "setu_r18_url.json" - if setu_url_path.exists(): - setu_url_path.unlink() - if setu_r18_url_path.exists(): - setu_r18_url_path.unlink() - logger.info( - f"更新旧色图数据完成,成功更新数据:{count} 条,累计失败:{fail_count} 条" - ) - - -# 删除色图rar文件夹 -shutil.rmtree(IMAGE_PATH / "setu_rar", ignore_errors=True) -shutil.rmtree(IMAGE_PATH / "r18_rar", ignore_errors=True) -shutil.rmtree(IMAGE_PATH / "rar", ignore_errors=True) - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net", -} - - -async def update_setu_img(flag: bool = False) -> str | None: - """更新色图 - - 参数: - flag: 是否手动更新. - - 返回: - str | None: 更新信息 - """ - image_list = await Setu.all().order_by("local_id") - image_list.reverse() - _success = 0 - error_info = [] - error_type = [] - count = 0 - for image in image_list: - count += 1 - path = _path / "_r18" if image.is_r18 else _path / "_setu" - local_image = path / f"{image.local_id}.jpg" - path.mkdir(exist_ok=True, parents=True) - TEMP_PATH.mkdir(exist_ok=True, parents=True) - if not local_image.exists() or not image.img_hash: - temp_file = TEMP_PATH / f"{image.local_id}.jpg" - if temp_file.exists(): - temp_file.unlink() - url_ = change_pixiv_image_links(image.img_url) - try: - if not await AsyncHttpx.download_file( - url_, TEMP_PATH / f"{image.local_id}.jpg" - ): - continue - _success += 1 - try: - if ( - os.path.getsize( - TEMP_PATH / f"{image.local_id}.jpg", - ) - > 1024 * 1024 * 1.5 - ): - compressed_image( - TEMP_PATH / f"{image.local_id}.jpg", - path / f"{image.local_id}.jpg", - ) - else: - logger.info( - f"不需要压缩,移动图片{TEMP_PATH}/{image.local_id}.jpg " - f"--> /{path}/{image.local_id}.jpg" - ) - os.rename( - TEMP_PATH / f"{image.local_id}.jpg", - path / f"{image.local_id}.jpg", - ) - except FileNotFoundError: - logger.warning(f"文件 {image.local_id}.jpg 不存在,跳过...") - continue - # img_hash = str(get_img_hash(f"{path}/{image.local_id}.jpg")) - image.img_hash = "" - await image.save(update_fields=["img_hash"]) - # await Setu.update_setu_data(image.pid, img_hash=img_hash) - except UnidentifiedImageError: - # 图片已删除 - unlink = False - with open(local_image, "r") as f: - if "404 Not Found" in f.read(): - unlink = True - if unlink: - local_image.unlink() - max_num = await Setu.delete_image(image.pid, image.img_url) - if (path / f"{max_num}.jpg").exists(): - os.rename(path / f"{max_num}.jpg", local_image) - logger.warning(f"更新色图 PID:{image.pid} 404,已删除并替换") - except Exception as e: - _success -= 1 - logger.error(f"更新色图 {image.local_id}.jpg 错误 {type(e)}: {e}") - if type(e) not in error_type: - error_type.append(type(e)) - error_info.append( - f"更新色图 {image.local_id}.jpg 错误 {type(e)}: {e}" - ) - else: - logger.info(f"更新色图 {image.local_id}.jpg 已存在") - if _success or error_info or flag: - return ( - f'{str(datetime.now()).split(".")[0]} 更新 色图 完成,本地存在 {count} 张,实际更新 {_success} 张,以下为更新时未知错误:\n' - + "\n".join(error_info), - ) - return None diff --git a/zhenxun/plugins/send_voice/__init__.py b/zhenxun/plugins/send_voice/__init__.py deleted file mode 100644 index eb35e275..00000000 --- a/zhenxun/plugins/send_voice/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/send_voice/dinggong.py b/zhenxun/plugins/send_voice/dinggong.py deleted file mode 100644 index a01129ca..00000000 --- a/zhenxun/plugins/send_voice/dinggong.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import random - -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Arparma, UniMessage, Voice, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import RECORD_PATH -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="钉宫骂我", - description="请狠狠的骂我一次!", - usage=""" - 多骂我一点,球球了 - 指令: - 骂老子 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - limits=[PluginCdBlock(cd=3, result="就...就算求我骂你也得慢慢来...")], - ).dict(), -) - -_matcher = on_alconna(Alconna("ma-wo"), rule=to_me(), priority=5, block=True) - -_matcher.shortcut( - r".*?骂.*?我.*?", - command="ma-wo", - arguments=[], - prefix=True, -) - -path = RECORD_PATH / "dinggong" - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - if not path.exists(): - await MessageUtils.build_message("钉宫语音文件夹不存在...").finish() - files = os.listdir(path) - if not files: - await MessageUtils.build_message("钉宫语音文件夹为空...").finish() - voice = random.choice(files) - await UniMessage([Voice(path=path / voice)]).send() - await MessageUtils.build_message(voice.split("_")[1]).send() - logger.info(f"发送钉宫骂人: {voice}", arparma.header_result, session=session) diff --git a/zhenxun/plugins/statistics/statistics_hook.py b/zhenxun/plugins/statistics/statistics_hook.py deleted file mode 100644 index d1fc4635..00000000 --- a/zhenxun/plugins/statistics/statistics_hook.py +++ /dev/null @@ -1,48 +0,0 @@ -from datetime import datetime - -from nonebot.adapters import Bot, Event -from nonebot.adapters.onebot.v11 import PokeNotifyEvent -from nonebot.matcher import Matcher -from nonebot.message import run_postprocessor -from nonebot.plugin import PluginMetadata -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.statistics import Statistics -from zhenxun.utils.enum import PluginType - -__plugin_meta__ = PluginMetadata( - name="功能调用统计", - description="功能调用统计", - usage="""""".strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN - ).dict(), -) - - -@run_postprocessor -async def _( - matcher: Matcher, - exception: Exception | None, - bot: Bot, - session: EventSession, - event: Event, -): - if matcher.type == "notice" and not isinstance(event, PokeNotifyEvent): - """过滤除poke外的notice""" - return - if session.id1: - plugin = await PluginInfo.get_or_none(module=matcher.module_name) - plugin_type = plugin.plugin_type if plugin else None - if plugin_type == PluginType.NORMAL and matcher.plugin_name not in [ - "update_info", - "statistics_handle", - ]: - await Statistics.create( - user_id=session.id1, - group_id=session.id3 or session.id2, - plugin_name=matcher.plugin_name, - create_time=datetime.now(), - ) diff --git a/zhenxun/plugins/translate/__init__.py b/zhenxun/plugins/translate/__init__.py deleted file mode 100644 index 372a5774..00000000 --- a/zhenxun/plugins/translate/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.depends import CheckConfig -from zhenxun.utils.image_utils import ImageTemplate -from zhenxun.utils.message import MessageUtils - -from .data_source import language, translate_message - -__plugin_meta__ = PluginMetadata( - name="翻译", - description="出国旅游好助手", - usage=""" - 指令: - 翻译语种: (查看soruce与to可用值,代码与中文都可) - 示例: - 翻译 你好: 将中文翻译为英文 - 翻译 Hello: 将英文翻译为中文 - - 翻译 你好 -to 希腊语: 将"你好"翻译为希腊语 - 翻译 你好: 允许form和to使用中文 - 翻译 你好 -form:中文 to:日语 你好: 指定原语种并将"你好"翻译为日文 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - configs=[ - RegisterConfig(key="APPID", value=None, help="百度翻译APPID"), - RegisterConfig(key="SECRET_KEY", value=None, help="百度翻译秘钥"), - ], - ).dict(), -) - -_matcher = on_alconna( - Alconna( - "翻译", - Args["text", str], - Option("-s|--source", Args["source_text", str, "auto"]), - Option("-t|--to", Args["to_text", str, "auto"]), - ), - priority=5, - block=True, -) - -_language_matcher = on_alconna(Alconna("翻译语种"), priority=5, block=True) - - -@_language_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - s = "" - column_list = ["语种", "代码"] - data_list = [] - for key, value in language.items(): - data_list.append([key, value]) - image = await ImageTemplate.table_page("翻译语种", "", column_list, data_list) - await MessageUtils.build_message(image).send() - logger.info(f"查看翻译语种", arparma.header_result, session=session) - - -@_matcher.handle( - parameterless=[ - CheckConfig(config="APPID"), - CheckConfig(config="SECRET_KEY"), - ] -) -async def _( - session: EventSession, - arparma: Arparma, - text: str, - source_text: Match[str], - to_text: Match[str], -): - source = source_text.result if source_text.available else "auto" - to = to_text.result if to_text.available else "auto" - values = language.values() - keys = language.keys() - if source not in values and source not in keys: - await MessageUtils.build_message("源语种不支持...").finish() - if to not in values and to not in keys: - await MessageUtils.build_message("目标语种不支持...").finish() - result = await translate_message(text, source, to) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"source: {source}, to: {to}, 翻译: {text}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/translate/data_source.py b/zhenxun/plugins/translate/data_source.py deleted file mode 100644 index a7a3018d..00000000 --- a/zhenxun/plugins/translate/data_source.py +++ /dev/null @@ -1,83 +0,0 @@ -import time -from hashlib import md5 - -from zhenxun.configs.config import Config -from zhenxun.utils.http_utils import AsyncHttpx - -URL = "http://api.fanyi.baidu.com/api/trans/vip/translate" - - -language = { - "自动": "auto", - "粤语": "yue", - "韩语": "kor", - "泰语": "th", - "葡萄牙语": "pt", - "希腊语": "el", - "保加利亚语": "bul", - "芬兰语": "fin", - "斯洛文尼亚语": "slo", - "繁体中文": "cht", - "中文": "zh", - "文言文": "wyw", - "法语": "fra", - "阿拉伯语": "ara", - "德语": "de", - "荷兰语": "nl", - "爱沙尼亚语": "est", - "捷克语": "cs", - "瑞典语": "swe", - "越南语": "vie", - "英语": "en", - "日语": "jp", - "西班牙语": "spa", - "俄语": "ru", - "意大利语": "it", - "波兰语": "pl", - "丹麦语": "dan", - "罗马尼亚语": "rom", - "匈牙利语": "hu", -} - - -async def translate_message(word: str, form: str, to: str) -> str: - """翻译 - - 参数: - word (str): 翻译文字 - form (str): 源语言 - to (str): 目标语言 - - 返回: - str: 翻译后的文字 - """ - if form in language: - form = language[form] - if to in language: - to = language[to] - salt = str(time.time()) - app_id = Config.get_config("translate", "APPID") - secret_key = Config.get_config("translate", "SECRET_KEY") - sign = app_id + word + salt + secret_key # type: ignore - md5_ = md5() - md5_.update(sign.encode("utf-8")) - sign = md5_.hexdigest() - params = { - "q": word, - "from": form, - "to": to, - "appid": app_id, - "salt": salt, - "sign": sign, - } - url = URL + "?" - for key, value in params.items(): - url += f"{key}={value}&" - url = url[:-1] - resp = await AsyncHttpx.get(url) - data = resp.json() - if data.get("error_code"): - return data.get("error_msg") - if trans_result := data.get("trans_result"): - return trans_result[0]["dst"] - return "没有找到翻译捏..." diff --git a/zhenxun/plugins/wbtop/__init__.py b/zhenxun/plugins/wbtop/__init__.py deleted file mode 100644 index fce1b995..00000000 --- a/zhenxun/plugins/wbtop/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncPlaywright -from zhenxun.utils.message import MessageUtils - -from .data_source import get_hot_image - -__plugin_meta__ = PluginMetadata( - name="微博热搜", - description="刚买完瓜,在吃瓜现场", - usage=""" - 指令: - 微博热搜:发送实时热搜 - 微博热搜 [id]:截图该热搜页面 - 示例:微博热搜 5 - """.strip(), - extra=PluginExtraData( - author="HibiKier & yajiwa", - version="0.1", - ).dict(), -) - - -_matcher = on_alconna(Alconna("微博热搜", Args["idx?", int]), priority=5, block=True) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma, idx: Match[int]): - result, data_list = await get_hot_image() - if isinstance(result, str): - await MessageUtils.build_message(result).finish(reply_to=True) - if idx.available: - _idx = idx.result - url = data_list[_idx - 1]["url"] - file = IMAGE_PATH / "temp" / f"wbtop_{session.id1}.png" - img = await AsyncPlaywright.screenshot( - url, - file, - "#pl_feed_main", - wait_time=12, - ) - if img: - await MessageUtils.build_message(file).send() - logger.info( - f"查询微博热搜 Id: {_idx}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message("获取图片失败...").send() - else: - await MessageUtils.build_message(result).send() - logger.info(f"查询微博热搜", arparma.header_result, session=session) diff --git a/zhenxun/plugins/wbtop/data_source.py b/zhenxun/plugins/wbtop/data_source.py deleted file mode 100644 index e9c20627..00000000 --- a/zhenxun/plugins/wbtop/data_source.py +++ /dev/null @@ -1,63 +0,0 @@ -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage - -URL = "https://weibo.com/ajax/side/hotSearch" - - -async def get_data() -> list | str: - """获取数据 - - 返回: - list | str: 数据或消息 - """ - data_list = [] - for _ in range(3): - try: - response = await AsyncHttpx.get(URL, timeout=20) - if response.status_code == 200: - data_json = response.json()["data"]["realtime"] - for item in data_json: - if "is_ad" in item: - """广告跳过""" - continue - data = { - "hot_word": item["note"], - "hot_word_num": str(item["num"]), - "url": "https://s.weibo.com/weibo?q=%23" + item["word"] + "%23", - } - data_list.append(data) - if not data: - return "没有搜索到..." - return data_list - except Exception as e: - logger.error("获取微博热搜错误", e=e) - return "获取失败,请十分钟后再试..." - - -async def get_hot_image() -> tuple[BuildImage | str, list]: - """构造图片 - - 返回: - BuildImage | str: 热搜图片 - """ - data = await get_data() - if isinstance(data, str): - return data, [] - bk = BuildImage(700, 32 * 50 + 280, color="#797979") - wbtop_bk = BuildImage(700, 280, background=f"{IMAGE_PATH}/other/webtop.png") - await bk.paste(wbtop_bk) - text_bk = BuildImage(700, 32 * 50, color="#797979") - image_list = [] - for i, _data in enumerate(data): - title = f"{i + 1}. {_data['hot_word']}" - hot = str(_data["hot_word_num"]) - img = BuildImage(700, 30, font_size=20) - _, h = img.getsize(title) - await img.text((10, int((30 - h) / 2)), title) - await img.text((580, int((30 - h) / 2)), hot) - image_list.append(img) - text_bk = await text_bk.auto_paste(image_list, 1, 2, 0) - await bk.paste(text_bk, (0, 280)) - return bk, data diff --git a/zhenxun/plugins/web_ui/api/__init__.py b/zhenxun/plugins/web_ui/api/__init__.py deleted file mode 100644 index 32d31b27..00000000 --- a/zhenxun/plugins/web_ui/api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .tabs import * diff --git a/zhenxun/plugins/web_ui/api/logs/__init__.py b/zhenxun/plugins/web_ui/api/logs/__init__.py deleted file mode 100644 index d6684888..00000000 --- a/zhenxun/plugins/web_ui/api/logs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .logs import * diff --git a/zhenxun/plugins/web_ui/api/tabs/__init__.py b/zhenxun/plugins/web_ui/api/tabs/__init__.py deleted file mode 100644 index 99ed6ea1..00000000 --- a/zhenxun/plugins/web_ui/api/tabs/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .database import * -from .main import * -from .manage import * -from .plugin_manage import * -from .system import * diff --git a/zhenxun/plugins/web_ui/api/tabs/database/__init__.py b/zhenxun/plugins/web_ui/api/tabs/database/__init__.py deleted file mode 100644 index 2fd77085..00000000 --- a/zhenxun/plugins/web_ui/api/tabs/database/__init__.py +++ /dev/null @@ -1,121 +0,0 @@ -import nonebot -from fastapi import APIRouter, Request -from nonebot.drivers import Driver -from tortoise import Tortoise -from tortoise.exceptions import OperationalError - -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.services.db_context import TestSQL - -from ....base_model import BaseResultModel, QueryModel, Result -from ....utils import authentication -from .models.model import SqlModel, SqlText -from .models.sql_log import SqlLog - -router = APIRouter(prefix="/database") - - -driver: Driver = nonebot.get_driver() - - -SQL_DICT = {} - - -SELECT_TABLE_SQL = """ -select a.tablename as name,d.description as desc from pg_tables a - left join pg_class c on relname=tablename - left join pg_description d on oid=objoid and objsubid=0 where a.schemaname = 'public' -""" - -SELECT_TABLE_COLUMN_SQL = """ -SELECT column_name, data_type, character_maximum_length as max_length, is_nullable -FROM information_schema.columns -WHERE table_name = '{}'; -""" - - -@driver.on_startup -async def _(): - for plugin in nonebot.get_loaded_plugins(): - module = plugin.name - sql_list = [] - if plugin.metadata and plugin.metadata.extra: - sql_list = plugin.metadata.extra.get("sql_list") - if module in SQL_DICT: - raise ValueError(f"{module} 常用SQL module 重复") - if sql_list: - SqlModel( - name="", - module=module, - sql_list=sql_list, - ) - SQL_DICT[module] = SqlModel - if SQL_DICT: - result = await PluginInfo.filter(module__in=SQL_DICT.keys()).values_list( - "module", "name" - ) - module2name = {r[0]: r[1] for r in result} - for s in SQL_DICT: - module = SQL_DICT[s].module - if module in module2name: - SQL_DICT[s].name = module2name[module] - else: - SQL_DICT[s].name = module - - -@router.get( - "/get_table_list", dependencies=[authentication()], description="获取数据库表" -) -async def _() -> Result: - db = Tortoise.get_connection("default") - query = await db.execute_query_dict(SELECT_TABLE_SQL) - return Result.ok(query) - - -@router.get( - "/get_table_column", dependencies=[authentication()], description="获取表字段" -) -async def _(table_name: str) -> Result: - db = Tortoise.get_connection("default") - print(SELECT_TABLE_COLUMN_SQL.format(table_name)) - query = await db.execute_query_dict(SELECT_TABLE_COLUMN_SQL.format(table_name)) - return Result.ok(query) - - -@router.post("/exec_sql", dependencies=[authentication()], description="执行sql") -async def _(sql: SqlText, request: Request) -> Result: - ip = request.client.host if request.client else "unknown" - try: - if sql.sql.lower().startswith("select"): - db = Tortoise.get_connection("default") - res = await db.execute_query_dict(sql.sql) - await SqlLog.add(ip or "0.0.0.0", sql.sql, "") - return Result.ok(res, "执行成功啦!") - else: - result = await TestSQL.raw(sql.sql) - await SqlLog.add(ip or "0.0.0.0", sql.sql, str(result)) - return Result.ok(info="执行成功啦!") - except OperationalError as e: - await SqlLog.add(ip or "0.0.0.0", sql.sql, str(e), False) - return Result.warning_(f"sql执行错误: {e}") - - -@router.post("/get_sql_log", dependencies=[authentication()], description="sql日志列表") -async def _(query: QueryModel) -> Result: - total = await SqlLog.all().count() - if total % query.size: - total += 1 - data = ( - await SqlLog.all() - .order_by("-id") - .offset((query.index - 1) * query.size) - .limit(query.size) - ) - return Result.ok(BaseResultModel(total=total, data=data)) - - -@router.get("/get_common_sql", dependencies=[authentication()], description="常用sql") -async def _(plugin_name: str | None = None) -> Result: - if plugin_name: - return Result.ok(SQL_DICT.get(plugin_name)) - return Result.ok(str(SQL_DICT)) diff --git a/zhenxun/plugins/web_ui/api/tabs/main/__init__.py b/zhenxun/plugins/web_ui/api/tabs/main/__init__.py deleted file mode 100644 index ed8bb576..00000000 --- a/zhenxun/plugins/web_ui/api/tabs/main/__init__.py +++ /dev/null @@ -1,290 +0,0 @@ -import asyncio -import time -from datetime import datetime, timedelta -from pathlib import Path - -import nonebot -from fastapi import APIRouter, WebSocket -from starlette.websockets import WebSocket, WebSocketDisconnect, WebSocketState -from tortoise.functions import Count -from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK - -from zhenxun.models.chat_history import ChatHistory -from zhenxun.models.group_info import GroupInfo -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.statistics import Statistics -from zhenxun.services.log import logger -from zhenxun.utils.platform import PlatformUtils - -from ....base_model import Result -from ....config import AVA_URL, GROUP_AVA_URL, QueryDateType -from ....utils import authentication, get_system_status -from .data_source import bot_live -from .model import ActiveGroup, BaseInfo, ChatHistoryCount, HotPlugin - -run_time = time.time() - -ws_router = APIRouter() -router = APIRouter(prefix="/main") - - -@router.get("/get_base_info", dependencies=[authentication()], description="基础信息") -async def _(bot_id: str | None = None) -> Result: - """获取Bot基础信息 - - 参数: - bot_id (Optional[str], optional): bot_id. Defaults to None. - - 返回: - Result: 获取指定bot信息与bot列表 - """ - bot_list: list[BaseInfo] = [] - if bots := nonebot.get_bots(): - select_bot: BaseInfo - for key, bot in bots.items(): - login_info = await bot.get_login_info() - bot_list.append( - BaseInfo( - bot=bot, # type: ignore - self_id=bot.self_id, - nickname=login_info["nickname"], - ava_url=AVA_URL.format(bot.self_id), - ) - ) - # 获取指定qq号的bot信息,若无指定 则获取第一个 - if _bl := [b for b in bot_list if b.self_id == bot_id]: - select_bot = _bl[0] - else: - select_bot = bot_list[0] - select_bot.is_select = True - select_bot.config = select_bot.bot.config - now = datetime.now() - # 今日累计接收消息 - select_bot.received_messages = await ChatHistory.filter( - bot_id=select_bot.self_id, - create_time__gte=now - timedelta(hours=now.hour), - ).count() - # 群聊数量 - select_bot.group_count = len(await select_bot.bot.get_group_list()) - # 好友数量 - select_bot.friend_count = len(await select_bot.bot.get_friend_list()) - for bot in bot_list: - bot.bot = None # type: ignore - # 插件加载数量 - select_bot.plugin_count = await PluginInfo.all().count() - fail_count = await PluginInfo.filter(load_status=False).count() - select_bot.fail_plugin_count = fail_count - select_bot.success_plugin_count = ( - select_bot.plugin_count - select_bot.fail_plugin_count - ) - # 连接时间 - select_bot.connect_time = bot_live.get(select_bot.self_id) or 0 - if select_bot.connect_time: - connect_date = datetime.fromtimestamp(select_bot.connect_time) - connect_date_str = connect_date.strftime("%Y-%m-%d %H:%M:%S") - select_bot.connect_date = datetime.strptime( - connect_date_str, "%Y-%m-%d %H:%M:%S" - ) - version_file = Path() / "__version__" - if version_file.exists(): - if text := version_file.open().read(): - if ver := text.replace("__version__: ", "").strip(): - select_bot.version = ver - day_call = await Statistics.filter( - create_time__gte=now - timedelta(hours=now.hour) - ).count() - select_bot.day_call = day_call - return Result.ok(bot_list, "拿到信息啦!") - return Result.warning_("无Bot连接...") - - -@router.get( - "/get_all_ch_count", dependencies=[authentication()], description="获取接收消息数量" -) -async def _(bot_id: str) -> Result: - now = datetime.now() - all_count = await ChatHistory.filter(bot_id=bot_id).count() - day_count = await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(hours=now.hour) - ).count() - week_count = await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(days=7) - ).count() - month_count = await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(days=30) - ).count() - year_count = await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(days=365) - ).count() - return Result.ok( - ChatHistoryCount( - num=all_count, - day=day_count, - week=week_count, - month=month_count, - year=year_count, - ) - ) - - -@router.get( - "/get_ch_count", dependencies=[authentication()], description="获取接收消息数量" -) -async def _(bot_id: str, query_type: QueryDateType | None = None) -> Result: - if bots := nonebot.get_bots(): - if not query_type: - return Result.ok(await ChatHistory.filter(bot_id=bot_id).count()) - now = datetime.now() - if query_type == QueryDateType.DAY: - return Result.ok( - await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(hours=now.hour) - ).count() - ) - if query_type == QueryDateType.WEEK: - return Result.ok( - await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(days=7) - ).count() - ) - if query_type == QueryDateType.MONTH: - return Result.ok( - await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(days=30) - ).count() - ) - if query_type == QueryDateType.YEAR: - return Result.ok( - await ChatHistory.filter( - bot_id=bot_id, create_time__gte=now - timedelta(days=365) - ).count() - ) - return Result.warning_("无Bot连接...") - - -@router.get( - "get_fg_count", dependencies=[authentication()], description="好友/群组数量" -) -async def _(bot_id: str) -> Result: - if bots := nonebot.get_bots(): - if bot_id not in bots: - return Result.warning_("指定Bot未连接...") - bot = bots[bot_id] - platform = PlatformUtils.get_platform(bot) - if platform == "qq": - data = { - "friend_count": len(await bot.get_friend_list()), - "group_count": len(await bot.get_group_list()), - } - return Result.ok(data) - return Result.warning_("暂不支持该平台...") - return Result.warning_("无Bot连接...") - - -@router.get( - "/get_run_time", dependencies=[authentication()], description="获取nb运行时间" -) -async def _() -> Result: - return Result.ok(int(time.time() - run_time)) - - -@router.get( - "/get_active_group", dependencies=[authentication()], description="获取活跃群聊" -) -async def _(date_type: QueryDateType | None = None) -> Result: - query = ChatHistory - now = datetime.now() - if date_type == QueryDateType.DAY: - query = ChatHistory.filter(create_time__gte=now - timedelta(hours=now.hour)) - if date_type == QueryDateType.WEEK: - query = ChatHistory.filter(create_time__gte=now - timedelta(days=7)) - if date_type == QueryDateType.MONTH: - query = ChatHistory.filter(create_time__gte=now - timedelta(days=30)) - if date_type == QueryDateType.YEAR: - query = ChatHistory.filter(create_time__gte=now - timedelta(days=365)) - data_list = ( - await query.annotate(count=Count("id")) - .filter(group_id__not_isnull=True) - .group_by("group_id") - .order_by("-count") - .limit(5) - .values_list("group_id", "count") - ) - active_group_list = [] - id2name = {} - if data_list: - if info_list := await GroupInfo.filter( - group_id__in=[x[0] for x in data_list] - ).all(): - for group_info in info_list: - id2name[group_info.group_id] = group_info.group_name - for data in data_list: - active_group_list.append( - ActiveGroup( - group_id=data[0], - name=id2name.get(data[0]) or data[0], - chat_num=data[1], - ava_img=GROUP_AVA_URL.format(data[0], data[0]), - ) - ) - active_group_list = sorted( - active_group_list, key=lambda x: x.chat_num, reverse=True - ) - if len(active_group_list) > 5: - active_group_list = active_group_list[:5] - return Result.ok(active_group_list) - - -@router.get( - "/get_hot_plugin", dependencies=[authentication()], description="获取热门插件" -) -async def _(date_type: QueryDateType | None = None) -> Result: - query = Statistics - now = datetime.now() - if date_type == QueryDateType.DAY: - query = Statistics.filter(create_time__gte=now - timedelta(hours=now.hour)) - if date_type == QueryDateType.WEEK: - query = Statistics.filter(create_time__gte=now - timedelta(days=7)) - if date_type == QueryDateType.MONTH: - query = Statistics.filter(create_time__gte=now - timedelta(days=30)) - if date_type == QueryDateType.YEAR: - query = Statistics.filter(create_time__gte=now - timedelta(days=365)) - data_list = ( - await query.annotate(count=Count("id")) - .group_by("plugin_name") - .order_by("-count") - .limit(5) - .values_list("plugin_name", "count") - ) - hot_plugin_list = [] - module_list = [x[0] for x in data_list] - plugins = await PluginInfo.filter(module__in=module_list).all() - module2name = {p.module: p.name for p in plugins} - for data in data_list: - module = data[0] - name = module2name.get(module) or module - hot_plugin_list.append( - HotPlugin( - module=data[0], - name=name, - count=data[1], - ) - ) - hot_plugin_list = sorted(hot_plugin_list, key=lambda x: x.count, reverse=True) - if len(hot_plugin_list) > 5: - hot_plugin_list = hot_plugin_list[:5] - return Result.ok(hot_plugin_list) - - -@ws_router.websocket("/system_status") -async def system_logs_realtime(websocket: WebSocket, sleep: int = 5): - await websocket.accept() - logger.debug("ws system_status is connect") - try: - while websocket.client_state == WebSocketState.CONNECTED: - system_status = await get_system_status() - await websocket.send_text(system_status.json()) - await asyncio.sleep(sleep) - except (WebSocketDisconnect, ConnectionClosedError, ConnectionClosedOK): - pass - return diff --git a/zhenxun/plugins/web_ui/api/tabs/main/data_source.py b/zhenxun/plugins/web_ui/api/tabs/main/data_source.py deleted file mode 100644 index ca445016..00000000 --- a/zhenxun/plugins/web_ui/api/tabs/main/data_source.py +++ /dev/null @@ -1,35 +0,0 @@ -import time - -import nonebot -from nonebot.adapters.onebot.v11 import Bot -from nonebot.drivers import Driver - -driver: Driver = nonebot.get_driver() - - -class BotLive: - def __init__(self): - self._data = {} - - def add(self, bot_id: str): - self._data[bot_id] = time.time() - - def get(self, bot_id: str) -> int | None: - return self._data.get(bot_id) - - def remove(self, bot_id: str): - if bot_id in self._data: - del self._data[bot_id] - - -bot_live = BotLive() - - -@driver.on_bot_connect -async def _(bot: Bot): - bot_live.add(bot.self_id) - - -@driver.on_bot_disconnect -async def _(bot: Bot): - bot_live.remove(bot.self_id) diff --git a/zhenxun/plugins/web_ui/api/tabs/manage/__init__.py b/zhenxun/plugins/web_ui/api/tabs/manage/__init__.py deleted file mode 100644 index 4f545200..00000000 --- a/zhenxun/plugins/web_ui/api/tabs/manage/__init__.py +++ /dev/null @@ -1,440 +0,0 @@ -import nonebot -from fastapi import APIRouter -from nonebot.adapters.onebot.v11 import ActionFailed -from tortoise.functions import Count - -from zhenxun.configs.config import NICKNAME -from zhenxun.models.ban_console import BanConsole -from zhenxun.models.chat_history import ChatHistory -from zhenxun.models.fg_request import FgRequest -from zhenxun.models.group_console import GroupConsole -from zhenxun.models.plugin_info import PluginInfo -from zhenxun.models.statistics import Statistics -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import RequestHandleType, RequestType -from zhenxun.utils.exception import NotFoundError -from zhenxun.utils.platform import PlatformUtils - -from ....base_model import Result -from ....config import AVA_URL, GROUP_AVA_URL -from ....utils import authentication -from .model import ( - ClearRequest, - DeleteFriend, - Friend, - FriendRequestResult, - GroupDetail, - GroupRequestResult, - GroupResult, - HandleRequest, - LeaveGroup, - Plugin, - ReqResult, - SendMessage, - Task, - UpdateGroup, - UserDetail, -) - -router = APIRouter(prefix="/manage") - - -@router.get( - "/get_group_list", dependencies=[authentication()], description="获取群组列表" -) -async def _(bot_id: str) -> Result: - """ - 获取群信息 - """ - if bots := nonebot.get_bots(): - if bot_id not in bots: - return Result.warning_("指定Bot未连接...") - group_list_result = [] - try: - group_list = await bots[bot_id].get_group_list() - for g in group_list: - gid = g["group_id"] - g["ava_url"] = GROUP_AVA_URL.format(gid, gid) - group_list_result.append(GroupResult(**g)) - except Exception as e: - logger.error("调用API错误", "/get_group_list", e=e) - return Result.fail(f"{type(e)}: {e}") - return Result.ok(group_list_result, "拿到了新鲜出炉的数据!") - return Result.warning_("无Bot连接...") - - -@router.post( - "/update_group", dependencies=[authentication()], description="修改群组信息" -) -async def _(group: UpdateGroup) -> Result: - try: - group_id = group.group_id - if db_group := await GroupConsole.get_group(group_id): - task_list = await TaskInfo.all().values_list("module", flat=True) - db_group.level = group.level - db_group.status = group.status - if group.close_plugins: - db_group.block_plugin = ",".join(group.close_plugins) + "," - if group.task: - block_task = [] - for t in task_list: - if t not in group.task: - block_task.append(t) - if block_task: - db_group.block_task = ",".join(block_task) + "," - await db_group.save( - update_fields=["level", "status", "block_plugin", "block_task"] - ) - except Exception as e: - logger.error("调用API错误", "/get_group", e=e) - return Result.fail(f"{type(e)}: {e}") - return Result.ok(info="已完成记录!") - - -@router.get( - "/get_friend_list", dependencies=[authentication()], description="获取好友列表" -) -async def _(bot_id: str) -> Result: - """ - 获取群信息 - """ - if bots := nonebot.get_bots(): - if bot_id not in bots: - return Result.warning_("指定Bot未连接...") - try: - platform = PlatformUtils.get_platform(bots[bot_id]) - if platform != "qq": - return Result.warning_("该平台暂不支持该功能...") - friend_list = await bots[bot_id].get_friend_list() - for f in friend_list: - f["ava_url"] = AVA_URL.format(f["user_id"]) - return Result.ok( - [Friend(**f) for f in friend_list if str(f["user_id"]) != bot_id], - "拿到了新鲜出炉的数据!", - ) - except Exception as e: - logger.error("调用API错误", "/get_group_list", e=e) - return Result.fail(f"{type(e)}: {e}") - return Result.warning_("无Bot连接...") - - -@router.get( - "/get_request_count", dependencies=[authentication()], description="获取请求数量" -) -async def _() -> Result: - f_count = await FgRequest.filter( - request_type=RequestType.FRIEND, handle_type__isnull=True - ).count() - g_count = await FgRequest.filter( - request_type=RequestType.GROUP, handle_type__isnull=True - ).count() - data = { - "friend_count": f_count, - "group_count": g_count, - } - return Result.ok(data, f"{NICKNAME}带来了最新的数据!") - - -@router.get( - "/get_request_list", dependencies=[authentication()], description="获取请求列表" -) -async def _() -> Result: - try: - req_result = ReqResult() - data_list = await FgRequest.filter(handle_type__isnull=True).all() - for req in data_list: - if req.request_type == RequestType.FRIEND: - req_result.friend.append( - FriendRequestResult( - oid=req.id, - bot_id=req.bot_id, - id=req.user_id, - flag=req.flag, - nickname=req.nickname, - comment=req.comment, - ava_url=AVA_URL.format(req.user_id), - type=str(req.request_type).lower(), - ) - ) - else: - req_result.group.append( - GroupRequestResult( - oid=req.id, - bot_id=req.bot_id, - id=req.user_id, - flag=req.flag, - nickname=req.nickname, - comment=req.comment, - ava_url=GROUP_AVA_URL.format(req.group_id, req.group_id), - type=str(req.request_type).lower(), - invite_group=req.group_id, - group_name=None, - ) - ) - req_result.friend.reverse() - req_result.group.reverse() - except Exception as e: - logger.error("调用API错误", "/get_request", e=e) - return Result.fail(f"{type(e)}: {e}") - return Result.ok(req_result, f"{NICKNAME}带来了最新的数据!") - - -@router.post( - "/clear_request", dependencies=[authentication()], description="清空请求列表" -) -async def _(cr: ClearRequest) -> Result: - await FgRequest.filter( - handle_type__isnull=True, request_type=cr.request_type - ).update(handle_type=RequestHandleType.IGNORE) - return Result.ok(info="成功清除了数据!") - - -@router.post("/refuse_request", dependencies=[authentication()], description="拒绝请求") -async def _(parma: HandleRequest) -> Result: - try: - if bots := nonebot.get_bots(): - bot_id = parma.bot_id - if bot_id not in nonebot.get_bots(): - return Result.warning_("指定Bot未连接...") - try: - await FgRequest.refused(bots[bot_id], parma.id) - except ActionFailed as e: - await FgRequest.expire(parma.id) - return Result.warning_("请求失败,可能该请求已失效或请求数据错误...") - except NotFoundError: - return Result.warning_("未找到此Id请求...") - return Result.ok(info="成功处理了请求!") - return Result.warning_("无Bot连接...") - except Exception as e: - logger.error("调用API错误", "/refuse_request", e=e) - return Result.fail(f"{type(e)}: {e}") - - -@router.post("/delete_request", dependencies=[authentication()], description="忽略请求") -async def _(parma: HandleRequest) -> Result: - await FgRequest.ignore(parma.id) - return Result.ok(info="成功处理了请求!") - - -@router.post( - "/approve_request", dependencies=[authentication()], description="同意请求" -) -async def _(parma: HandleRequest) -> Result: - try: - if bots := nonebot.get_bots(): - bot_id = parma.bot_id - if bot_id not in nonebot.get_bots(): - return Result.warning_("指定Bot未连接...") - if req := await FgRequest.get_or_none(id=parma.id): - if group := await GroupConsole.get_group(group_id=req.group_id): - group.group_flag = 1 - await group.save(update_fields=["group_flag"]) - else: - group_info = await bots[bot_id].get_group_info( - group_id=req.group_id - ) - await GroupConsole.update_or_create( - group_id=str(group_info["group_id"]), - defaults={ - "group_name": group_info["group_name"], - "max_member_count": group_info["max_member_count"], - "member_count": group_info["member_count"], - "group_flag": 1, - }, - ) - else: - return Result.warning_("未找到此Id请求...") - try: - await FgRequest.approve(bots[bot_id], parma.id) - return Result.ok(info="成功处理了请求!") - except ActionFailed as e: - await FgRequest.expire(parma.id) - return Result.warning_("请求失败,可能该请求已失效或请求数据错误...") - return Result.warning_("无Bot连接...") - except Exception as e: - logger.error("调用API错误", "/approve_request", e=e) - return Result.fail(f"{type(e)}: {e}") - - -@router.post("/leave_group", dependencies=[authentication()], description="退群") -async def _(param: LeaveGroup) -> Result: - try: - if bots := nonebot.get_bots(): - bot_id = param.bot_id - platform = PlatformUtils.get_platform(bots[bot_id]) - if platform != "qq": - return Result.warning_("该平台不支持退群操作...") - group_list = await bots[bot_id].get_group_list() - if param.group_id not in [str(g["group_id"]) for g in group_list]: - return Result.warning_("Bot未在该群聊中...") - await bots[bot_id].set_group_leave(group_id=param.group_id) - return Result.ok(info="成功处理了请求!") - return Result.warning_("无Bot连接...") - except Exception as e: - logger.error("调用API错误", "/leave_group", e=e) - return Result.fail(f"{type(e)}: {e}") - - -@router.post("/delete_friend", dependencies=[authentication()], description="删除好友") -async def _(param: DeleteFriend) -> Result: - try: - if bots := nonebot.get_bots(): - bot_id = param.bot_id - platform = PlatformUtils.get_platform(bots[bot_id]) - if platform != "qq": - return Result.warning_("该平台不支持删除好友操作...") - friend_list = await bots[bot_id].get_friend_list() - if param.user_id not in [str(g["user_id"]) for g in friend_list]: - return Result.warning_("Bot未有其好友...") - await bots[bot_id].delete_friend(user_id=param.user_id) - return Result.ok(info="成功处理了请求!") - return Result.warning_("Bot未连接...") - except Exception as e: - logger.error("调用API错误", "/delete_friend", e=e) - return Result.fail(f"{type(e)}: {e}") - - -@router.get( - "/get_friend_detail", dependencies=[authentication()], description="获取好友详情" -) -async def _(bot_id: str, user_id: str) -> Result: - if bots := nonebot.get_bots(): - if bot_id in bots: - if fd := [ - x - for x in await bots[bot_id].get_friend_list() - if str(x["user_id"]) == user_id - ]: - like_plugin_list = ( - await Statistics.filter(user_id=user_id) - .annotate(count=Count("id")) - .group_by("plugin_name") - .order_by("-count") - .limit(5) - .values_list("plugin_name", "count") - ) - like_plugin = {} - module_list = [x[0] for x in like_plugin_list] - plugins = await PluginInfo.filter(module__in=module_list).all() - module2name = {p.module: p.name for p in plugins} - for data in like_plugin_list: - name = module2name.get(data[0]) or data[0] - like_plugin[name] = data[1] - user = fd[0] - user_detail = UserDetail( - user_id=user_id, - ava_url=AVA_URL.format(user_id), - nickname=user["nickname"], - remark=user["remark"], - is_ban=await BanConsole.is_ban(user_id), - chat_count=await ChatHistory.filter(user_id=user_id).count(), - call_count=await Statistics.filter(user_id=user_id).count(), - like_plugin=like_plugin, - ) - return Result.ok(user_detail) - else: - return Result.warning_("未添加指定好友...") - return Result.warning_("无Bot连接...") - - -@router.get( - "/get_group_detail", dependencies=[authentication()], description="获取群组详情" -) -async def _(bot_id: str, group_id: str) -> Result: - if bots := nonebot.get_bots(): - if bot_id in bots: - group = await GroupConsole.get_or_none(group_id=group_id) - if not group: - return Result.warning_("指定群组未被收录...") - like_plugin_list = ( - await Statistics.filter(group_id=group_id) - .annotate(count=Count("id")) - .group_by("plugin_name") - .order_by("-count") - .limit(5) - .values_list("plugin_name", "count") - ) - like_plugin = {} - plugins = await PluginInfo.all() - module2name = {p.module: p.name for p in plugins} - for data in like_plugin_list: - name = module2name.get(data[0]) or data[0] - like_plugin[name] = data[1] - close_plugins = [] - if group.block_plugin: - for module in group.block_plugin.split(","): - module_ = module.replace(":super", "") - is_super_block = module.endswith(":super") - plugin = Plugin( - module=module_, - plugin_name=module, - is_super_block=is_super_block, - ) - plugin.plugin_name = module2name.get(module) or module - close_plugins.append(plugin) - all_task = await TaskInfo.annotate().values_list("module", "name") - task_module2name = {x[0]: x[1] for x in all_task} - task_list = [] - if group.block_task: - split_task = group.block_task.split(",") - for task in all_task: - task_list.append( - Task( - name=task[0], - zh_name=task_module2name.get(task[0]) or task[0], - status=task[0] not in split_task, - ) - ) - else: - for task in all_task: - task_list.append( - Task( - name=task[0], - zh_name=task_module2name.get(task[0]) or task[0], - status=True, - ) - ) - group_detail = GroupDetail( - group_id=group_id, - ava_url=GROUP_AVA_URL.format(group_id, group_id), - name=group.group_name, - member_count=group.member_count, - max_member_count=group.max_member_count, - chat_count=await ChatHistory.filter(group_id=group_id).count(), - call_count=await Statistics.filter(group_id=group_id).count(), - like_plugin=like_plugin, - level=group.level, - status=group.status, - close_plugins=close_plugins, - task=task_list, - ) - return Result.ok(group_detail) - else: - return Result.warning_("未添加指定群组...") - return Result.warning_("无Bot连接...") - - -@router.post( - "/send_message", dependencies=[authentication()], description="获取群组详情" -) -async def _(param: SendMessage) -> Result: - if bots := nonebot.get_bots(): - if param.bot_id in bots: - platform = PlatformUtils.get_platform(bots[param.bot_id]) - if platform != "qq": - return Result.warning_("暂不支持该平台...") - try: - if param.user_id: - await bots[param.bot_id].send_private_msg( - user_id=str(param.user_id), message=param.message - ) - else: - await bots[param.bot_id].send_group_msg( - group_id=str(param.group_id), message=param.message - ) - except Exception as e: - return Result.fail(str(e)) - return Result.ok("发送成功!") - return Result.warning_("指定Bot未连接...") - return Result.warning_("无Bot连接...") diff --git a/zhenxun/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/zhenxun/plugins/web_ui/api/tabs/plugin_manage/__init__.py deleted file mode 100644 index 2608eb0c..00000000 --- a/zhenxun/plugins/web_ui/api/tabs/plugin_manage/__init__.py +++ /dev/null @@ -1,190 +0,0 @@ -import re - -import cattrs -from fastapi import APIRouter, Query - -from zhenxun.configs.config import Config -from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import BlockType, PluginType - -from ....base_model import Result -from ....utils import authentication -from .model import ( - PluginConfig, - PluginCount, - PluginDetail, - PluginInfo, - PluginSwitch, - UpdatePlugin, -) - -router = APIRouter(prefix="/plugin") - - -@router.get( - "/get_plugin_list", dependencies=[authentication()], deprecated="获取插件列表" # type: ignore -) -async def _( - plugin_type: list[PluginType] = Query(None), menu_type: str | None = None -) -> Result: - try: - plugin_list: list[PluginInfo] = [] - query = DbPluginInfo - if plugin_type: - query = query.filter(plugin_type__in=plugin_type, load_status=True) - if menu_type: - query = query.filter(menu_type=menu_type) - plugins = await query.all() - for plugin in plugins: - plugin_info = PluginInfo( - module=plugin.module, - plugin_name=plugin.name, - default_status=plugin.default_status, - limit_superuser=plugin.limit_superuser, - cost_gold=plugin.cost_gold, - menu_type=plugin.menu_type, - version=plugin.version or "0", - level=plugin.level, - status=plugin.status, - author=plugin.author, - ) - plugin_list.append(plugin_info) - except Exception as e: - logger.error("调用API错误", "/get_plugins", e=e) - return Result.fail(f"{type(e)}: {e}") - return Result.ok(plugin_list, "拿到了新鲜出炉的数据!") - - -@router.get( - "/get_plugin_count", dependencies=[authentication()], deprecated="获取插件数量" # type: ignore -) -async def _() -> Result: - plugin_count = PluginCount() - plugin_count.normal = await DbPluginInfo.filter( - plugin_type=PluginType.NORMAL, load_status=True - ).count() - plugin_count.admin = await DbPluginInfo.filter( - plugin_type__in=[PluginType.ADMIN, PluginType.SUPER_AND_ADMIN], load_status=True - ).count() - plugin_count.superuser = await DbPluginInfo.filter( - plugin_type__in=[PluginType.SUPERUSER, PluginType.SUPER_AND_ADMIN], - load_status=True, - ).count() - plugin_count.other = await DbPluginInfo.filter( - plugin_type__in=[PluginType.HIDDEN, PluginType.DEPENDANT], load_status=True - ).count() - return Result.ok(plugin_count) - - -@router.post( - "/update_plugin", dependencies=[authentication()], description="更新插件参数" -) -async def _(plugin: UpdatePlugin) -> Result: - try: - db_plugin = await DbPluginInfo.get_or_none( - module=plugin.module, load_status=True - ) - if not db_plugin: - return Result.fail("插件不存在...") - db_plugin.default_status = plugin.default_status - db_plugin.limit_superuser = plugin.limit_superuser - db_plugin.cost_gold = plugin.cost_gold - db_plugin.level = plugin.level - db_plugin.menu_type = plugin.menu_type - db_plugin.block_type = plugin.block_type - if plugin.block_type == BlockType.ALL: - db_plugin.status = False - else: - db_plugin.status = True - await db_plugin.save() - # 配置项 - if plugin.configs and (configs := Config.get(plugin.module)): - for key in plugin.configs: - if c := configs.configs.get(key): - value = plugin.configs[key] - if c.type and value is not None: - value = cattrs.structure(value, c.type) - Config.set_config(plugin.module, key, value) - except Exception as e: - logger.error("调用API错误", "/update_plugins", e=e) - return Result.fail(f"{type(e)}: {e}") - return Result.ok(info="已经帮你写好啦!") - - -@router.post("/change_switch", dependencies=[authentication()], description="开关插件") -async def _(param: PluginSwitch) -> Result: - db_plugin = await DbPluginInfo.get_or_none(module=param.module, load_status=True) - if not db_plugin: - return Result.fail("插件不存在...") - if not param.status: - db_plugin.block_type = BlockType.ALL - db_plugin.status = False - else: - db_plugin.block_type = None - db_plugin.status = True - await db_plugin.save() - return Result.ok(info="成功改变了开关状态!") - - -@router.get( - "/get_plugin_menu_type", dependencies=[authentication()], description="获取插件类型" -) -async def _() -> Result: - menu_type_list = [] - result = await DbPluginInfo.annotate().values_list("menu_type", flat=True) - for r in result: - if r not in menu_type_list and r: - menu_type_list.append(r) - return Result.ok(menu_type_list) - - -@router.get("/get_plugin", dependencies=[authentication()], description="获取插件详情") -async def _(module: str) -> Result: - db_plugin = await DbPluginInfo.get_or_none(module=module, load_status=True) - if not db_plugin: - return Result.fail("插件不存在...") - config_list = [] - if config := Config.get(module): - for cfg in config.configs: - type_str = "" - type_inner = None - x = str(config.configs[cfg].type) - r = re.search(r"", str(config.configs[cfg].type)) - if r: - type_str = r.group(1) - else: - r = re.search(r"typing\.(.*)\[(.*)\]", str(config.configs[cfg].type)) - if r: - type_str = r.group(1) - if type_str: - type_str = type_str.lower() - type_inner = r.group(2) - if type_inner: - type_inner = [x.strip() for x in type_inner.split(",")] - config_list.append( - PluginConfig( - module=module, - key=cfg, - value=config.configs[cfg].value, - help=config.configs[cfg].help, - default_value=config.configs[cfg].default_value, - type=type_str, - type_inner=type_inner, # type: ignore - ) - ) - plugin_info = PluginDetail( - module=module, - plugin_name=db_plugin.name, - default_status=db_plugin.default_status, - limit_superuser=db_plugin.limit_superuser, - cost_gold=db_plugin.cost_gold, - menu_type=db_plugin.menu_type, - version=db_plugin.version or "0", - level=db_plugin.level, - status=db_plugin.status, - author=db_plugin.author, - config_list=config_list, - block_type=db_plugin.block_type, - ) - return Result.ok(plugin_info) diff --git a/zhenxun/plugins/what_anime/__init__.py b/zhenxun/plugins/what_anime/__init__.py deleted file mode 100644 index d3b91876..00000000 --- a/zhenxun/plugins/what_anime/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma -from nonebot_plugin_alconna import Image as alcImg -from nonebot_plugin_alconna import Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import get_anime - -__plugin_meta__ = PluginMetadata( - name="识番", - description="以图识番", - usage=""" - usage: - api.trace.moe 以图识番 - 指令: - 识番 [图片] - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", menu_type="一些工具" - ).dict(), -) - - -_matcher = on_alconna(Alconna("识番", Args["image?", alcImg]), block=True, priority=5) - - -@_matcher.handle() -async def _(image: Match[alcImg]): - if image.available: - _matcher.set_path_arg("image", image.result) - - -@_matcher.got_path("image", prompt="图来!") -async def _( - session: EventSession, - arparma: Arparma, - image: alcImg, -): - if not image.url: - await MessageUtils.build_message("图片url为空...").finish() - await MessageUtils.build_message("开始识别...").send() - anime_data_report = await get_anime(image.url) - if anime_data_report: - await MessageUtils.build_message(anime_data_report).send(reply_to=True) - logger.info( - f" 识番 {image.url} --> {anime_data_report}", - arparma.header_result, - session=session, - ) - else: - logger.info( - f"识番 {image.url} 未找到...", arparma.header_result, session=session - ) - await MessageUtils.build_message(f"没有寻找到该番剧,果咩..").send( - reply_to=True - ) diff --git a/zhenxun/plugins/what_anime/data_source.py b/zhenxun/plugins/what_anime/data_source.py deleted file mode 100644 index 15801f62..00000000 --- a/zhenxun/plugins/what_anime/data_source.py +++ /dev/null @@ -1,47 +0,0 @@ -import time - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - - -async def get_anime(anime: str) -> str: - s_time = time.time() - url = "https://api.trace.moe/search?anilistInfo&url={}".format(anime) - logger.debug("[info]Now starting get the {}".format(url)) - try: - anime_json = (await AsyncHttpx.get(url)).json() - if not anime_json["error"]: - if anime_json == "Error reading imagenull": - return "图像源错误,注意必须是静态图片哦" - repass = "" - # 拿到动漫 中文名 - for anime in anime_json["result"][:5]: - synonyms = anime["anilist"]["synonyms"] - for x in synonyms: - _count_ch = 0 - for word in x: - if "\u4e00" <= word <= "\u9fff": - _count_ch += 1 - if _count_ch > 3: - anime_name = x - break - else: - anime_name = anime["anilist"]["title"]["native"] - episode = anime["episode"] - from_ = int(anime["from"]) - m, s = divmod(from_, 60) - similarity = anime["similarity"] - putline = "[ {} ][{}][{}:{}] 相似度:{:.2%}".format( - anime_name, - episode if episode else "?", - m, - s, - similarity, - ) - repass += putline + "\n" - return f"耗时 {int(time.time() - s_time)} 秒\n" + repass[:-1] - else: - return f'访问错误 error:{anime_json["error"]}' - except Exception as e: - logger.error(f"识番发生错误", e=e) - return "发生了奇怪的错误,那就没办法了,再试一次?" diff --git a/zhenxun/plugins/word_bank/__init__.py b/zhenxun/plugins/word_bank/__init__.py deleted file mode 100644 index c3ef6546..00000000 --- a/zhenxun/plugins/word_bank/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from pathlib import Path - -import nonebot - -from zhenxun.configs.config import Config - -Config.add_plugin_config( - "word_bank", - "WORD_BANK_LEVEL", - 5, - help="设置增删词库的权限等级", - default_value=5, - type=int, -) -Config.set_name("word_bank", "词库问答") - - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/word_bank/_config.py b/zhenxun/plugins/word_bank/_config.py deleted file mode 100644 index 3d074c18..00000000 --- a/zhenxun/plugins/word_bank/_config.py +++ /dev/null @@ -1,24 +0,0 @@ -from zhenxun.configs.path_config import DATA_PATH - -data_dir = DATA_PATH / "word_bank" -data_dir.mkdir(parents=True, exist_ok=True) - -scope2int = { - "全局": 0, - "群聊": 1, - "私聊": 2, -} - -type2int = { - "精准": 0, - "模糊": 1, - "正则": 2, - "图片": 3, -} - -int2type = { - 0: "精准", - 1: "模糊", - 2: "正则", - 3: "图片", -} diff --git a/zhenxun/plugins/word_bank/_data_source.py b/zhenxun/plugins/word_bank/_data_source.py deleted file mode 100644 index c4ed9b12..00000000 --- a/zhenxun/plugins/word_bank/_data_source.py +++ /dev/null @@ -1,288 +0,0 @@ -from nonebot_plugin_alconna import At -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Image -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import Text as alcText -from nonebot_plugin_alconna import UniMessage, UniMsg - -from zhenxun.utils.image_utils import ImageTemplate -from zhenxun.utils.message import MessageUtils - -from ._model import WordBank - - -def get_img_and_at_list(message: UniMsg) -> tuple[list[str], list[str]]: - """获取图片和at数据 - - 参数: - message: UniMsg - - 返回: - tuple[list[str], list[str]]: 图片列表,at列表 - """ - img_list, at_list = [], [] - for msg in message: - if isinstance(msg, alcImage): - img_list.append(msg.url) - elif isinstance(msg, alcAt): - at_list.append(msg.target) - return img_list, at_list - - -def get_problem(message: UniMsg) -> str: - """获取问题内容 - - 参数: - message: UniMsg - - 返回: - str: 问题文本 - """ - problem = "" - a, b = True, True - for msg in message: - if isinstance(msg, alcText) or isinstance(msg, str): - msg = str(msg) - if "问" in str(msg) and a: - a = False - split_text = msg.split("问") - if len(split_text) > 1: - problem += "问".join(split_text[1:]) - if b: - if "答" in problem: - b = False - problem = problem.split("答")[0] - elif "答" in msg and b: - b = False - # problem += "答".join(msg.split("答")[:-1]) - problem += msg.split("答")[0] - if not a and not b: - break - if isinstance(msg, alcAt): - problem += f"[at:{msg.target}]" - return problem - - -def get_answer(message: UniMsg) -> UniMessage | None: - """获取at时回答 - - 参数: - message: UniMsg - - 返回: - str: 回答内容 - """ - temp_message = None - answer = "" - index = 0 - for msg in message: - index += 1 - if isinstance(msg, alcText) or isinstance(msg, str): - msg = str(msg) - if "答" in msg: - answer += "答".join(msg.split("答")[1:]) - break - if answer: - temp_message = message[index:] - temp_message.insert(0, alcText(answer)) - return temp_message - - -class WordBankManage: - - @classmethod - async def update_word( - cls, - replace: str, - problem: str = "", - index: int | None = None, - group_id: str | None = None, - word_scope: int = 1, - ) -> tuple[str, str]: - """修改群词条 - - 参数: - params: 参数 - group_id: 群号 - word_scope: 词条范围 - - 返回: - tuple[str, str]: 处理消息,替换的旧词条 - """ - return await cls.__word_handle( - problem, group_id, "update", index, None, word_scope, replace - ) - - @classmethod - async def delete_word( - cls, - problem: str, - index: int | None = None, - aid: int | None = None, - group_id: str | None = None, - word_scope: int = 1, - ) -> tuple[str, str]: - """删除群词条 - - 参数: - params: 参数 - index: 指定下标 - aid: 指定回答下标 - group_id: 群号 - word_scope: 词条范围 - - 返回: - tuple[str, str]: 处理消息,空 - """ - return await cls.__word_handle( - problem, group_id, "delete", index, aid, word_scope - ) - - @classmethod - async def __word_handle( - cls, - problem: str, - group_id: str | None, - handle_type: str, - index: int | None = None, - aid: int | None = None, - word_scope: int = 0, - replace_problem: str = "", - ) -> tuple[str, str]: - """词条操作 - - 参数: - problem: 参数 - group_id: 群号 - handle_type: 类型 - index: 指定回答下标 - aid: 指定回答下标 - word_scope: 词条范围 - replace_problem: 替换问题内容 - - 返回: - tuple[str, str]: 处理消息,替换的旧词条 - """ - if index is not None: - problem, code = await cls.__get_problem_str(index, group_id, word_scope) - if code != 200: - return problem, "" - if handle_type == "delete": - if index: - problem, _problem_list = await WordBank.get_problem_all_answer( - problem, None, group_id, word_scope - ) - if not _problem_list: - return problem, "" - if await WordBank.delete_group_problem(problem, group_id, aid, word_scope): # type: ignore - return "删除词条成功!", "" - return "词条不存在", "" - if handle_type == "update": - old_problem = await WordBank.update_group_problem( - problem, replace_problem, group_id, word_scope=word_scope - ) - return f"修改词条成功!\n{old_problem} -> {replace_problem}", old_problem - return "类型错误", "" - - @classmethod - async def __get_problem_str( - cls, idx: int, group_id: str | None = None, word_scope: int = 1 - ) -> tuple[str, int]: - """通过id获取问题字符串 - - 参数: - idx: 下标 - group_id: 群号 - word_scope: 获取类型 - """ - if word_scope in [0, 2]: - all_problem = await WordBank.get_problem_by_scope(word_scope) - elif group_id: - all_problem = await WordBank.get_group_all_problem(group_id) - else: - raise Exception("词条类型与群组id不能为空") - if idx < 0 or idx >= len(all_problem): - return "问题下标id必须在范围内", 999 - return all_problem[idx][0], 200 - - @classmethod - async def show_word( - cls, - problem: str | None, - index: int | None = None, - group_id: str | None = None, - word_scope: int | None = 1, - ) -> UniMessage: - """获取群词条 - - 参数: - problem: 问题 - group_id: 群组id - word_scope: 词条范围 - index: 指定回答下标 - """ - if problem or index != None: - msg_list = [] - problem, _problem_list = await WordBank.get_problem_all_answer( - problem, # type: ignore - index, - group_id if group_id is not None else None, - word_scope, - ) - if not _problem_list: - return MessageUtils.build_message(problem) - for msg in _problem_list: - _text = str(msg) - if isinstance(msg, At): - _text = f"[at:{msg.target}]" - elif isinstance(msg, Image): - _text = msg.url or msg.path - elif isinstance(msg, list): - _text = [] - for m in msg: - __text = str(m) - if isinstance(m, At): - __text = f"[at:{m.target}]" - elif isinstance(m, Image): - # TODO: 显示词条回答图片 - # __text = (m.data["image"], 30, 30) - __text = "[图片]" - _text.append(__text) - _text = "".join(_text) - msg_list.append(_text) - column_name = ["序号", "回答内容"] - data_list = [] - for index, msg in enumerate(msg_list): - data_list.append([index, msg]) - template_image = await ImageTemplate.table_page( - f"词条 {problem} 的回答", None, column_name, data_list - ) - return MessageUtils.build_message(template_image) - else: - result = [] - if group_id: - _problem_list = await WordBank.get_group_all_problem(group_id) - elif word_scope is not None: - _problem_list = await WordBank.get_problem_by_scope(word_scope) - else: - raise Exception("群组id和词条范围不能都为空") - global_problem_list = await WordBank.get_problem_by_scope(0) - if not _problem_list and not global_problem_list: - return MessageUtils.build_message("未收录任何词条...") - column_name = ["序号", "关键词", "匹配类型", "收录用户"] - data_list = [list(s) for s in _problem_list] - for i in range(len(data_list)): - data_list[i].insert(0, i) - group_image = await ImageTemplate.table_page( - "群组内词条" if group_id else "私聊词条", None, column_name, data_list - ) - result.append(group_image) - if global_problem_list: - data_list = [list(s) for s in global_problem_list] - for i in range(len(data_list)): - data_list[i].insert(0, i) - global_image = await ImageTemplate.table_page( - "全局词条", None, column_name, data_list - ) - result.append(global_image) - return MessageUtils.build_message(result) diff --git a/zhenxun/plugins/word_bank/_model.py b/zhenxun/plugins/word_bank/_model.py deleted file mode 100644 index f31620a2..00000000 --- a/zhenxun/plugins/word_bank/_model.py +++ /dev/null @@ -1,566 +0,0 @@ -import random -import re -import time -import uuid -from datetime import datetime -from typing import Any - -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import Text as alcText -from nonebot_plugin_alconna import UniMessage -from tortoise import Tortoise, fields -from tortoise.expressions import Q -from typing_extensions import Self - -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.services.db_context import Model -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import get_img_hash -from zhenxun.utils.message import MessageUtils - -from ._config import int2type - -path = DATA_PATH / "word_bank" - - -class WordBank(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255, null=True) - """群聊id""" - word_scope = fields.IntField(default=0) - """生效范围 0: 全局 1: 群聊 2: 私聊""" - word_type = fields.IntField(default=0) - """词条类型 0: 完全匹配 1: 模糊 2: 正则 3: 图片""" - status = fields.BooleanField() - """词条状态""" - problem = fields.TextField() - """问题,为图片时使用图片hash""" - answer = fields.TextField() - """回答""" - placeholder = fields.TextField(null=True) - """占位符""" - image_path = fields.TextField(null=True) - """使用图片作为问题时图片存储的路径""" - to_me = fields.CharField(255, null=True) - """昵称开头时存储的昵称""" - create_time = fields.DatetimeField(auto_now=True) - """创建时间""" - update_time = fields.DatetimeField(auto_now_add=True) - """更新时间""" - platform = fields.CharField(255, default="qq") - """平台""" - author = fields.CharField(255, null=True, default="") - """收录人""" - - class Meta: - table = "word_bank2" - table_description = "词条数据库" - - @classmethod - async def exists( - cls, - user_id: str | None, - group_id: str | None, - problem: str, - answer: str | None, - word_scope: int | None = None, - word_type: int | None = None, - ) -> bool: - """检测问题是否存在 - - 参数: - user_id: 用户id - group_id: 群号 - problem: 问题 - answer: 回答 - word_scope: 词条范围 - word_type: 词条类型 - """ - query = cls.filter(problem=problem) - if user_id: - query = query.filter(user_id=user_id) - if group_id: - query = query.filter(group_id=group_id) - if answer: - query = query.filter(answer=answer) - if word_type is not None: - query = query.filter(word_type=word_type) - if word_scope is not None: - query = query.filter(word_scope=word_scope) - return await query.exists() - - @classmethod - async def add_problem_answer( - cls, - user_id: str, - group_id: str | None, - word_scope: int, - word_type: int, - problem: str, - answer: list[str | alcText | alcAt | alcImage], - to_me_nickname: str | None = None, - platform: str = "", - author: str = "", - ): - """添加或新增一个问答 - - 参数: - user_id: 用户id - group_id: 群号 - word_scope: 词条范围, - word_type: 词条类型, - problem: 问题, 为图片时是URl - answer: 回答 - to_me_nickname: at真寻名称 - platform: 所属平台 - author: 收录人id - """ - # 对图片做额外处理 - image_path = None - if word_type == 3: - _uuid = uuid.uuid1() - _file = path / "problem" / f"{group_id}" / f"{user_id}_{_uuid}.jpg" - _file.parent.mkdir(exist_ok=True, parents=True) - await AsyncHttpx.download_file(problem, _file) - problem = get_img_hash(_file) - image_path = f"problem/{group_id}/{user_id}_{_uuid}.jpg" - new_answer, placeholder_list = await cls._answer2format( - answer, user_id, group_id - ) - if not await cls.exists( - user_id, group_id, problem, new_answer, word_scope, word_type - ): - await cls.create( - user_id=user_id, - group_id=group_id, - word_scope=word_scope, - word_type=word_type, - status=True, - problem=str(problem).strip(), - answer=new_answer, - image_path=image_path, - placeholder=",".join(placeholder_list), - create_time=datetime.now().replace(microsecond=0), - update_time=datetime.now().replace(microsecond=0), - to_me=to_me_nickname, - platform=platform, - author=author, - ) - - @classmethod - async def _answer2format( - cls, - answer: list[str | alcText | alcAt | alcImage], - user_id: str, - group_id: str | None, - ) -> tuple[str, list[Any]]: - """将特殊字段转化为占位符,图片,at等 - - 参数: - answer: 回答内容 - user_id: 用户id - group_id: 群号 - - 返回: - tuple[str, list[Any]]: 替换后的文本回答内容,占位符 - """ - placeholder_list = [] - text = "" - index = 0 - for seg in answer: - placeholder = uuid.uuid1() - if isinstance(seg, str): - text += seg - elif isinstance(seg, alcText): - text += seg.text - elif seg.type == "face": # TODO: face貌似无用... - text += f"[face:placeholder_{placeholder}]" - placeholder_list.append(seg.data["id"]) - elif isinstance(seg, alcAt): - text += f"[at:placeholder_{placeholder}]" - placeholder_list.append(seg.target) - elif isinstance(seg, alcImage) and seg.url: - text += f"[image:placeholder_{placeholder}]" - index += 1 - _file = ( - path - / "answer" - / f"{group_id or user_id}" - / f"{user_id}_{placeholder}.jpg" - ) - _file.parent.mkdir(exist_ok=True, parents=True) - await AsyncHttpx.download_file(seg.url, _file) - placeholder_list.append( - f"answer/{group_id or user_id}/{user_id}_{placeholder}.jpg" - ) - return text, placeholder_list - - @classmethod - async def _format2answer( - cls, - problem: str, - answer: str, - user_id: int, - group_id: int, - query: Self | None = None, - ) -> UniMessage: - """将占位符转换为实际内容 - - 参数: - problem: 问题内容 - answer: 回答内容 - user_id: 用户id - group_id: 群组id - """ - result_list = [] - if not query: - query = await cls.get_or_none( - problem=problem, - user_id=user_id, - group_id=group_id, - answer=answer, - ) - if not answer: - answer = str(query.answer) # type: ignore - if query and query.placeholder: - type_list = re.findall(rf"\[(.*?):placeholder_.*?]", answer) - answer_split = re.split(rf"\[.*:placeholder_.*?]", answer) - placeholder_split = query.placeholder.split(",") - for index, ans in enumerate(answer_split): - result_list.append(ans) - if index < len(type_list): - t = type_list[index] - p = placeholder_split[index] - if t == "image": - result_list.append(path / p) - elif t == "at": - result_list.append(alcAt(flag="user", target=p)) - return MessageUtils.build_message(result_list) - return MessageUtils.build_message(answer) - - @classmethod - async def check_problem( - cls, - group_id: str | None, - problem: str, - word_scope: int | None = None, - word_type: int | None = None, - ) -> Any: - """检测是否包含该问题并获取所有回答 - - 参数: - group_id: 群组id - problem: 问题内容 - word_scope: 词条范围 - word_type: 词条类型 - """ - query = cls - if group_id: - if word_scope: - query = query.filter(word_scope=word_scope) - else: - query = query.filter(Q(group_id=group_id) | Q(word_scope=0)) - else: - query = query.filter(Q(word_scope=2) | Q(word_scope=0)) - if word_type: - query = query.filter(word_scope=word_type) - # 完全匹配 - if data_list := await query.filter( - Q(Q(word_type=0) | Q(word_type=3)), Q(problem=problem) - ).all(): - return data_list - db = Tortoise.get_connection("default") - # 模糊匹配 - sql = query.filter(word_type=1).sql() + " and POSITION(problem in $1) > 0" - data_list = await db.execute_query_dict(sql, [problem]) - if data_list: - return [cls(**data) for data in data_list] - # 正则 - sql = ( - query.filter(word_type=2, word_scope__not=999).sql() + " and $1 ~ problem;" - ) - data_list = await db.execute_query_dict(sql, [problem]) - if data_list: - return [cls(**data) for data in data_list] - return None - - @classmethod - async def get_answer( - cls, - group_id: str | None, - problem: str, - word_scope: int | None = None, - word_type: int | None = None, - ) -> UniMessage | None: - """根据问题内容获取随机回答 - - 参数: - user_id: 用户id - group_id: 群组id - problem: 问题内容 - word_scope: 词条范围 - word_type: 词条类型 - """ - data_list = await cls.check_problem(group_id, problem, word_scope, word_type) - if data_list: - random_answer = random.choice(data_list) - if random_answer.word_type == 2: - r = re.search(random_answer.problem, problem) - has_placeholder = re.search(rf"\$(\d)", random_answer.answer) - if r and r.groups() and has_placeholder: - pats = re.sub(r"\$(\d)", r"\\\1", random_answer.answer) - random_answer.answer = re.sub(random_answer.problem, pats, problem) - return ( - await cls._format2answer( - random_answer.problem, - random_answer.answer, - random_answer.user_id, - random_answer.group_id, - random_answer, - ) - if random_answer.placeholder - else MessageUtils.build_message(random_answer.answer) - ) - - @classmethod - async def get_problem_all_answer( - cls, - problem: str, - index: int | None = None, - group_id: str | None = None, - word_scope: int | None = 0, - ) -> tuple[str, list[UniMessage]]: - """获取指定问题所有回答 - - 参数: - problem: 问题 - index: 下标 - group_id: 群号 - word_scope: 词条范围 - - 返回: - tuple[str, list[UniMessage]]: 问题和所有回答 - """ - if index is not None: - # TODO: group_by和order_by不能同时使用 - if group_id: - _problem = ( - await cls.filter(group_id=group_id).order_by("create_time") - # .group_by("problem") - .values_list("problem", flat=True) - ) - else: - _problem = ( - await cls.filter(word_scope=(word_scope or 0)).order_by( - "create_time" - ) - # .group_by("problem") - .values_list("problem", flat=True) - ) - # if index is None and problem not in _problem: - # return "词条不存在...", [] - sort_problem = [] - for p in _problem: - if p not in sort_problem: - sort_problem.append(p) - if index > len(sort_problem) - 1: - return "下标错误,必须小于问题数量...", [] - problem = sort_problem[index] # type: ignore - f = cls.filter(problem=problem, word_scope=(word_scope or 0)) - if group_id: - f = f.filter(group_id=group_id) - answer_list = await f.all() - if not answer_list: - return "词条不存在...", [] - return problem, [await cls._format2answer("", "", 0, 0, a) for a in answer_list] - - @classmethod - async def delete_group_problem( - cls, - problem: str, - group_id: str | None, - index: int | None = None, - word_scope: int = 1, - ): - """删除指定问题全部或指定回答 - - 参数: - problem: 问题文本 - group_id: 群号 - index: 回答下标 - word_scope: 词条范围 - """ - if await cls.exists(None, group_id, problem, None, word_scope): - if index is not None: - if group_id: - query = await cls.filter( - group_id=group_id, problem=problem, word_scope=word_scope - ).all() - else: - query = await cls.filter( - word_scope=word_scope, problem=problem - ).all() - await query[index].delete() - else: - if group_id: - await WordBank.filter( - group_id=group_id, problem=problem, word_scope=word_scope - ).delete() - else: - await WordBank.filter( - word_scope=word_scope, problem=problem - ).delete() - return True - return False - - @classmethod - async def update_group_problem( - cls, - problem: str, - replace_str: str, - group_id: str | None, - index: int | None = None, - word_scope: int = 1, - ) -> str: - """修改词条问题 - - 参数: - problem: 问题 - replace_str: 替换问题 - group_id: 群号 - index: 问题下标 - word_scope: 词条范围 - - 返回: - str: 修改前的问题 - """ - if index is not None: - if group_id: - query = await cls.filter(group_id=group_id, problem=problem).all() - else: - query = await cls.filter(word_scope=word_scope, problem=problem).all() - tmp = query[index].problem - query[index].problem = replace_str - await query[index].save(update_fields=["problem"]) - return tmp - else: - if group_id: - await cls.filter(group_id=group_id, problem=problem).update( - problem=replace_str - ) - else: - await cls.filter(word_scope=word_scope, problem=problem).update( - problem=replace_str - ) - return problem - - @classmethod - async def get_group_all_problem(cls, group_id: str) -> list[tuple[Any | str]]: - """获取群聊所有词条 - - 参数: - group_id: 群号 - """ - return cls._handle_problem( - await cls.filter(group_id=group_id).order_by("create_time").all() # type: ignore - ) - - @classmethod - async def get_problem_by_scope(cls, word_scope: int): - """通过词条范围获取词条 - - 参数: - word_scope: 词条范围 - """ - return cls._handle_problem( - await cls.filter(word_scope=word_scope).order_by("create_time").all() # type: ignore - ) - - @classmethod - async def get_problem_by_type(cls, word_type: int): - """通过词条类型获取词条 - - 参数: - word_type: 词条类型 - """ - return cls._handle_problem( - await cls.filter(word_type=word_type).order_by("create_time").all() # type: ignore - ) - - @classmethod - def _handle_problem(cls, problem_list: list["WordBank"]): - """格式化处理问题 - - 参数: - msg_list: 消息列表 - """ - _tmp = [] - result_list = [] - for q in problem_list: - if q.problem not in _tmp: - # TODO: 获取收录人名称 - problem = ( - (path / q.image_path, 30, 30) if q.image_path else q.problem, - int2type[q.word_type], - # q.author, - "-", - ) - result_list.append(problem) - _tmp.append(q.problem) - return result_list - - @classmethod - async def _move( - cls, - user_id: str, - group_id: str | None, - problem: str, - answer: str, - placeholder: str, - ): - """旧词条图片移动方法 - - 参数: - user_id: 用户id - group_id: 群号 - problem: 问题 - answer: 回答 - placeholder: 占位符 - """ - word_scope = 0 - word_type = 0 - # 对图片做额外处理 - if not await cls.exists( - user_id, group_id, problem, answer, word_scope, word_type - ): - await cls.create( - user_id=user_id, - group_id=group_id, - word_scope=word_scope, - word_type=word_type, - status=True, - problem=problem, - answer=answer, - image_path=None, - placeholder=placeholder, - create_time=datetime.now().replace(microsecond=0), - update_time=datetime.now().replace(microsecond=0), - ) - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE word_bank2 ADD to_me varchar(255);", # 添加 to_me 字段 - "ALTER TABLE word_bank2 ALTER COLUMN create_time TYPE timestamp with time zone USING create_time::timestamp with time zone;", - "ALTER TABLE word_bank2 ALTER COLUMN update_time TYPE timestamp with time zone USING update_time::timestamp with time zone;", - "ALTER TABLE word_bank2 RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE word_bank2 ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE word_bank2 ALTER COLUMN group_id TYPE character varying(255);", - "ALTER TABLE word_bank2 ADD platform varchar(255) DEFAULT 'qq';", - "ALTER TABLE word_bank2 ADD author varchar(255) DEFAULT '';", - ] diff --git a/zhenxun/plugins/word_bank/_rule.py b/zhenxun/plugins/word_bank/_rule.py deleted file mode 100644 index 3ce032ca..00000000 --- a/zhenxun/plugins/word_bank/_rule.py +++ /dev/null @@ -1,58 +0,0 @@ -from io import BytesIO - -import imagehash -from nonebot.adapters import Bot, Event -from nonebot.typing import T_State -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Text as alcText -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession -from PIL import Image - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - -from ._data_source import get_img_and_at_list -from ._model import WordBank - - -async def check( - bot: Bot, - event: Event, - message: UniMsg, - session: EventSession, - state: T_State, -) -> bool: - text = message.extract_plain_text().strip() - img_list, at_list = get_img_and_at_list(message) - problem = text - if not text and len(img_list) == 1: - try: - r = await AsyncHttpx.get(img_list[0]) - problem = str(imagehash.average_hash(Image.open(BytesIO(r.content)))) - except Exception as e: - logger.warning(f"获取图片失败", "词条检测", session=session, e=e) - if at_list: - temp = "" - # TODO: 支持更多消息类型 - for msg in message: - if isinstance(msg, alcAt): - temp += f"[at:{msg.target}]" - elif isinstance(msg, alcText): - temp += msg.text - problem = temp - if event.is_tome() and bot.config.nickname: - if isinstance(message[0], alcAt) and message[0].target == bot.self_id: - problem = f"[at:{bot.self_id}]" + problem - else: - if problem and bot.config.nickname: - nickname = [ - nk for nk in bot.config.nickname if str(message).startswith(nk) - ] - problem = nickname[0] + problem if nickname else problem - if problem and ( - await WordBank.check_problem(session.id3 or session.id2, problem) is not None - ): - state["problem"] = problem # type: ignore - return True - return False diff --git a/zhenxun/plugins/word_bank/command.py b/zhenxun/plugins/word_bank/command.py deleted file mode 100644 index 64049d30..00000000 --- a/zhenxun/plugins/word_bank/command.py +++ /dev/null @@ -1,47 +0,0 @@ -from nonebot import on_regex -from nonebot_plugin_alconna import Alconna, Args, Option, on_alconna, store_true - -from zhenxun.utils.rules import admin_check, ensure_group - -_add_matcher = on_regex( - r"^(全局|私聊)?添加词条\s*?(模糊|正则|图片)?问\s*?(\S*\s?\S*)\s*?答\s?(\S*)", - priority=5, - block=True, - rule=admin_check("word_bank", "WORD_BANK_LEVEL"), -) - - -_del_matcher = on_alconna( - Alconna( - "删除词条", - Args["problem?", str], - Option("--all", action=store_true, help_text="所有词条"), - Option("--id", Args["index", int], help_text="下标id"), - Option("--aid", Args["answer_id", int], help_text="回答下标id"), - ), - priority=5, - block=True, -) - - -_update_matcher = on_alconna( - Alconna( - "修改词条", - Args["replace", str]["problem?", str], - Option("--id", Args["index", int], help_text="词条id"), - Option("--all", action=store_true, help_text="全局词条"), - ) -) - -_show_matcher = on_alconna( - Alconna( - "显示词条", - Args["problem?", str], - Option("-g|--group", Args["gid", str], help_text="群组id"), - Option("--id", Args["index", int], help_text="词条id"), - Option("--all", action=store_true, help_text="全局词条"), - ), - aliases={"查看词条"}, - priority=5, - block=True, -) diff --git a/zhenxun/plugins/word_bank/message_handle.py b/zhenxun/plugins/word_bank/message_handle.py deleted file mode 100644 index 07a4b518..00000000 --- a/zhenxun/plugins/word_bank/message_handle.py +++ /dev/null @@ -1,31 +0,0 @@ -from nonebot import on_message -from nonebot.plugin import PluginMetadata -from nonebot.typing import T_State -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services import logger -from zhenxun.utils.enum import PluginType - -from ._model import WordBank -from ._rule import check - -__plugin_meta__ = PluginMetadata( - name="词库问答回复操作", - description="", - usage="""""", - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.DEPENDANT - ).dict(), -) - -_matcher = on_message(priority=6, block=True, rule=check) - - -@_matcher.handle() -async def _(session: EventSession, state: T_State): - if problem := state.get("problem"): - gid = session.id3 or session.id2 - if result := await WordBank.get_answer(gid, problem): - await result.send() - logger.info(f"触发词条 {problem}", "词条检测", session=session) diff --git a/zhenxun/plugins/word_bank/word_handle.py b/zhenxun/plugins/word_bank/word_handle.py deleted file mode 100644 index 39894a45..00000000 --- a/zhenxun/plugins/word_bank/word_handle.py +++ /dev/null @@ -1,329 +0,0 @@ -import re -from typing import Any - -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import unescape -from nonebot.exception import FinishedException -from nonebot.internal.params import Arg, ArgStr -from nonebot.params import RegexGroup -from nonebot.plugin import PluginMetadata -from nonebot.typing import T_State -from nonebot_plugin_alconna import AlconnaQuery, Arparma -from nonebot_plugin_alconna import Image -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import Match, Query, UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._config import scope2int, type2int -from ._data_source import WordBankManage, get_answer, get_img_and_at_list, get_problem -from ._model import WordBank -from .command import _add_matcher, _del_matcher, _show_matcher, _update_matcher - -base_config = Config.get("word_bank") - - -__plugin_meta__ = PluginMetadata( - name="词库问答", - description="自定义词条内容随机回复", - usage=""" - usage: - 对指定问题的随机回答,对相同问题可以设置多个不同回答 - 删除词条后每个词条的id可能会变化,请查看后再删除 - 更推荐使用id方式删除 - 问题回答支持的类型:at, image - 查看词条命令:群聊时为 群词条+全局词条,私聊时为 私聊词条+全局词条 - 添加词条正则:添加词条(模糊|正则|图片)?问\s*?(\S*\s?\S*)\s*?答\s?(\S*) - 正则问可以通过$1类推()捕获的组 - 指令: - 添加词条 ?[模糊|正则|图片]问...答...:添加问答词条,可重复添加相同问题的不同回答 - 示例: - 添加词条问你好答你也好 - 添加图片词条问答看看涩图 - 删除词条 ?[问题] ?[序号] ?[回答序号]:删除指定词条指定或全部回答 - 示例: - 删除词条 谁是萝莉 : 删除文字是 谁是萝莉 的词条 - 删除词条 --id 2 : 删除序号为2的词条 - 删除词条 谁是萝莉 --aid 2 : 删除 谁是萝莉 词条的第2个回答 - 删除词条 --id 2 --aid 2 : 删除序号为2词条的第2个回答 - 修改词条 [替换文字] ?[旧词条文字] ?[序号]:修改词条问题 - 示例: - 修改词条 谁是萝莉 谁是萝莉啊? : 将词条 谁是萝莉 修改为 谁是萝莉啊? - 修改词条 谁是萝莉 --id 2 : 将序号为2的词条修改为 谁是萝莉 - 查看词条 ?[问题] ?[序号]:查看全部词条或对应词条回答 - 示例: - 查看词条: - (在群组中使用时): 查看当前群组词条和全局词条 - (在私聊中使用时): 查看当前私聊词条和全局词条 - 查看词条 谁是萝莉 : 查看词条 谁是萝莉 的全部回答 - 查看词条 --id 2 : 查看词条序号为2的全部回答 - 查看词条 谁是萝莉 --all: 查看全局词条 谁是萝莉 的全部回答 - 查看词条 --id 2 --all: 查看全局词条序号为2的全部回答 - """.strip(), - extra=PluginExtraData( - author="HibiKier & yajiwa", - version="0.1", - superuser_help=""" - 在私聊中超级用户额外设置 - 指令: - (全局|私聊)?添加词条\s*?(模糊|正则|图片)?问\s*?(\S*\s?\S*)\s*?答\s?(\S*):添加问答词条,可重复添加相同问题的不同回答 - 全局添加词条 - 私聊添加词条 - (私聊情况下)删除词条: 删除私聊词条 - (私聊情况下)修改词条: 修改私聊词条 - 通过添加参数 --all才指定全局词条 - 示例: - 删除词条 --id 2 --all: 删除全局词条中序号为2的词条 - 用法与普通用法相同 - """, - admin_level=base_config.get("WORD_BANK_LEVEL"), - ).dict(), -) - - -@_add_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - state: T_State, - message: UniMsg, - reg_group: tuple[Any, ...] = RegexGroup(), -): - img_list, at_list = get_img_and_at_list(message) - user_id = session.id1 - group_id = session.id3 or session.id2 - if not group_id and user_id not in bot.config.superusers: - await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) - word_scope, word_type, problem, answer = reg_group - if not word_scope and not group_id: - word_scope = "私聊" - if ( - word_scope - and word_scope in ["全局", "私聊"] - and user_id not in bot.config.superusers - ): - await MessageUtils.build_message("权限不足,无法添加该范围词条...").finish( - reply_to=True - ) - if (not problem or not problem.strip()) and word_type != "图片": - await MessageUtils.build_message("词条问题不能为空!").finish(reply_to=True) - if (not answer or not answer.strip()) and not len(img_list) and not len(at_list): - await MessageUtils.build_message("词条回答不能为空!").finish(reply_to=True) - if word_type != "图片": - state["problem_image"] = "YES" - temp_problem = message.copy() - # answer = message.copy() - # 对at问题对额外处理 - # if at_list: - answer = get_answer(message.copy()) - # text = str(message.pop(0)).split("答", maxsplit=1)[-1].strip() - # temp_problem.insert(0, alcMessageUtils.build_message(text)) - state["word_scope"] = word_scope - state["word_type"] = word_type - state["problem"] = get_problem(temp_problem) - state["answer"] = answer - logger.info( - f"添加词条 范围: {word_scope} 类型: {word_type} 问题: {problem} 回答: {answer}", - "添加词条", - session=session, - ) - - -@_add_matcher.got("problem_image", prompt="请发送该回答设置的问题图片") -async def _( - bot: Bot, - session: EventSession, - message: UniMsg, - word_scope: str | None = ArgStr("word_scope"), - word_type: str | None = ArgStr("word_type"), - problem: str | None = ArgStr("problem"), - answer: Any = Arg("answer"), -): - if not session.id1: - await MessageUtils.build_message("用户id不存在...").finish() - user_id = session.id1 - group_id = session.id3 or session.id2 - try: - if word_type == "图片": - problem = [m for m in message if isinstance(m, alcImage)][0].url - elif word_type == "正则" and problem: - problem = unescape(problem) - try: - re.compile(problem) - except re.error: - await MessageUtils.build_message( - f"添加词条失败,正则表达式 {problem} 非法!" - ).finish(reply_to=True) - # if str(event.user_id) in bot.config.superusers and isinstance(event, PrivateMessageEvent): - # word_scope = "私聊" - nickname = None - if problem and bot.config.nickname: - nickname = [nk for nk in bot.config.nickname if problem.startswith(nk)] - if not problem: - await MessageUtils.build_message("获取问题失败...").finish(reply_to=True) - await WordBank.add_problem_answer( - user_id, - ( - group_id - if group_id and (not word_scope or word_scope == "私聊") - else "0" - ), - scope2int[word_scope] if word_scope else 1, - type2int[word_type] if word_type else 0, - problem, - answer, - nickname[0] if nickname else None, - session.platform, - session.id1, - ) - except Exception as e: - if isinstance(e, FinishedException): - await _add_matcher.finish() - logger.error( - f"添加词条 {problem} 错误...", - "添加词条", - session=session, - e=e, - ) - await MessageUtils.build_message( - f"添加词条 {problem if word_type != '图片' else '图片'} 发生错误!" - ).finish(reply_to=True) - if word_type == "图片": - result = MessageUtils.build_message( - ["添加词条 ", Image(url=problem), " 成功!"] - ) - else: - result = MessageUtils.build_message(f"添加词条 {problem} 成功!") - await result.send() - logger.info( - f"添加词条 {problem} 成功!", - "添加词条", - session=session, - ) - - -@_del_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - problem: Match[str], - index: Match[int], - answer_id: Match[int], - arparma: Arparma, - all: Query[bool] = AlconnaQuery("all.value", False), -): - if not problem.available and not index.available: - await MessageUtils.build_message( - "此命令之后需要跟随指定词条或id,通过“显示词条“查看" - ).finish(reply_to=True) - word_scope = 1 if session.id3 or session.id2 else 2 - if all.result: - word_scope = 0 - if gid := session.id3 or session.id2: - result, _ = await WordBankManage.delete_word( - problem.result, - index.result if index.available else None, - answer_id.result if answer_id.available else None, - gid, - word_scope, - ) - else: - if session.id1 not in bot.config.superusers: - await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) - result, _ = await WordBankManage.delete_word( - problem.result, - index.result if index.available else None, - answer_id.result if answer_id.available else None, - None, - word_scope, - ) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info(f"删除词条: {problem.result}", arparma.header_result, session=session) - - -@_update_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - replace: str, - problem: Match[str], - index: Match[int], - arparma: Arparma, - all: Query[bool] = AlconnaQuery("all.value", False), -): - if not problem.available and not index.available: - await MessageUtils.build_message( - "此命令之后需要跟随指定词条或id,通过“显示词条“查看" - ).finish(reply_to=True) - word_scope = 1 if session.id3 or session.id2 else 2 - if all.result: - word_scope = 0 - if gid := session.id3 or session.id2: - result, old_problem = await WordBankManage.update_word( - replace, - problem.result if problem.available else "", - index.result if index.available else None, - gid, - word_scope, - ) - else: - if session.id1 not in bot.config.superusers: - await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) - result, old_problem = await WordBankManage.update_word( - replace, - problem.result if problem.available else "", - index.result if index.available else None, - session.id3 or session.id2, - word_scope, - ) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"更新词条词条: {old_problem} -> {replace}", - arparma.header_result, - session=session, - ) - - -@_show_matcher.handle() -async def _( - session: EventSession, - problem: Match[str], - index: Match[int], - gid: Match[str], - arparma: Arparma, - all: Query[bool] = AlconnaQuery("all.value", False), -): - word_scope = 1 if session.id3 or session.id2 else 2 - if all.result: - word_scope = 0 - group_id = session.id3 or session.id2 - if gid.available: - group_id = gid.result - if problem.available: - if index.available: - if index.result < 0 or index.result > len( - await WordBank.get_problem_by_scope(2) - ): - await MessageUtils.build_message("id必须在范围内...").finish( - reply_to=True - ) - result = await WordBankManage.show_word( - problem.result, - index.result if index.available else None, - group_id, - word_scope, - ) - else: - result = await WordBankManage.show_word( - None, index.result if index.available else None, group_id, word_scope - ) - await result.send() - logger.info( - f"查看词条回答: {problem.result if problem.available else index.result}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/services/__init__.py b/zhenxun/services/__init__.py index aae2c093..980a7277 100644 --- a/zhenxun/services/__init__.py +++ b/zhenxun/services/__init__.py @@ -1,2 +1,7 @@ -from .db_context import * -from .log import * +from nonebot import require + +require("nonebot_plugin_apscheduler") +require("nonebot_plugin_alconna") +require("nonebot_plugin_session") +require("nonebot_plugin_htmlrender") +require("nonebot_plugin_uninfo") diff --git a/zhenxun/services/db_context.py b/zhenxun/services/db_context.py index 2bf9c109..9a44fa74 100644 --- a/zhenxun/services/db_context.py +++ b/zhenxun/services/db_context.py @@ -1,27 +1,14 @@ -import ujson as json from nonebot.utils import is_coroutine_callable -from tortoise import Tortoise, fields +from tortoise import Tortoise from tortoise.connection import connections from tortoise.models import Model as Model_ -from zhenxun.configs.config import ( - address, - bind, - database, - password, - port, - sql_name, - user, -) -from zhenxun.configs.path_config import DATA_PATH +from zhenxun.configs.config import BotConfig from .log import logger -MODELS: list[str] = [] - SCRIPT_METHOD = [] - -DATABASE_SETTING_FILE = DATA_PATH / "database.json" +MODELS: list[str] = [] class Model(Model_): @@ -29,7 +16,7 @@ class Model(Model_): 自动添加模块 Args: - Model_ (_type_): _description_ + Model_: Model """ def __init_subclass__(cls, **kwargs): @@ -39,62 +26,41 @@ class Model(Model_): SCRIPT_METHOD.append((cls.__module__, func)) -class TestSQL(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" +class DbUrlIsNode(Exception): + """ + 数据库链接地址为空 + """ - class Meta: - abstract = True - table = "test_sql" - table_description = "执行SQL命令,不记录任何数据" + pass + + +class DbConnectError(Exception): + """ + 数据库连接错误 + """ + + pass async def init(): - if DATABASE_SETTING_FILE.exists(): - with open(DATABASE_SETTING_FILE, "r", encoding="utf-8") as f: - setting_data = json.load(f) - else: - i_bind = bind - if not i_bind and any([user, password, address, port, database]): - i_bind = f"{sql_name}://{user}:{password}@{address}:{port}/{database}" - setting_data = { - "bind": i_bind, - "sql_name": sql_name, - "user": user, - "password": password, - "address": address, - "port": port, - "database": database, - } - with open(DATABASE_SETTING_FILE, "w", encoding="utf-8") as f: - json.dump(setting_data, f, ensure_ascii=False, indent=4) - i_bind = setting_data.get("bind") - _sql_name = setting_data.get("sql_name") - _user = setting_data.get("user") - _password = setting_data.get("password") - _address = setting_data.get("address") - _port = setting_data.get("port") - _database = setting_data.get("database") - if not i_bind and not any([_user, _password, _address, _port, _database]): - raise ValueError("\n数据库配置未填写...") - if not i_bind: - i_bind = f"{_sql_name}://{_user}:{_password}@{_address}:{_port}/{_database}" + if not BotConfig.db_url: + raise DbUrlIsNode("数据库配置为空,请在.env.dev中配置DB_URL...") try: await Tortoise.init( - db_url=i_bind, modules={"models": MODELS}, timezone="Asia/Shanghai" + db_url=BotConfig.db_url, + modules={"models": MODELS}, + timezone="Asia/Shanghai", ) if SCRIPT_METHOD: db = Tortoise.get_connection("default") logger.debug( - f"即将运行SCRIPT_METHOD方法, 合计 {len(SCRIPT_METHOD)} 个..." + "即将运行SCRIPT_METHOD方法, 合计 " + f"{len(SCRIPT_METHOD)} 个..." ) sql_list = [] for module, func in SCRIPT_METHOD: try: - if is_coroutine_callable(func): - sql = await func() - else: - sql = func() + sql = await func() if is_coroutine_callable(func) else func() if sql: sql_list += sql except Exception as e: @@ -109,9 +75,9 @@ async def init(): if sql_list: logger.debug("SCRIPT_METHOD方法执行完毕!") await Tortoise.generate_schemas() - logger.info(f"Database loaded successfully!") + logger.info("Database loaded successfully!") except Exception as e: - raise Exception(f"数据库连接错误... e:{e}") + raise DbConnectError(f"数据库连接错误... e:{e}") from e async def disconnect(): diff --git a/zhenxun/services/log.py b/zhenxun/services/log.py index a2b5e07a..96a45bce 100644 --- a/zhenxun/services/log.py +++ b/zhenxun/services/log.py @@ -1,18 +1,24 @@ from datetime import datetime, timedelta -from typing import Any, Dict, overload +from typing import Any, overload +import nonebot from nonebot import require require("nonebot_plugin_session") from loguru import logger as logger_ from nonebot.log import default_filter, default_format from nonebot_plugin_session import Session +from nonebot_plugin_uninfo import Session as uninfoSession from zhenxun.configs.path_config import LOG_PATH +driver = nonebot.get_driver() + +log_level = driver.config.log_level or "INFO" + logger_.add( LOG_PATH / f"{datetime.now().date()}.log", - level="INFO", + level=log_level, rotation="00:00", format=default_format, filter=default_filter, @@ -33,8 +39,10 @@ class logger: TEMPLATE_A = "Adapter[{}] {}" TEMPLATE_B = "Adapter[{}] [{}]: {}" TEMPLATE_C = "Adapter[{}] 用户[{}] 触发 [{}]: {}" - TEMPLATE_D = "Adapter[{}] 群聊[{}] 用户[{}] 触发 [{}]: {}" - TEMPLATE_E = "Adapter[{}] 群聊[{}] 用户[{}] 触发 [{}] [Target]({}): {}" + TEMPLATE_D = "Adapter[{}] 群聊[{}] 用户[{}] 触发" + " [{}]: {}" + TEMPLATE_E = "Adapter[{}] 群聊[{}] 用户[{}] 触发" + " [{}] [Target]({}): {}" TEMPLATE_ADAPTER = "Adapter[{}] " TEMPLATE_USER = "用户[{}] " @@ -75,21 +83,32 @@ class logger: platform: str | None = None, ): ... + @overload @classmethod def info( cls, info: str, command: str | None = None, *, - session: int | str | Session | None = None, + session: uninfoSession | None = None, + target: Any = None, + platform: str | None = None, + ): ... + + @classmethod + def info( + cls, + info: str, + command: str | None = None, + *, + session: int | str | Session | uninfoSession | None = None, group_id: int | str | None = None, adapter: str | None = None, target: Any = None, platform: str | None = None, ): user_id: str | None = session # type: ignore - group_id = None - if type(session) == Session: + if isinstance(session, Session): user_id = session.id1 adapter = session.bot_type if session.id3: @@ -97,12 +116,18 @@ class logger: elif session.id2: group_id = f"{session.id2}" platform = platform or session.platform + elif isinstance(session, uninfoSession): + user_id = session.user.id + adapter = session.adapter + if session.group: + group_id = session.group.id + platform = session.basic["scope"] template = cls.__parser_template( info, command, user_id, group_id, adapter, target, platform ) try: logger_.opt(colors=True).info(template) - except Exception as e: + except Exception: logger_.info(template) @classmethod @@ -110,7 +135,7 @@ class logger: cls, info: str, command: str, - param: Dict[str, Any] | None = None, + param: dict[str, Any] | None = None, result: str = "", ): param_str = "" @@ -149,13 +174,27 @@ class logger: e: Exception | None = None, ): ... + @overload @classmethod def warning( cls, info: str, command: str | None = None, *, - session: int | str | Session | None = None, + session: uninfoSession | None = None, + adapter: str | None = None, + target: Any = None, + platform: str | None = None, + e: Exception | None = None, + ): ... + + @classmethod + def warning( + cls, + info: str, + command: str | None = None, + *, + session: int | str | Session | uninfoSession | None = None, group_id: int | str | None = None, adapter: str | None = None, target: Any = None, @@ -163,8 +202,7 @@ class logger: e: Exception | None = None, ): user_id: str | None = session # type: ignore - group_id = None - if type(session) == Session: + if isinstance(session, Session): user_id = session.id1 adapter = session.bot_type if session.id3: @@ -172,6 +210,12 @@ class logger: elif session.id2: group_id = f"{session.id2}" platform = platform or session.platform + elif isinstance(session, uninfoSession): + user_id = session.user.id + adapter = session.adapter + if session.group: + group_id = session.group.id + platform = session.basic["scope"] template = cls.__parser_template( info, command, user_id, group_id, adapter, target, platform ) @@ -210,13 +254,26 @@ class logger: e: Exception | None = None, ): ... + @overload @classmethod def error( cls, info: str, command: str | None = None, *, - session: int | str | Session | None = None, + session: uninfoSession | None = None, + target: Any = None, + platform: str | None = None, + e: Exception | None = None, + ): ... + + @classmethod + def error( + cls, + info: str, + command: str | None = None, + *, + session: int | str | Session | uninfoSession | None = None, group_id: int | str | None = None, adapter: str | None = None, target: Any = None, @@ -224,8 +281,7 @@ class logger: e: Exception | None = None, ): user_id: str | None = session # type: ignore - group_id = None - if type(session) == Session: + if isinstance(session, Session): user_id = session.id1 adapter = session.bot_type if session.id3: @@ -233,6 +289,12 @@ class logger: elif session.id2: group_id = f"{session.id2}" platform = platform or session.platform + elif isinstance(session, uninfoSession): + user_id = session.user.id + adapter = session.adapter + if session.group: + group_id = session.group.id + platform = session.basic["scope"] template = cls.__parser_template( info, command, user_id, group_id, adapter, target, platform ) @@ -271,13 +333,26 @@ class logger: e: Exception | None = None, ): ... + @overload @classmethod def debug( cls, info: str, command: str | None = None, *, - session: int | str | Session | None = None, + session: uninfoSession | None = None, + target: Any = None, + platform: str | None = None, + e: Exception | None = None, + ): ... + + @classmethod + def debug( + cls, + info: str, + command: str | None = None, + *, + session: int | str | Session | uninfoSession | None = None, group_id: int | str | None = None, adapter: str | None = None, target: Any = None, @@ -285,8 +360,7 @@ class logger: e: Exception | None = None, ): user_id: str | None = session # type: ignore - group_id = None - if type(session) == Session: + if isinstance(session, Session): user_id = session.id1 adapter = session.bot_type if session.id3: @@ -294,6 +368,12 @@ class logger: elif session.id2: group_id = f"{session.id2}" platform = platform or session.platform + elif isinstance(session, uninfoSession): + user_id = session.user.id + adapter = session.adapter + if session.group: + group_id = session.group.id + platform = session.basic["scope"] template = cls.__parser_template( info, command, user_id, group_id, adapter, target, platform ) diff --git a/zhenxun/services/plugin_init.py b/zhenxun/services/plugin_init.py new file mode 100644 index 00000000..159e042c --- /dev/null +++ b/zhenxun/services/plugin_init.py @@ -0,0 +1,105 @@ +from abc import ABC, abstractmethod +from collections.abc import Callable + +import nonebot +from nonebot.utils import is_coroutine_callable +from pydantic import BaseModel + +from zhenxun.services.log import logger + +driver = nonebot.get_driver() + + +class PluginInit(ABC): + """ + 插件安装与卸载模块 + """ + + def __init_subclass__(cls, **kwargs): + module_path = cls.__module__ + install_func = getattr(cls, "install", None) + remove_func = getattr(cls, "remove", None) + if install_func or remove_func: + PluginInitManager.plugins[module_path] = PluginInitData( + module_path=module_path, + install=install_func, + remove=remove_func, + class_=cls, + ) + + @abstractmethod + async def install(self): + raise NotImplementedError + + @abstractmethod + async def remove(self): + raise NotImplementedError + + +class PluginInitData(BaseModel): + module_path: str + """模块名""" + install: Callable | None + """安装方法""" + remove: Callable | None + """卸载方法""" + class_: type[PluginInit] + """类""" + + +class PluginInitManager: + plugins: dict[str, PluginInitData] = {} # noqa: RUF012 + + @classmethod + async def install_all(cls): + """运行所有插件安装方法""" + if cls.plugins: + for module_path, model in cls.plugins.items(): + if model.install: + class_ = model.class_() + try: + logger.debug(f"开始执行: {module_path}:install 方法") + if is_coroutine_callable(class_.install): + await class_.install() + else: + class_.install() # type: ignore + logger.debug(f"执行: {module_path}:install 完成") + except Exception as e: + logger.error(f"执行: {module_path}:install 失败", e=e) + + @classmethod + async def install(cls, module_path: str): + """运行指定插件安装方法""" + if model := cls.plugins.get(module_path): + if model.install: + class_ = model.class_() + try: + logger.debug(f"开始执行: {module_path}:install 方法") + if is_coroutine_callable(class_.install): + await class_.install() + else: + class_.install() # type: ignore + logger.debug(f"执行: {module_path}:install 完成") + except Exception as e: + logger.error(f"执行: {module_path}:install 失败", e=e) + + @classmethod + async def remove(cls, module_path: str): + """运行指定插件安装方法""" + if model := cls.plugins.get(module_path): + if model.remove: + class_ = model.class_() + try: + logger.debug(f"开始执行: {module_path}:remove 方法") + if is_coroutine_callable(class_.remove): + await class_.remove() + else: + class_.remove() # type: ignore + logger.debug(f"执行: {module_path}:remove 完成") + except Exception as e: + logger.error(f"执行: {module_path}:remove 失败", e=e) + + +@driver.on_startup +async def _(): + await PluginInitManager.install_all() diff --git a/zhenxun/utils/_build_image.py b/zhenxun/utils/_build_image.py index 33666f7e..99eccf74 100644 --- a/zhenxun/utils/_build_image.py +++ b/zhenxun/utils/_build_image.py @@ -1,15 +1,18 @@ import base64 -import math -import uuid +import contextlib from io import BytesIO +import itertools +import math from pathlib import Path -from typing import Literal, Tuple, TypeAlias, overload +from typing import Literal, TypeAlias, overload +from typing_extensions import Self +import uuid from nonebot.utils import run_sync from PIL import Image, ImageDraw, ImageFilter, ImageFont from PIL.Image import Image as tImage +from PIL.Image import Resampling, Transpose from PIL.ImageFont import FreeTypeFont -from typing_extensions import Self from zhenxun.configs.path_config import FONT_PATH @@ -18,7 +21,7 @@ ModeType = Literal[ ] """图片类型""" -ColorAlias: TypeAlias = str | Tuple[int, int, int] | Tuple[int, int, int, int] | None +ColorAlias: TypeAlias = str | tuple[int, int, int] | tuple[int, int, int, int] | None CenterType = Literal["center", "height", "width"] """ @@ -45,36 +48,37 @@ class BuildImage: mode: ModeType = "RGBA", font: str | Path | FreeTypeFont = "HYWenHei-85W.ttf", font_size: int = 20, - background: str | BytesIO | Path | None = None, + background: str | BytesIO | Path | bytes | None = None, ) -> None: self.uid = uuid.uuid1() self.width = width self.height = height self.color = color self.font = ( - self.load_font(font, font_size) - if not isinstance(font, FreeTypeFont) - else font + font if isinstance(font, FreeTypeFont) else self.load_font(font, font_size) ) if background: - self.markImg = Image.open(background) + if isinstance(background, bytes): + self.markImg = Image.open(BytesIO(background)) + else: + self.markImg = Image.open(background) if width and height: - self.markImg = self.markImg.resize((width, height), Image.LANCZOS) + self.markImg = self.markImg.resize((width, height), Resampling.LANCZOS) else: self.width = self.markImg.width self.height = self.markImg.height - else: - if not width or not height: - raise ValueError("长度和宽度不能为空...") + elif width and height: self.markImg = Image.new(mode, (width, height), color) # type: ignore + else: + raise ValueError("长度和宽度不能为空...") self.draw = ImageDraw.Draw(self.markImg) @property - def size(self) -> Tuple[int, int]: + def size(self) -> tuple[int, int]: return self.markImg.size @classmethod - def open(cls, path: str | Path) -> Self: + def open(cls, path: str | Path | bytes) -> Self: """打开图片 参数: @@ -91,9 +95,9 @@ class BuildImage: text: str, font: str | FreeTypeFont | Path = "HYWenHei-85W.ttf", size: int = 10, - font_color: str | Tuple[int, int, int] = (0, 0, 0), + font_color: str | tuple[int, int, int] = (0, 0, 0), color: ColorAlias = None, - padding: int | Tuple[int, int, int, int] | None = None, + padding: int | tuple[int, int, int, int] | None = None, ) -> Self: """构建文本图片 @@ -113,7 +117,7 @@ class BuildImage: _font = None if isinstance(font, FreeTypeFont): _font = font - elif isinstance(font, (str, Path)): + elif isinstance(font, str | Path): _font = cls.load_font(font, size) width, height = cls.get_text_size(text, _font) if isinstance(padding, int): @@ -153,24 +157,35 @@ class BuildImage: """ if not img_list: raise ValueError("贴图类别为空...") - width, height = img_list[0].size + width = max(img.size[0] for img in img_list) + height = max(img.size[1] for img in img_list) background_width = width * row + space * (row - 1) + padding * 2 row_count = math.ceil(len(img_list) / row) if row_count == 1: background_width = ( - sum([img.width for img in img_list]) + space * (row - 1) + padding * 2 + sum(img.width for img in img_list) + space * (row - 1) + padding * 2 ) background_height = height * row_count + space * (row_count - 1) + padding * 2 background_image = cls( background_width, background_height, color=color, background=background ) _cur_width, _cur_height = padding, padding - for img in img_list: + row_num = 0 + for i in range(len(img_list)): + row_num += 1 + img: Self | tImage = img_list[i] await background_image.paste(img, (_cur_width, _cur_height)) _cur_width += space + img.width - if _cur_width + padding >= background_image.width: + next_image_width = 0 + if i != len(img_list) - 1: + next_image_width = img_list[i + 1].width + if ( + row_num == row + or _cur_width + padding + next_image_width >= background_image.width + 1 + ): _cur_height += space + img.height _cur_width = padding + row_num = 0 return background_image @classmethod @@ -186,20 +201,20 @@ class BuildImage: 返回: FreeTypeFont: 字体 """ - path = FONT_PATH / font if type(font) == str else font + path = FONT_PATH / font if type(font) is str else font return ImageFont.truetype(str(path), font_size) @overload @classmethod def get_text_size( cls, text: str, font: FreeTypeFont | None = None - ) -> Tuple[int, int]: ... + ) -> tuple[int, int]: ... @overload @classmethod def get_text_size( cls, text: str, font: str | None = None, font_size: int = 10 - ) -> Tuple[int, int]: ... + ) -> tuple[int, int]: ... @classmethod def get_text_size( @@ -207,7 +222,7 @@ class BuildImage: text: str, font: str | FreeTypeFont | None = "HYWenHei-85W.ttf", font_size: int = 10, - ) -> Tuple[int, int]: + ) -> tuple[int, int]: # sourcery skip: remove-unnecessary-cast """获取该字体下文本需要的长宽 参数: @@ -216,10 +231,10 @@ class BuildImage: font_size: 字体大小 返回: - Tuple[int, int]: 长宽 + tuple[int, int]: 长宽 """ _font = font - if font and type(font) == str: + if font and type(font) is str: _font = cls.load_font(font, font_size) temp_image = Image.new("RGB", (1, 1), (255, 255, 255)) draw = ImageDraw.Draw(temp_image) @@ -229,7 +244,8 @@ class BuildImage: return text_width, text_height + 10 # return _font.getsize(str(text)) # type: ignore - def getsize(self, msg: str) -> Tuple[int, int]: + def getsize(self, msg: str) -> tuple[int, int]: + # sourcery skip: remove-unnecessary-cast """ 获取文字在该图片 font_size 下所需要的空间 @@ -237,7 +253,7 @@ class BuildImage: msg: 文本 返回: - Tuple[int, int]: 长宽 + tuple[int, int]: 长宽 """ temp_image = Image.new("RGB", (1, 1), (255, 255, 255)) draw = ImageDraw.Draw(temp_image) @@ -249,11 +265,11 @@ class BuildImage: def __center_xy( self, - pos: Tuple[int, int], + pos: tuple[int, int], width: int, height: int, center_type: CenterType | None, - ) -> Tuple[int, int]: + ) -> tuple[int, int]: """ 根据居中类型定位xy @@ -263,7 +279,7 @@ class BuildImage: center_type: 居中类型 返回: - Tuple[int, int]: 定位 + tuple[int, int]: 定位 """ # _width, _height = pos if self.width and self.height: @@ -282,7 +298,7 @@ class BuildImage: def paste( self, image: Self | tImage, - pos: Tuple[int, int] = (0, 0), + pos: tuple[int, int] = (0, 0), center_type: CenterType | None = None, ) -> Self: """贴图 @@ -300,7 +316,6 @@ class BuildImage: """ if center_type and center_type not in ["center", "height", "width"]: raise ValueError("center_type must be 'center', 'width' or 'height'") - width, height = 0, 0 _image = image if isinstance(image, BuildImage): _image = image.markImg @@ -314,7 +329,7 @@ class BuildImage: @run_sync def point( - self, pos: Tuple[int, int], fill: Tuple[int, int, int] | None = None + self, pos: tuple[int, int], fill: tuple[int, int, int] | None = None ) -> Self: """ 绘制多个或单独的像素 @@ -332,9 +347,9 @@ class BuildImage: @run_sync def ellipse( self, - pos: Tuple[int, int, int, int], - fill: Tuple[int, int, int] | None = None, - outline: Tuple[int, int, int] | None = None, + pos: tuple[int, int, int, int], + fill: tuple[int, int, int] | None = None, + outline: tuple[int, int, int] | None = None, width: int = 1, ) -> Self: """ @@ -355,13 +370,13 @@ class BuildImage: @run_sync def text( self, - pos: Tuple[int, int], + pos: tuple[int, int], text: str, - fill: str | Tuple[int, int, int] = (0, 0, 0), + fill: str | tuple[int, int, int] = (0, 0, 0), center_type: CenterType | None = None, font: FreeTypeFont | str | Path | None = None, font_size: int = 10, - ) -> Self: + ) -> Self: # sourcery skip: remove-unnecessary-cast """ 在图片上添加文字 @@ -379,11 +394,10 @@ class BuildImage: 异常: ValueError: 居中类型错误 """ - text = str(text) if center_type and center_type not in ["center", "height", "width"]: raise ValueError("center_type must be 'center', 'width' or 'height'") max_length_text = "" - sentence = text.split("\n") + sentence = str(text).split("\n") for x in sentence: max_length_text = x if len(x) > len(max_length_text) else max_length_text if font: @@ -395,7 +409,7 @@ class BuildImage: ttf_w, ttf_h = self.getsize(max_length_text) # type: ignore # ttf_h = ttf_h * len(sentence) pos = self.__center_xy(pos, ttf_w, ttf_h, center_type) - self.draw.text(pos, text, fill=fill, font=font) + self.draw.text(pos, str(text), fill=fill, font=font) return self @run_sync @@ -434,7 +448,7 @@ class BuildImage: if not width and not height and not ratio: raise ValueError("缺少参数...") if self.width and self.height: - if not width and not height and ratio: + if not width and not height: width = int(self.width * ratio) height = int(self.height * ratio) self.markImg = self.markImg.resize((width, height), Image.LANCZOS) # type: ignore @@ -443,7 +457,7 @@ class BuildImage: return self @run_sync - def crop(self, box: Tuple[int, int, int, int]) -> Self: + def crop(self, box: tuple[int, int, int, int]) -> Self: """ 裁剪图片 @@ -472,11 +486,10 @@ class BuildImage: """ self.markImg = self.markImg.convert("RGBA") x, y = self.markImg.size - for i in range(n, x - n): - for k in range(n, y - n): - color = self.markImg.getpixel((i, k)) - color = color[:-1] + (int(100 * alpha_ratio),) - self.markImg.putpixel((i, k), color) + for i, k in itertools.product(range(n, x - n), range(n, y - n)): + color = self.markImg.getpixel((i, k)) + color = color[:-1] + (int(100 * alpha_ratio),) # type: ignore + self.markImg.putpixel((i, k), color) self.draw = ImageDraw.Draw(self.markImg) return self @@ -489,7 +502,7 @@ class BuildImage: buf = BytesIO() self.markImg.save(buf, format="PNG") base64_str = base64.b64encode(buf.getvalue()).decode() - return "base64://" + base64_str + return f"base64://{base64_str}" def pic2bytes(self) -> bytes: """获取bytes @@ -498,7 +511,13 @@ class BuildImage: bytes: bytes """ buf = BytesIO() - self.markImg.save(buf, format="PNG") + img_format = self.markImg.format.upper() if self.markImg.format else "PNG" + + if img_format == "GIF": + self.markImg.save(buf, format="GIF", save_all=True, loop=0) + else: + self.markImg.save(buf, format="PNG") + return buf.getvalue() def convert(self, type_: ModeType) -> Self: @@ -517,8 +536,8 @@ class BuildImage: @run_sync def rectangle( self, - xy: Tuple[int, int, int, int], - fill: Tuple[int, int, int] | None = None, + xy: tuple[int, int, int, int], + fill: tuple[int, int, int] | None = None, outline: str | None = None, width: int = 1, ) -> Self: @@ -540,8 +559,8 @@ class BuildImage: @run_sync def polygon( self, - xy: list[Tuple[int, int]], - fill: Tuple[int, int, int] = (0, 0, 0), + xy: list[tuple[int, int]], + fill: tuple[int, int, int] = (0, 0, 0), outline: int = 1, ) -> Self: """ @@ -561,8 +580,8 @@ class BuildImage: @run_sync def line( self, - xy: Tuple[int, int, int, int], - fill: Tuple[int, int, int] | str = "#D8DEE4", + xy: tuple[int, int, int, int], + fill: tuple[int, int, int] | str = "#D8DEE4", width: int = 1, ) -> Self: """ @@ -602,21 +621,19 @@ class BuildImage: ) draw = ImageDraw.Draw(mask) for offset, fill in (width / -2.0, "black"), (width / 2.0, "white"): - left, top = [(value + offset) * antialias for value in ellipse_box[:2]] - right, bottom = [(value - offset) * antialias for value in ellipse_box[2:]] + left, top = ((value + offset) * antialias for value in ellipse_box[:2]) + right, bottom = ((value - offset) * antialias for value in ellipse_box[2:]) draw.ellipse([left, top, right, bottom], fill=fill) - mask = mask.resize(self.markImg.size, Image.LANCZOS) - try: + mask = mask.resize(self.markImg.size, Resampling.LANCZOS) + with contextlib.suppress(ValueError): self.markImg.putalpha(mask) - except ValueError: - pass return self @run_sync def circle_corner( self, radii: int = 30, - point_list: list[Literal["lt", "rt", "lb", "rb"]] = ["lt", "rt", "lb", "rb"], + point_list: list[Literal["lt", "rt", "lb", "rb"]] | None = None, ) -> Self: """ 矩形四角变圆 @@ -628,6 +645,8 @@ class BuildImage: 返回: BuildImage: Self """ + if point_list is None: + point_list = ["lt", "rt", "lb", "rb"] # 画圆(用于分离4个角) img = self.markImg.convert("RGBA") alpha = img.split()[-1] @@ -667,7 +686,7 @@ class BuildImage: return self @run_sync - def transpose(self, angle: Literal[0, 1, 2, 3, 4, 5, 6]) -> Self: + def transpose(self, angle: Transpose) -> Self: """ 旋转图片(包括边框) @@ -718,3 +737,11 @@ class BuildImage: bytes: bytes """ return self.markImg.tobytes() + + def copy(self) -> "BuildImage": + """复制 + + 返回: + BuildImage: Self + """ + return BuildImage.open(self.pic2bytes()) diff --git a/zhenxun/utils/_build_mat.py b/zhenxun/utils/_build_mat.py index 6f30d301..de73e69d 100644 --- a/zhenxun/utils/_build_mat.py +++ b/zhenxun/utils/_build_mat.py @@ -1,16 +1,14 @@ -import random from io import BytesIO from pathlib import Path -from re import S +import random -from pydantic import BaseModel +from pydantic import BaseModel, Field from strenum import StrEnum from ._build_image import BuildImage class MatType(StrEnum): - LINE = "LINE" """折线图""" BAR = "BAR" @@ -20,18 +18,17 @@ class MatType(StrEnum): class BuildMatData(BaseModel): - mat_type: MatType """类型""" - data: list[int | float] = [] + data: list[int | float] = Field(default_factory=list) """数据""" x_name: str | None = None """X轴坐标名称""" y_name: str | None = None """Y轴坐标名称""" - x_index: list[str] = [] + x_index: list[str] = Field(default_factory=list) """显示轴坐标值""" - y_index: list[int | float] = [] + y_index: list[int | float] = Field(default_factory=list) """数据轴坐标值""" space: tuple[int, int] = (20, 20) """坐标值间隔(X, Y)""" @@ -51,7 +48,7 @@ class BuildMatData(BaseModel): """背景颜色""" background: Path | bytes | None = None """背景图片""" - bar_color: list[str] = ["*"] + bar_color: list[str] = Field(default_factory=lambda: ["*"]) """柱状图柱子颜色, 多个时随机, 使用 * 时七色随机""" padding: tuple[int, int] = (50, 50) """图表上下左右边距""" @@ -64,7 +61,6 @@ class BuildMat: """ class InitGraph(BaseModel): - mark_image: BuildImage """BuildImage""" x_height: int @@ -291,7 +287,8 @@ class BuildMat: self.build_data.font, self.build_data.font_size + 4 ) title_width, title_height = BuildImage.get_text_size( - self.build_data.x_name, font # type: ignore + self.build_data.x_name, + font, # type: ignore ) pos = ( A.width - title_width - 20, diff --git a/zhenxun/utils/_image_template.py b/zhenxun/utils/_image_template.py index 399c054c..7f27db76 100644 --- a/zhenxun/utils/_image_template.py +++ b/zhenxun/utils/_image_template.py @@ -1,9 +1,8 @@ -import random +from collections.abc import Callable from io import BytesIO from pathlib import Path -from typing import Any, Callable, Dict +import random -from fastapi import background from PIL.ImageFont import FreeTypeFont from pydantic import BaseModel @@ -11,7 +10,6 @@ from ._build_image import BuildImage class RowStyle(BaseModel): - font: FreeTypeFont | str | Path | None = "HYWenHei-85W.ttf" """字体""" font_size: int = 20 @@ -24,14 +22,13 @@ class RowStyle(BaseModel): class ImageTemplate: - - color_list = ["#C2CEFE", "#FFA94C", "#3FE6A0", "#D1D4F5"] + color_list = ["#C2CEFE", "#FFA94C", "#3FE6A0", "#D1D4F5"] # noqa: RUF012 @classmethod async def hl_page( cls, head_text: str, - items: Dict[str, str], + items: dict[str, str], row_space: int = 10, padding: int = 30, ) -> BuildImage: @@ -89,7 +86,7 @@ class ImageTemplate: head_text: str, tip_text: str | None, column_name: list[str], - data_list: list[list[str | tuple[Path | BuildImage, int, int]]], + data_list: list[list[str | int | tuple[Path | BuildImage, int, int]]], row_space: int = 35, column_space: int = 30, padding: int = 5, @@ -138,7 +135,7 @@ class ImageTemplate: async def table( cls, column_name: list[str], - data_list: list[list[str | tuple[Path | BuildImage, int, int]]], + data_list: list[list[str | int | tuple[Path | BuildImage, int, int]]], row_space: int = 25, column_space: int = 10, padding: int = 5, @@ -162,9 +159,11 @@ class ImageTemplate: column_data = [] for i in range(len(column_name)): c = [] - for l in data_list: - if len(l) > i: - c.append(l[i]) + for item in data_list: + if len(item) > i: + c.append( + item[i] if isinstance(item[i], tuple | list) else str(item[i]) + ) else: c.append("") column_data.append(c) @@ -177,34 +176,41 @@ class ImageTemplate: if isinstance(s, tuple): w = s[1] else: - w, _ = BuildImage.get_text_size(s, font) + w, _ = BuildImage.get_text_size(str(s), font) if w > _temp["width"]: _temp["width"] = w build_data_list.append(_temp) column_image_list = [] + column_name_image_list: list[BuildImage] = [] + for i, data in enumerate(build_data_list): + column_name_image = await BuildImage.build_text_image( + column_name[i], font, 12, "#C8CCCF" + ) + column_name_image_list.append(column_name_image) + max_h = max(c.height for c in column_name_image_list) for i, data in enumerate(build_data_list): width = data["width"] + padding * 2 height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2 background = BuildImage(width, height, (255, 255, 255)) - column_name_image = await BuildImage.build_text_image( - column_name[i], font, 12, "#C8CCCF" - ) + column_name_image = column_name_image_list[i] await background.paste(column_name_image, (0, 20), center_type="width") - cur_h = column_name_image.height + row_space + 20 + cur_h = max_h + row_space + 20 for item in data["data"]: style = RowStyle(font=font) if text_style: style = text_style(column_name[i], item) - if isinstance(item, tuple): + if isinstance(item, tuple | list): """图片""" data, width, height = item + image_ = None if isinstance(data, Path): image_ = BuildImage(width, height, background=data) elif isinstance(data, bytes): image_ = BuildImage(width, height, background=BytesIO(data)) elif isinstance(data, BuildImage): image_ = data - await background.paste(image_, (padding, cur_h)) + if image_: + await background.paste(image_, (padding, cur_h)) else: await background.text( (padding, cur_h), @@ -215,8 +221,6 @@ class ImageTemplate: ) cur_h += base_h + row_space column_image_list.append(background) - # height = max([bk.height for bk in column_image_list]) - # width = sum([bk.width for bk in column_image_list]) return await BuildImage.auto_paste( column_image_list, len(column_image_list), column_space ) @@ -273,10 +277,9 @@ class ImageTemplate: width = 0 height = 0 _, h = BuildImage.get_text_size("A", font) - image_list = [] for s in text.split("\n"): s = s.strip() or "A" w, _ = BuildImage.get_text_size(s, font) - width = width if width > w else w + width = max(width, w) height += h return width, height diff --git a/zhenxun/utils/browser.py b/zhenxun/utils/browser.py index fccfa55e..ca2e7755 100644 --- a/zhenxun/utils/browser.py +++ b/zhenxun/utils/browser.py @@ -5,7 +5,7 @@ from nonebot import get_driver from playwright.__main__ import main from playwright.async_api import Browser, Playwright, async_playwright -from zhenxun.configs.config import SYSTEM_PROXY +from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger driver = get_driver() @@ -45,12 +45,12 @@ def install(): os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = ( "https://npmmirror.com/mirrors/playwright/" ) - if SYSTEM_PROXY: - os.environ["HTTPS_PROXY"] = SYSTEM_PROXY + if BotConfig.system_proxy: + os.environ["HTTPS_PROXY"] = BotConfig.system_proxy def restore_env_variables(): os.environ.pop("PLAYWRIGHT_DOWNLOAD_HOST", None) - if SYSTEM_PROXY: + if BotConfig.system_proxy: os.environ.pop("HTTPS_PROXY", None) if original_proxy is not None: os.environ["HTTPS_PROXY"] = original_proxy diff --git a/zhenxun/utils/common_utils.py b/zhenxun/utils/common_utils.py new file mode 100644 index 00000000..cc143898 --- /dev/null +++ b/zhenxun/utils/common_utils.py @@ -0,0 +1,124 @@ +from typing import overload + +from nonebot.adapters import Bot +from nonebot_plugin_uninfo import Session, SupportScope, Uninfo, get_interface + +from zhenxun.configs.config import BotConfig +from zhenxun.models.ban_console import BanConsole +from zhenxun.models.bot_console import BotConsole +from zhenxun.models.group_console import GroupConsole +from zhenxun.models.task_info import TaskInfo +from zhenxun.services.log import logger + + +class CommonUtils: + @classmethod + async def task_is_block( + cls, session: Uninfo | Bot, module: str, group_id: str | None = None + ) -> bool: + """判断被动技能是否可以发送 + + 参数: + module: 被动技能模块名 + group_id: 群组id + + 返回: + bool: 是否可以发送 + """ + if isinstance(session, Bot): + if interface := get_interface(session): + info = interface.basic_info() + if info["scope"] == SupportScope.qq_api: + logger.info("q官bot放弃所有被动技能发言...") + """q官bot放弃所有被动技能发言""" + return False + if session.scene == SupportScope.qq_api: + """q官bot放弃所有被动技能发言""" + logger.info("q官bot放弃所有被动技能发言...") + return False + if not group_id and isinstance(session, Session): + group_id = session.group.id if session.group else None + if task := await TaskInfo.get_or_none(module=module): + """被动全局状态""" + if not task.status: + return True + if not await BotConsole.get_bot_status(session.self_id): + """bot是否休眠""" + return True + block_tasks = await BotConsole.get_tasks(session.self_id, False) + if module in block_tasks: + """bot是否禁用被动""" + return True + if group_id: + if await GroupConsole.is_block_task(group_id, module): + """群组是否禁用被动""" + return True + if g := await GroupConsole.get_or_none( + group_id=group_id, channel_id__isnull=True + ): + """群组权限是否小于0""" + if g.level < 0: + return True + if await BanConsole.is_ban(None, group_id): + """群组是否被ban""" + return True + return False + + @staticmethod + def format(name: str) -> str: + return f"<{name}," + + @overload + @classmethod + def convert_module_format(cls, data: str) -> list[str]: ... + + @overload + @classmethod + def convert_module_format(cls, data: list[str]) -> str: ... + + @classmethod + def convert_module_format(cls, data: str | list[str]) -> str | list[str]: + """ + 在 ` str: + db_class_name = BotConfig.get_sql_type() + if "postgres" in db_class_name or "sqlite" in db_class_name: + query = f"{query.sql()} ORDER BY RANDOM() LIMIT {limit};" + elif "mysql" in db_class_name: + query = f"{query.sql()} ORDER BY RAND() LIMIT {limit};" + else: + logger.warning( + f"Unsupported database type: {db_class_name}", query.__module__ + ) + return query + + @classmethod + def add_column( + cls, + table_name: str, + column_name: str, + column_type: str, + default: str | None = None, + not_null: bool = False, + ) -> str: + sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}" + if default: + sql += f" DEFAULT {default}" + if not_null: + sql += " NOT NULL" + return sql diff --git a/zhenxun/utils/decorator/retry.py b/zhenxun/utils/decorator/retry.py new file mode 100644 index 00000000..2a6cc757 --- /dev/null +++ b/zhenxun/utils/decorator/retry.py @@ -0,0 +1,16 @@ +from httpx import ConnectError, HTTPStatusError, TimeoutException +from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed + + +class Retry: + @staticmethod + def api(): + """接口调用重试""" + return retry( + reraise=True, + stop=stop_after_attempt(3), + wait=wait_fixed(1), + retry=retry_if_exception_type( + (TimeoutException, ConnectError, HTTPStatusError) + ), + ) diff --git a/zhenxun/utils/decorator/shop.py b/zhenxun/utils/decorator/shop.py index 3e46db6a..408b7e1f 100644 --- a/zhenxun/utils/decorator/shop.py +++ b/zhenxun/utils/decorator/shop.py @@ -1,16 +1,15 @@ -from typing import Any, Callable, Dict +from collections.abc import Callable from nonebot.adapters.onebot.v11 import Message, MessageSegment from nonebot.plugin import require -from pydantic import BaseModel +from pydantic import BaseModel, Field from zhenxun.models.goods_info import GoodsInfo class Goods(BaseModel): - - before_handle: list[Callable] = [] - after_handle: list[Callable] = [] + before_handle: list[Callable] = Field(default_factory=list) + after_handle: list[Callable] = Field(default_factory=list) price: int des: str = "" discount: float @@ -18,17 +17,17 @@ class Goods(BaseModel): daily_limit: int icon: str | None = None is_passive: bool + partition: str | None func: Callable - kwargs: Dict[str, str] = {} + kwargs: dict[str, str] = Field(default_factory=dict) send_success_msg: bool max_num_limit: int class ShopRegister(dict): - def __init__(self, *args, **kwargs): - super(ShopRegister, self).__init__(*args, **kwargs) - self._data: Dict[str, Goods] = {} + super().__init__(*args, **kwargs) + self._data: dict[str, Goods] = {} self._flag = True def before_handle(self, name: str | tuple[str, ...], load_status: bool = True): @@ -42,7 +41,7 @@ class ShopRegister(dict): def register_before_handle(name_list: tuple[str, ...], func: Callable): if load_status: for name_ in name_list: - if goods := self._data.get(name_): + if self._data.get(name_): self._data[name_].before_handle.append(func) _name = (name,) if isinstance(name, str) else name @@ -59,7 +58,7 @@ class ShopRegister(dict): def register_after_handle(name_list: tuple[str, ...], func: Callable): if load_status: for name_ in name_list: - if goods := self._data.get(name_): + if self._data.get(name_): self._data[name_].after_handle.append(func) _name = (name,) if isinstance(name, str) else name @@ -75,6 +74,7 @@ class ShopRegister(dict): load_status: tuple[bool, ...], daily_limit: tuple[int, ...], is_passive: tuple[bool, ...], + partition: tuple[str, ...], icon: tuple[str, ...], send_success_msg: tuple[bool, ...], max_num_limit: tuple[int, ...], @@ -91,6 +91,7 @@ class ShopRegister(dict): load_status: 是否加载 daily_limit: 每日限购 is_passive: 是否被动道具 + partition: 分区名称 icon: 图标 send_success_msg: 成功时发送消息 max_num_limit: 单次最大使用次数 @@ -99,7 +100,7 @@ class ShopRegister(dict): def add_register_item(func: Callable): if name in self._data.keys(): raise ValueError("该商品已注册,请替换其他名称!") - for n, p, d, dd, l, s, dl, pa, i, ssm, mnl in zip( + for n, p, d, dd, lmt, s, dl, pa, par, i, ssm, mnl in zip( name, price, des, @@ -108,6 +109,7 @@ class ShopRegister(dict): load_status, daily_limit, is_passive, + partition, icon, send_success_msg, max_num_limit, @@ -123,9 +125,10 @@ class ShopRegister(dict): price=p, des=d, discount=dd, - limit_time=l, + limit_time=lmt, daily_limit=dl, is_passive=pa, + partition=par, func=func, send_success_msg=ssm, max_num_limit=mnl, @@ -133,10 +136,11 @@ class ShopRegister(dict): goods.price = p goods.des = d goods.discount = dd - goods.limit_time = l + goods.limit_time = lmt goods.daily_limit = dl goods.icon = i goods.is_passive = pa + goods.partition = par goods.func = func goods.kwargs = _temp_kwargs goods.send_success_msg = ssm @@ -164,6 +168,7 @@ class ShopRegister(dict): goods.limit_time, goods.daily_limit, goods.is_passive, + goods.partition, goods.icon, ) if uuid: @@ -188,6 +193,7 @@ class ShopRegister(dict): load_status: bool | tuple[bool, ...] = True, daily_limit: int | tuple[int, ...] = 0, is_passive: bool | tuple[bool, ...] = False, + partition: str | tuple[str, ...] | None = None, icon: str | tuple[str, ...] = "", send_success_msg: bool | tuple[bool, ...] = True, max_num_limit: int | tuple[int, ...] = 1, @@ -204,11 +210,11 @@ class ShopRegister(dict): load_status: 是否加载 daily_limit: 每日限购 is_passive: 是否被动道具 + partition: 分区名称 icon: 图标 send_success_msg: 成功时发送消息 max_num_limit: 单次最大使用次数 """ - _tuple_list = [] _current_len = -1 for x in [name, price, des, discount, limit_time, load_status]: if isinstance(x, tuple): @@ -216,7 +222,8 @@ class ShopRegister(dict): _current_len = len(x) if _current_len != len(x): raise ValueError( - f"注册商品 {name} 中 name,price,des,discount,limit_time,load_status,daily_limit 数量不符!" + f"注册商品 {name} 中 name,price,des,discount,limit_time," + "load_status,daily_limit 数量不符!" ) _current_len = _current_len if _current_len > -1 else 1 _name = self.__get(name, _current_len) @@ -227,6 +234,7 @@ class ShopRegister(dict): _load_status = self.__get(load_status, _current_len) _daily_limit = self.__get(daily_limit, _current_len) _is_passive = self.__get(is_passive, _current_len) + _partition = self.__get(partition, _current_len) _icon = self.__get(icon, _current_len) _send_success_msg = self.__get(send_success_msg, _current_len) _max_num_limit = self.__get(max_num_limit, _current_len) @@ -239,6 +247,7 @@ class ShopRegister(dict): _load_status, _daily_limit, _is_passive, + _partition, _icon, _send_success_msg, _max_num_limit, @@ -249,7 +258,7 @@ class ShopRegister(dict): return ( value if isinstance(value, tuple) - else tuple([value for _ in range(_current_len)]) + else tuple(value for _ in range(_current_len)) ) def __setitem__(self, key, value): diff --git a/zhenxun/utils/depends/__init__.py b/zhenxun/utils/depends/__init__.py index ca6b1ba4..887813dd 100644 --- a/zhenxun/utils/depends/__init__.py +++ b/zhenxun/utils/depends/__init__.py @@ -4,7 +4,7 @@ from nonebot.internal.params import Depends from nonebot.matcher import Matcher from nonebot.params import Command from nonebot_plugin_session import EventSession -from nonebot_plugin_userinfo import EventUserInfo, UserInfo +from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.config import Config from zhenxun.utils.message import MessageUtils @@ -49,10 +49,8 @@ def UserName(): 用户名称 """ - async def dependency(user_info: UserInfo = EventUserInfo()): - return ( - user_info.user_displayname or user_info.user_remark or user_info.user_name - ) or "" + async def dependency(user_info: Uninfo): + return user_info.user.nick or user_info.user.name or "" return Depends(dependency) diff --git a/zhenxun/utils/echart_utils/__init__.py b/zhenxun/utils/echart_utils/__init__.py new file mode 100644 index 00000000..029dc3df --- /dev/null +++ b/zhenxun/utils/echart_utils/__init__.py @@ -0,0 +1,32 @@ +import os +import random + +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.utils._build_image import BuildImage + +from .models import Barh + +BACKGROUND_PATH = TEMPLATE_PATH / "bar_chart" / "background" + + +class ChartUtils: + @classmethod + async def barh(cls, data: Barh) -> BuildImage: + """横向统计图""" + to_json = data.to_dict() + to_json["background_image"] = ( + f"./background/{random.choice(os.listdir(BACKGROUND_PATH))}" + ) + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "bar_chart").absolute()), + template_name="main.html", + templates={"data": to_json}, + pages={ + "viewport": {"width": 1000, "height": 1000}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + return BuildImage.open(pic) diff --git a/zhenxun/utils/echart_utils/models.py b/zhenxun/utils/echart_utils/models.py new file mode 100644 index 00000000..6c29ff9e --- /dev/null +++ b/zhenxun/utils/echart_utils/models.py @@ -0,0 +1,14 @@ +from nonebot.compat import model_dump +from pydantic import BaseModel + + +class Barh(BaseModel): + category_data: list[str] + """坐标轴数据""" + data: list[int | float] + """实际数据""" + title: str + """标题""" + + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) diff --git a/zhenxun/utils/enum.py b/zhenxun/utils/enum.py index 86d41d13..c0b79342 100644 --- a/zhenxun/utils/enum.py +++ b/zhenxun/utils/enum.py @@ -42,6 +42,8 @@ class PluginType(StrEnum): """依赖插件,一般为没有主动触发命令的插件,受权限控制""" HIDDEN = "HIDDEN" """隐藏插件,一般为没有主动触发命令的插件,不受权限控制,如消息统计""" + PARENT = "PARENT" + """父插件,仅仅标记""" class BlockType(StrEnum): diff --git a/zhenxun/utils/github_utils/__init__.py b/zhenxun/utils/github_utils/__init__.py new file mode 100644 index 00000000..ecb80bb1 --- /dev/null +++ b/zhenxun/utils/github_utils/__init__.py @@ -0,0 +1,27 @@ +from collections.abc import Generator + +from .const import GITHUB_REPO_URL_PATTERN +from .func import get_fastest_archive_formats, get_fastest_raw_formats +from .models import GitHubStrategy, JsdelivrStrategy, RepoAPI, RepoInfo + +__all__ = [ + "GithubUtils", + "get_fastest_archive_formats", + "get_fastest_raw_formats", +] + + +class GithubUtils: + # 使用 + jsdelivr_api = RepoAPI(JsdelivrStrategy()) # type: ignore + github_api = RepoAPI(GitHubStrategy()) # type: ignore + + @classmethod + def iter_api_strategies(cls) -> Generator[RepoAPI]: + yield from [cls.github_api, cls.jsdelivr_api] + + @classmethod + def parse_github_url(cls, github_url: str) -> "RepoInfo": + if matched := GITHUB_REPO_URL_PATTERN.match(github_url): + return RepoInfo(**{k: v for k, v in matched.groupdict().items() if v}) + raise ValueError("github地址格式错误") diff --git a/zhenxun/utils/github_utils/const.py b/zhenxun/utils/github_utils/const.py new file mode 100644 index 00000000..13b013be --- /dev/null +++ b/zhenxun/utils/github_utils/const.py @@ -0,0 +1,35 @@ +import re + +GITHUB_REPO_URL_PATTERN = re.compile( + r"^https://github.com/(?P[^/]+)/(?P[^/]+)(/tree/(?P[^/]+))?$" +) +"""github仓库地址正则""" + +JSD_PACKAGE_API_FORMAT = ( + "https://data.jsdelivr.com/v1/packages/gh/{owner}/{repo}@{branch}" +) +"""jsdelivr包地址格式""" + +GIT_API_TREES_FORMAT = ( + "https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1" +) +"""git api trees地址格式""" + +CACHED_API_TTL = 300 +"""缓存api ttl""" + +RAW_CONTENT_FORMAT = "https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}" +"""raw content格式""" + +ARCHIVE_URL_FORMAT = "https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip" +"""archive url格式""" + +RELEASE_ASSETS_FORMAT = ( + "https://github.com/{owner}/{repo}/releases/download/{version}/{filename}" +) +"""release assets格式""" + +RELEASE_SOURCE_FORMAT = ( + "https://codeload.github.com/{owner}/{repo}/legacy.{compress}/refs/tags/{version}" +) +"""release 源码格式""" diff --git a/zhenxun/utils/github_utils/func.py b/zhenxun/utils/github_utils/func.py new file mode 100644 index 00000000..19daf10d --- /dev/null +++ b/zhenxun/utils/github_utils/func.py @@ -0,0 +1,64 @@ +from aiocache import cached + +from zhenxun.utils.http_utils import AsyncHttpx + +from .const import ( + ARCHIVE_URL_FORMAT, + RAW_CONTENT_FORMAT, + RELEASE_ASSETS_FORMAT, + RELEASE_SOURCE_FORMAT, +) + + +async def __get_fastest_formats(formats: dict[str, str]) -> list[str]: + sorted_urls = await AsyncHttpx.get_fastest_mirror(list(formats.keys())) + if not sorted_urls: + raise Exception("无法获取任意GitHub资源加速地址,请检查网络") + return [formats[url] for url in sorted_urls] + + +@cached() +async def get_fastest_raw_formats() -> list[str]: + """获取最快的raw下载地址格式""" + formats: dict[str, str] = { + "https://raw.githubusercontent.com/": RAW_CONTENT_FORMAT, + "https://ghproxy.cc/": f"https://ghproxy.cc/{RAW_CONTENT_FORMAT}", + "https://mirror.ghproxy.com/": f"https://mirror.ghproxy.com/{RAW_CONTENT_FORMAT}", + "https://gh-proxy.com/": f"https://gh-proxy.com/{RAW_CONTENT_FORMAT}", + "https://cdn.jsdelivr.net/": "https://cdn.jsdelivr.net/gh/{owner}/{repo}@{branch}/{path}", + } + return await __get_fastest_formats(formats) + + +@cached() +async def get_fastest_archive_formats() -> list[str]: + """获取最快的归档下载地址格式""" + formats: dict[str, str] = { + "https://github.com/": ARCHIVE_URL_FORMAT, + "https://ghproxy.cc/": f"https://ghproxy.cc/{ARCHIVE_URL_FORMAT}", + "https://mirror.ghproxy.com/": f"https://mirror.ghproxy.com/{ARCHIVE_URL_FORMAT}", + "https://gh-proxy.com/": f"https://gh-proxy.com/{ARCHIVE_URL_FORMAT}", + } + return await __get_fastest_formats(formats) + + +@cached() +async def get_fastest_release_formats() -> list[str]: + """获取最快的发行版资源下载地址格式""" + formats: dict[str, str] = { + "https://objects.githubusercontent.com/": RELEASE_ASSETS_FORMAT, + "https://ghproxy.cc/": f"https://ghproxy.cc/{RELEASE_ASSETS_FORMAT}", + "https://mirror.ghproxy.com/": f"https://mirror.ghproxy.com/{RELEASE_ASSETS_FORMAT}", + "https://gh-proxy.com/": f"https://gh-proxy.com/{RELEASE_ASSETS_FORMAT}", + } + return await __get_fastest_formats(formats) + + +@cached() +async def get_fastest_release_source_formats() -> list[str]: + """获取最快的发行版源码下载地址格式""" + formats: dict[str, str] = { + "https://codeload.github.com/": RELEASE_SOURCE_FORMAT, + "https://p.102333.xyz/": f"https://p.102333.xyz/{RELEASE_SOURCE_FORMAT}", + } + return await __get_fastest_formats(formats) diff --git a/zhenxun/utils/github_utils/models.py b/zhenxun/utils/github_utils/models.py new file mode 100644 index 00000000..d21c2387 --- /dev/null +++ b/zhenxun/utils/github_utils/models.py @@ -0,0 +1,240 @@ +from typing import Protocol + +from aiocache import cached +from nonebot.compat import model_dump +from pydantic import BaseModel, Field +from strenum import StrEnum + +from zhenxun.utils.http_utils import AsyncHttpx + +from .const import CACHED_API_TTL, GIT_API_TREES_FORMAT, JSD_PACKAGE_API_FORMAT +from .func import ( + get_fastest_archive_formats, + get_fastest_raw_formats, + get_fastest_release_source_formats, +) + + +class RepoInfo(BaseModel): + """仓库信息""" + + owner: str + repo: str + branch: str = "main" + + async def get_raw_download_url(self, path: str) -> str: + return (await self.get_raw_download_urls(path))[0] + + async def get_archive_download_url(self) -> str: + return (await self.get_archive_download_urls())[0] + + async def get_release_source_download_url_tgz(self, version: str) -> str: + return (await self.get_release_source_download_urls_tgz(version))[0] + + async def get_release_source_download_url_zip(self, version: str) -> str: + return (await self.get_release_source_download_urls_zip(version))[0] + + async def get_raw_download_urls(self, path: str) -> list[str]: + url_formats = await get_fastest_raw_formats() + return [ + url_format.format(**self.to_dict(), path=path) for url_format in url_formats + ] + + async def get_archive_download_urls(self) -> list[str]: + url_formats = await get_fastest_archive_formats() + return [url_format.format(**self.to_dict()) for url_format in url_formats] + + async def get_release_source_download_urls_tgz(self, version: str) -> list[str]: + url_formats = await get_fastest_release_source_formats() + return [ + url_format.format(**self.to_dict(), version=version, compress="tar.gz") + for url_format in url_formats + ] + + async def get_release_source_download_urls_zip(self, version: str) -> list[str]: + url_formats = await get_fastest_release_source_formats() + return [ + url_format.format(**self.to_dict(), version=version, compress="zip") + for url_format in url_formats + ] + + def to_dict(self, **kwargs): + return model_dump(self, **kwargs) + + +class APIStrategy(Protocol): + """API策略""" + + body: BaseModel + + async def parse_repo_info(self, repo_info: RepoInfo) -> BaseModel: ... + + def get_files(self, module_path: str, is_dir: bool) -> list[str]: ... + + +class RepoAPI: + """基础接口""" + + def __init__(self, strategy: APIStrategy): + self.strategy = strategy + + async def parse_repo_info(self, repo_info: RepoInfo): + body = await self.strategy.parse_repo_info(repo_info) + self.strategy.body = body + + def get_files(self, module_path: str, is_dir: bool) -> list[str]: + return self.strategy.get_files(module_path, is_dir) + + +class FileType(StrEnum): + """文件类型""" + + FILE = "file" + DIR = "directory" + PACKAGE = "gh" + + +class FileInfo(BaseModel): + """文件信息""" + + type: FileType + name: str + files: list["FileInfo"] = Field(default_factory=list) + + +class JsdelivrStrategy: + """Jsdelivr策略""" + + body: FileInfo + + def get_file_paths(self, module_path: str, is_dir: bool = True) -> list[str]: + """获取文件路径""" + paths = module_path.split("/") + filename = "" if is_dir and module_path else paths[-1] + paths = paths if is_dir and module_path else paths[:-1] + cur_file = self.body + for path in paths: # 导航到正确的目录 + cur_file = next( + ( + f + for f in cur_file.files + if f.type == FileType.DIR and f.name == path + ), + None, + ) + if not cur_file: + raise ValueError(f"模块路径{module_path}不存在") + + def collect_files(file: FileInfo, current_path: str, filename: str): + """收集文件""" + if file.type == FileType.FILE and (not filename or file.name == filename): + return [f"{current_path}/{file.name}"] + elif file.type == FileType.DIR and file.files: + return [ + path + for f in file.files + for path in collect_files( + f, + ( + f"{current_path}/{f.name}" + if f.type == FileType.DIR + else current_path + ), + filename, + ) + ] + return [] + + files = collect_files(cur_file, "/".join(paths), filename) + return files if module_path else [f[1:] for f in files] + + @classmethod + @cached(ttl=CACHED_API_TTL) + async def parse_repo_info(cls, repo_info: RepoInfo) -> "FileInfo": + """解析仓库信息""" + + """获取插件包信息 + + 参数: + repo_info: 仓库信息 + + 返回: + FileInfo: 插件包信息 + """ + jsd_package_url: str = JSD_PACKAGE_API_FORMAT.format( + owner=repo_info.owner, repo=repo_info.repo, branch=repo_info.branch + ) + res = await AsyncHttpx.get(url=jsd_package_url) + if res.status_code != 200: + raise ValueError(f"下载错误, code: {res.status_code}") + return FileInfo(**res.json()) + + def get_files(self, module_path: str, is_dir: bool = True) -> list[str]: + """获取文件路径""" + return self.get_file_paths(module_path, is_dir) + + +class TreeType(StrEnum): + """树类型""" + + FILE = "blob" + DIR = "tree" + + +class Tree(BaseModel): + """树""" + + path: str + mode: str + type: TreeType + sha: str + size: int | None = None + url: str + + +class TreeInfo(BaseModel): + """树信息""" + + sha: str + url: str + tree: list[Tree] + + +class GitHubStrategy: + """GitHub策略""" + + body: TreeInfo + + def export_files(self, module_path: str, is_dir: bool) -> list[str]: + """导出文件路径""" + tree_info = self.body + return [ + file.path + for file in tree_info.tree + if file.type == TreeType.FILE + and file.path.startswith(module_path) + and (not is_dir or file.path[len(module_path)] == "/" or not module_path) + ] + + @classmethod + @cached(ttl=CACHED_API_TTL) + async def parse_repo_info(cls, repo_info: RepoInfo) -> "TreeInfo": + """获取仓库树 + + 参数: + repo_info: 仓库信息 + + 返回: + TreesInfo: 仓库树信息 + """ + git_tree_url: str = GIT_API_TREES_FORMAT.format( + owner=repo_info.owner, repo=repo_info.repo, branch=repo_info.branch + ) + res = await AsyncHttpx.get(url=git_tree_url) + if res.status_code != 200: + raise ValueError(f"下载错误, code: {res.status_code}") + return TreeInfo(**res.json()) + + def get_files(self, module_path: str, is_dir: bool = True) -> list[str]: + """获取文件路径""" + return self.export_files(module_path, is_dir) diff --git a/zhenxun/utils/http_utils.py b/zhenxun/utils/http_utils.py index 15da938d..962c9e01 100644 --- a/zhenxun/utils/http_utils.py +++ b/zhenxun/utils/http_utils.py @@ -1,19 +1,22 @@ import asyncio from asyncio.exceptions import TimeoutError +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from pathlib import Path -from typing import Any, AsyncGenerator, Dict, Literal +import time +from typing import Any, ClassVar, Literal import aiofiles +from anyio import EndOfStream import httpx -import rich -from httpx import ConnectTimeout, Response +from httpx import ConnectTimeout, HTTPStatusError, Response from nonebot_plugin_alconna import UniMessage from nonebot_plugin_htmlrender import get_browser from playwright.async_api import Page from retrying import retry +import rich -from zhenxun.configs.config import SYSTEM_PROXY +from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils from zhenxun.utils.user_agent import get_user_agent @@ -22,22 +25,106 @@ from zhenxun.utils.user_agent import get_user_agent class AsyncHttpx: - - proxy = {"http://": SYSTEM_PROXY, "https://": SYSTEM_PROXY} + proxy: ClassVar[dict[str, str | None]] = { + "http://": BotConfig.system_proxy, + "https://": BotConfig.system_proxy, + } @classmethod @retry(stop_max_attempt_number=3) async def get( cls, - url: str, + url: str | list[str], *, - params: Dict[str, Any] | None = None, - headers: Dict[str, str] | None = None, - cookies: Dict[str, str] | None = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + cookies: dict[str, str] | None = None, verify: bool = True, use_proxy: bool = True, - proxy: Dict[str, str] | None = None, - timeout: int = 30, + proxy: dict[str, str] | None = None, + timeout: int = 30, # noqa: ASYNC109 + **kwargs, + ) -> Response: + """Get + + 参数: + url: url + params: params + headers: 请求头 + cookies: cookies + verify: verify + use_proxy: 使用默认代理 + proxy: 指定代理 + timeout: 超时时间 + """ + urls = [url] if isinstance(url, str) else url + return await cls._get_first_successful( + urls, + params=params, + headers=headers, + cookies=cookies, + verify=verify, + use_proxy=use_proxy, + proxy=proxy, + timeout=timeout, + **kwargs, + ) + + @classmethod + async def _get_first_successful( + cls, + urls: list[str], + **kwargs, + ) -> Response: + last_exception = None + for url in urls: + try: + return await cls._get_single(url, **kwargs) + except Exception as e: + last_exception = e + if url != urls[-1]: + logger.warning(f"获取 {url} 失败, 尝试下一个") + raise last_exception or Exception("All URLs failed") + + @classmethod + async def _get_single( + cls, + url: str, + *, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + cookies: dict[str, str] | None = None, + verify: bool = True, + use_proxy: bool = True, + proxy: dict[str, str] | None = None, + timeout: int = 30, # noqa: ASYNC109 + **kwargs, + ) -> Response: + if not headers: + headers = get_user_agent() + _proxy = proxy or (cls.proxy if use_proxy else None) + async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore + return await client.get( + url, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + **kwargs, + ) + + @classmethod + async def head( + cls, + url: str, + *, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + cookies: dict[str, str] | None = None, + verify: bool = True, + use_proxy: bool = True, + proxy: dict[str, str] | None = None, + timeout: int = 30, # noqa: ASYNC109 **kwargs, ) -> Response: """Get @@ -54,9 +141,9 @@ class AsyncHttpx: """ if not headers: headers = get_user_agent() - _proxy = proxy if proxy else cls.proxy if use_proxy else None + _proxy = proxy or (cls.proxy if use_proxy else None) async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore - return await client.get( + return await client.head( url, params=params, headers=headers, @@ -70,17 +157,17 @@ class AsyncHttpx: cls, url: str, *, - data: Dict[str, Any] | None = None, + data: dict[str, Any] | None = None, content: Any = None, files: Any = None, verify: bool = True, use_proxy: bool = True, - proxy: Dict[str, str] | None = None, - json: Dict[str, Any] | None = None, - params: Dict[str, str] | None = None, - headers: Dict[str, str] | None = None, - cookies: Dict[str, str] | None = None, - timeout: int = 30, + proxy: dict[str, str] | None = None, + json: dict[str, Any] | None = None, + params: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + cookies: dict[str, str] | None = None, + timeout: int = 30, # noqa: ASYNC109 **kwargs, ) -> Response: """ @@ -101,7 +188,7 @@ class AsyncHttpx: """ if not headers: headers = get_user_agent() - _proxy = proxy if proxy else cls.proxy if use_proxy else None + _proxy = proxy or (cls.proxy if use_proxy else None) async with httpx.AsyncClient(proxies=_proxy, verify=verify) as client: # type: ignore return await client.post( url, @@ -116,20 +203,26 @@ class AsyncHttpx: **kwargs, ) + @classmethod + async def get_content(cls, url: str, **kwargs) -> bytes: + res = await cls.get(url, **kwargs) + return res.content + @classmethod async def download_file( cls, - url: str, + url: str | list[str], path: str | Path, *, - params: Dict[str, str] | None = None, + params: dict[str, str] | None = None, verify: bool = True, use_proxy: bool = True, - proxy: Dict[str, str] | None = None, - headers: Dict[str, str] | None = None, - cookies: Dict[str, str] | None = None, - timeout: int = 30, + proxy: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + cookies: dict[str, str] | None = None, + timeout: int = 30, # noqa: ASYNC109 stream: bool = False, + follow_redirects: bool = True, **kwargs, ) -> bool: """下载文件 @@ -151,73 +244,86 @@ class AsyncHttpx: path.parent.mkdir(parents=True, exist_ok=True) try: for _ in range(3): - if not stream: + if not isinstance(url, list): + url = [url] + for u in url: try: - content = ( - await cls.get( - url, + if not stream: + response = await cls.get( + u, params=params, headers=headers, cookies=cookies, use_proxy=use_proxy, proxy=proxy, timeout=timeout, + follow_redirects=follow_redirects, **kwargs, ) - ).content - async with aiofiles.open(path, "wb") as wf: - await wf.write(content) - logger.info(f"下载 {url} 成功.. Path:{path.absolute()}") - return True - except (TimeoutError, ConnectTimeout): - pass - else: - if not headers: - headers = get_user_agent() - _proxy = proxy if proxy else cls.proxy if use_proxy else None - try: - async with httpx.AsyncClient( - proxies=_proxy, verify=verify # type: ignore - ) as client: - async with client.stream( - "GET", - url, - params=params, - headers=headers, - cookies=cookies, - timeout=timeout, - **kwargs, - ) as response: - logger.info( - f"开始下载 {path.name}.. Path: {path.absolute()}" - ) - async with aiofiles.open(path, "wb") as wf: - total = int(response.headers["Content-Length"]) - with rich.progress.Progress( # type: ignore - rich.progress.TextColumn(path.name), # type: ignore - "[progress.percentage]{task.percentage:>3.0f}%", # type: ignore - rich.progress.BarColumn(bar_width=None), # type: ignore - rich.progress.DownloadColumn(), # type: ignore - rich.progress.TransferSpeedColumn(), # type: ignore - ) as progress: - download_task = progress.add_task( - "Download", total=total - ) - async for chunk in response.aiter_bytes(): - await wf.write(chunk) - await wf.flush() - progress.update( - download_task, - completed=response.num_bytes_downloaded, - ) + response.raise_for_status() + content = response.content + async with aiofiles.open(path, "wb") as wf: + await wf.write(content) + logger.info(f"下载 {u} 成功.. Path:{path.absolute()}") + else: + if not headers: + headers = get_user_agent() + _proxy = proxy or (cls.proxy if use_proxy else None) + async with httpx.AsyncClient( + proxies=_proxy, # type: ignore + verify=verify, + ) as client: + async with client.stream( + "GET", + u, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + follow_redirects=True, + **kwargs, + ) as response: + response.raise_for_status() logger.info( - f"下载 {url} 成功.. Path:{path.absolute()}" + f"开始下载 {path.name}.. " + f"Url: {u}.. " + f"Path: {path.absolute()}" ) + async with aiofiles.open(path, "wb") as wf: + total = int( + response.headers.get("Content-Length", 0) + ) + with rich.progress.Progress( # type: ignore + rich.progress.TextColumn(path.name), # type: ignore + "[progress.percentage]{task.percentage:>3.0f}%", # type: ignore + rich.progress.BarColumn(bar_width=None), # type: ignore + rich.progress.DownloadColumn(), # type: ignore + rich.progress.TransferSpeedColumn(), # type: ignore + ) as progress: + download_task = progress.add_task( + "Download", + total=total or None, + ) + async for chunk in response.aiter_bytes(): + await wf.write(chunk) + await wf.flush() + progress.update( + download_task, + completed=response.num_bytes_downloaded, + ) + logger.info( + f"下载 {u} 成功.. Path:{path.absolute()}" + ) return True - except (TimeoutError, ConnectTimeout): - pass - else: - logger.error(f"下载 {url} 下载超时.. Path:{path.absolute()}") + except (TimeoutError, ConnectTimeout, HTTPStatusError): + logger.warning(f"下载 {u} 失败.. 尝试下一个地址..") + except EndOfStream as e: + logger.warning( + f"下载 {url} EndOfStream 异常 Path:{path.absolute()}", e=e + ) + if path.exists(): + return True + logger.error(f"下载 {url} 下载超时.. Path:{path.absolute()}") except Exception as e: logger.error(f"下载 {url} 错误 Path:{path.absolute()}", e=e) return False @@ -225,16 +331,16 @@ class AsyncHttpx: @classmethod async def gather_download_file( cls, - url_list: list[str], + url_list: list[str] | list[list[str]], path_list: list[str | Path], *, limit_async_number: int | None = None, - params: Dict[str, str] | None = None, + params: dict[str, str] | None = None, use_proxy: bool = True, - proxy: Dict[str, str] | None = None, - headers: Dict[str, str] | None = None, - cookies: Dict[str, str] | None = None, - timeout: int = 30, + proxy: dict[str, str] | None = None, + headers: dict[str, str] | None = None, + cookies: dict[str, str] | None = None, + timeout: int = 30, # noqa: ASYNC109 **kwargs, ) -> list[bool]: """分组同时下载文件 @@ -274,39 +380,78 @@ class AsyncHttpx: tasks = [] result_ = [] for x, y in zip(_split_url_list, _split_path_list): - for url, path in zip(x, y): - tasks.append( - asyncio.create_task( - cls.download_file( - url, - path, - params=params, - headers=headers, - cookies=cookies, - use_proxy=use_proxy, - timeout=timeout, - proxy=proxy, - **kwargs, - ) + tasks.extend( + asyncio.create_task( + cls.download_file( + url, + path, + params=params, + headers=headers, + cookies=cookies, + use_proxy=use_proxy, + timeout=timeout, + proxy=proxy, + **kwargs, ) ) + for url, path in zip(x, y) + ) _x = await asyncio.gather(*tasks) result_ = result_ + list(_x) tasks.clear() return result_ + @classmethod + async def get_fastest_mirror(cls, url_list: list[str]) -> list[str]: + assert url_list + + async def head_mirror(client: type[AsyncHttpx], url: str) -> dict[str, Any]: + begin_time = time.time() + + response = await client.head(url=url, timeout=6) + + elapsed_time = (time.time() - begin_time) * 1000 + content_length = int(response.headers.get("content-length", 0)) + + return { + "url": url, + "elapsed_time": elapsed_time, + "content_length": content_length, + } + + logger.debug(f"开始获取最快镜像,可能需要一段时间... | URL列表:{url_list}") + results = await asyncio.gather( + *(head_mirror(cls, url) for url in url_list), + return_exceptions=True, + ) + _results: list[dict[str, Any]] = [] + for result in results: + if isinstance(result, BaseException): + logger.warning(f"获取镜像失败,错误:{result}") + else: + logger.debug(f"获取镜像成功,结果:{result}") + _results.append(result) + _results = sorted(iter(_results), key=lambda r: r["elapsed_time"]) + return [result["url"] for result in _results] + class AsyncPlaywright: @classmethod @asynccontextmanager - async def new_page(cls, **kwargs) -> AsyncGenerator[Page, None]: + async def new_page( + cls, cookies: list[dict[str, Any]] | dict[str, Any] | None = None, **kwargs + ) -> AsyncGenerator[Page, None]: """获取一个新页面 参数: - user_agent: 请求头 + cookies: cookies """ - browser = get_browser() + browser = await get_browser() ctx = await browser.new_context(**kwargs) + if cookies: + if isinstance(cookies, dict): + cookies = [cookies] + await ctx.add_cookies(cookies) # type: ignore page = await ctx.new_page() try: yield page @@ -322,13 +467,14 @@ class AsyncPlaywright: element: str | list[str], *, wait_time: int | None = None, - viewport_size: Dict[str, int] | None = None, + viewport_size: dict[str, int] | None = None, wait_until: ( Literal["domcontentloaded", "load", "networkidle"] | None ) = "networkidle", - timeout: float | None = None, + timeout: float | None = None, # noqa: ASYNC109 type_: Literal["jpeg", "png"] | None = None, user_agent: str | None = None, + cookies: list[dict[str, Any]] | dict[str, Any] | None = None, **kwargs, ) -> UniMessage | None: """截图,该方法仅用于简单快捷截图,复杂截图请操作 page @@ -342,17 +488,17 @@ class AsyncPlaywright: wait_until: 等待类型 timeout: 超时限制 type_: 保存类型 + user_agent: user_agent + cookies: cookies """ if viewport_size is None: - viewport_size = dict(width=2560, height=1080) + viewport_size = {"width": 2560, "height": 1080} if isinstance(path, str): path = Path(path) wait_time = wait_time * 1000 if wait_time else None - if isinstance(element, str): - element_list = [element] - else: - element_list = element + element_list = [element] if isinstance(element, str) else element async with cls.new_page( + cookies, viewport=viewport_size, user_agent=user_agent, **kwargs, diff --git a/zhenxun/utils/image_utils.py b/zhenxun/utils/image_utils.py index c193a565..d5affc7d 100644 --- a/zhenxun/utils/image_utils.py +++ b/zhenxun/utils/image_utils.py @@ -1,23 +1,21 @@ +from collections.abc import Awaitable, Callable +from io import BytesIO import os +from pathlib import Path import random import re -from io import BytesIO -from pathlib import Path -from typing import Awaitable, Callable -import cv2 import imagehash -from imagehash import ImageHash from nonebot.utils import is_coroutine_callable from PIL import Image -from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH +from zhenxun.configs.path_config import TEMP_PATH from zhenxun.services.log import logger from zhenxun.utils.http_utils import AsyncHttpx from ._build_image import BuildImage, ColorAlias -from ._build_mat import BuildMat, MatType -from ._image_template import ImageTemplate, RowStyle +from ._build_mat import BuildMat, MatType # noqa: F401 +from ._image_template import ImageTemplate, RowStyle # noqa: F401 # TODO: text2image 长度错误 @@ -40,8 +38,8 @@ async def text2image( fs / font_size: int -> 特殊文本大小 fc / font_color: Union[str, Tuple[int, int, int]] -> 特殊文本颜色 示例 - 在不在,HibiKi小姐, - 你最近还好吗,我非常想你,这段时间我非常不好过, + 在不在,HibiKi, + 你最近还好吗,我非常想你抽卡抽不到金色,这让我很痛苦 参数: text: 文本 @@ -192,8 +190,9 @@ async def text2image( s.strip(), font, font_size, font_color ) ) + height = sum(img.height + 8 for img in image_list) + pw width += pw - height += ph + # height += ph A = BuildImage( width + left_padding, height + top_padding + 2, @@ -272,7 +271,6 @@ def group_image(image_list: list[BuildImage]) -> tuple[list[list[BuildImage]], i _min_h = 999999 _min_index = -1 for i, ig in enumerate(image_group): - # if i not in is_use and (_h := sum([x.h for x in ig]) + img.h) > _min_h: if (_h := sum([x.height for x in ig]) + img.height) < _min_h: _min_h = _h _min_index = i @@ -348,30 +346,6 @@ async def build_sort_image( return A -def compressed_image( - in_file: str | Path, - out_file: str | Path | None = None, - ratio: float = 0.9, -): - """压缩图片 - - 参数: - in_file: 被压缩的文件路径 - out_file: 压缩后输出的文件路径 - ratio: 压缩率,宽高 * 压缩率 - """ - in_file = IMAGE_PATH / in_file if isinstance(in_file, str) else in_file - if out_file: - out_file = IMAGE_PATH / out_file if isinstance(out_file, str) else out_file - else: - out_file = in_file - h, w, d = cv2.imread(str(in_file.absolute())).shape - img = cv2.resize( - cv2.imread(str(in_file.absolute())), (int(w * ratio), int(h * ratio)) - ) - cv2.imwrite(str(out_file.absolute()), img) - - def get_img_hash(image_file: str | Path) -> str: """获取图片的hash值 @@ -386,11 +360,11 @@ def get_img_hash(image_file: str | Path) -> str: with open(image_file, "rb") as fp: hash_value = imagehash.average_hash(Image.open(fp)) except Exception as e: - logger.warning(f"获取图片Hash出错", "禁言检测", e=e) + logger.warning("获取图片Hash出错", "禁言检测", e=e) return str(hash_value) -async def get_download_image_hash(url: str, mark: str) -> str: +async def get_download_image_hash(url: str, mark: str, use_proxy: bool = False) -> str: """下载图片获取哈希值 参数: @@ -402,12 +376,12 @@ async def get_download_image_hash(url: str, mark: str) -> str: """ try: if await AsyncHttpx.download_file( - url, TEMP_PATH / f"compare_download_{mark}_img.jpg" + url, TEMP_PATH / f"compare_download_{mark}_img.jpg", use_proxy=use_proxy ): img_hash = get_img_hash(TEMP_PATH / f"compare_download_{mark}_img.jpg") return str(img_hash) except Exception as e: - logger.warning(f"下载读取图片Hash出错", e=e) + logger.warning("下载读取图片Hash出错", e=e) return "" diff --git a/zhenxun/utils/manager/message_manager.py b/zhenxun/utils/manager/message_manager.py new file mode 100644 index 00000000..e714c8d8 --- /dev/null +++ b/zhenxun/utils/manager/message_manager.py @@ -0,0 +1,27 @@ +from typing import ClassVar + + +class MessageManager: + data: ClassVar[dict[str, list[str]]] = {} + + @classmethod + def add(cls, uid: str, msg_id: str): + if uid not in cls.data: + cls.data[uid] = [] + cls.data[uid].append(msg_id) + cls.remove_check(uid) + + @classmethod + def check(cls, uid: str, msg_id: str) -> bool: + return msg_id in cls.data.get(uid, []) + + @classmethod + def remove_check(cls, uid: str): + if len(cls.data[uid]) > 200: + cls.data[uid] = cls.data[uid][100:] + + @classmethod + def get(cls, uid: str) -> list[str]: + if uid in cls.data: + return cls.data[uid] + return [] diff --git a/zhenxun/utils/manager/resource_manager.py b/zhenxun/utils/manager/resource_manager.py new file mode 100644 index 00000000..a859d6b9 --- /dev/null +++ b/zhenxun/utils/manager/resource_manager.py @@ -0,0 +1,87 @@ +import os +from pathlib import Path +import shutil +import zipfile + +from zhenxun.configs.path_config import FONT_PATH +from zhenxun.services.log import logger +from zhenxun.utils.github_utils import GithubUtils +from zhenxun.utils.http_utils import AsyncHttpx + +CMD_STRING = "ResourceManager" + + +class DownloadResourceException(Exception): + pass + + +class ResourceManager: + GITHUB_URL = "https://github.com/zhenxun-org/zhenxun-bot-resources/tree/main" + + RESOURCE_PATH = Path() / "resources" + + TMP_PATH = Path() / "_resource_tmp" + + ZIP_FILE = TMP_PATH / "resources.zip" + + UNZIP_PATH = None + + @classmethod + async def init_resources(cls, force: bool = False): + if (FONT_PATH.exists() and os.listdir(FONT_PATH)) and not force: + return + if cls.TMP_PATH.exists(): + logger.debug( + "resources临时文件夹已存在,移除resources临时文件夹", CMD_STRING + ) + shutil.rmtree(cls.TMP_PATH) + cls.TMP_PATH.mkdir(parents=True, exist_ok=True) + try: + await cls.__download_resources() + cls.file_handle() + except Exception as e: + logger.error("获取resources资源包失败", CMD_STRING, e=e) + if cls.TMP_PATH.exists(): + logger.debug("移除resources临时文件夹", CMD_STRING) + shutil.rmtree(cls.TMP_PATH) + + @classmethod + def file_handle(cls): + if not cls.UNZIP_PATH: + return + cls.__recursive_folder(cls.UNZIP_PATH, "resources") + + @classmethod + def __recursive_folder(cls, dir: Path, parent_path: str): + for file in dir.iterdir(): + if file.is_dir(): + cls.__recursive_folder(file, f"{parent_path}/{file.name}") + else: + res_file = Path(parent_path) / file.name + if res_file.exists(): + res_file.unlink() + res_file.parent.mkdir(parents=True, exist_ok=True) + file.rename(res_file) + + @classmethod + async def __download_resources(cls): + """获取resources文件夹""" + repo_info = GithubUtils.parse_github_url(cls.GITHUB_URL) + url = await repo_info.get_archive_download_urls() + logger.debug("开始下载resources资源包...", CMD_STRING) + if not await AsyncHttpx.download_file(url, cls.ZIP_FILE, stream=True): + logger.error( + "下载resources资源包失败,请尝试重启重新下载或前往 " + "https://github.com/zhenxun-org/zhenxun-bot-resources 手动下载..." + ) + raise DownloadResourceException("下载resources资源包失败...") + logger.debug("下载resources资源文件压缩包完成...", CMD_STRING) + tf = zipfile.ZipFile(cls.ZIP_FILE) + tf.extractall(cls.TMP_PATH) + logger.debug("解压文件压缩包完成...", CMD_STRING) + download_file_path = cls.TMP_PATH / next( + x for x in os.listdir(cls.TMP_PATH) if (cls.TMP_PATH / x).is_dir() + ) + cls.UNZIP_PATH = download_file_path / "resources" + if tf: + tf.close() diff --git a/zhenxun/utils/message.py b/zhenxun/utils/message.py index 54e7f908..9cbdbbfa 100644 --- a/zhenxun/utils/message.py +++ b/zhenxun/utils/message.py @@ -1,23 +1,53 @@ +import base64 from io import BytesIO from pathlib import Path import nonebot from nonebot.adapters.onebot.v11 import Message, MessageSegment -from nonebot_plugin_alconna import At, Image, Text, UniMessage +from nonebot_plugin_alconna import ( + At, + AtAll, + Button, + CustomNode, + Image, + Reference, + Text, + UniMessage, + Video, + Voice, +) +from pydantic import BaseModel +import ujson as json -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger from zhenxun.utils._build_image import BuildImage driver = nonebot.get_driver() MESSAGE_TYPE = ( - str | int | float | Path | bytes | BytesIO | BuildImage | At | Image | Text + str + | int + | float + | Path + | bytes + | BytesIO + | BuildImage + | At + | AtAll + | Image + | Text + | Voice + | Video + | Button ) -class MessageUtils: +class Config(BaseModel): + image_to_bytes: bool = False + +class MessageUtils: @classmethod def __build_message(cls, msg_list: list[MESSAGE_TYPE]) -> list[Text | Image]: """构造消息 @@ -28,20 +58,22 @@ class MessageUtils: 返回: list[Text | Text]: 构造完成的消息列表 """ - is_bytes = False - try: - is_bytes = driver.config.image_to_bytes in ["True", "true"] - except AttributeError: - pass + config = nonebot.get_plugin_config(Config) message_list = [] for msg in msg_list: - if isinstance(msg, (Image, Text, At)): - message_list.append(msg) - elif isinstance(msg, (str, int, float)): + if isinstance(msg, str): + if msg.startswith("base64://"): + message_list.append(Image(raw=BytesIO(base64.b64decode(msg[9:])))) + elif msg.startswith("http://"): + message_list.append(Image(url=msg)) + else: + message_list.append(Text(msg)) + elif isinstance(msg, int | float): message_list.append(Text(str(msg))) elif isinstance(msg, Path): if msg.exists(): - if is_bytes: + if config.image_to_bytes: + logger.debug("图片转为bytes发送", "MessageUtils") image = BuildImage.open(msg) message_list.append(Image(raw=image.pic2bytes())) else: @@ -54,6 +86,8 @@ class MessageUtils: message_list.append(Image(raw=msg)) elif isinstance(msg, BuildImage): message_list.append(Image(raw=msg.pic2bytes())) + else: + message_list.append(msg) return message_list @classmethod @@ -76,12 +110,59 @@ class MessageUtils: message_list += cls.__build_message(_data) # type: ignore return UniMessage(message_list) + @classmethod + def alc_forward_msg( + cls, + msg_list: list, + uin: str, + name: str, + ) -> UniMessage: + """生成自定义合并消息 + + 参数: + msg_list: 消息列表 + uin: 发送者 QQ + name: 自定义名称 + + 返回: + list[dict]: 转发消息 + """ + node_list = [] + for _message in msg_list: + if isinstance(_message, list): + for i in range(len(_message.copy())): + if isinstance(_message[i], Path): + _message[i] = Image( + raw=BuildImage.open(_message[i]).pic2bytes() + ) + elif isinstance(_message[i], BuildImage): + _message[i] = Image(raw=_message[i].pic2bytes()) + node_list.append( + CustomNode(uid=uin, name=name, content=UniMessage(_message)) + ) + return UniMessage(Reference(nodes=node_list)) + + @classmethod + def markdown(cls, content: dict) -> Message: + """markdown格式消息 + + 参数: + content: 消息内容 + + 返回: + Message: 构造完成的消息 + """ + content_data = base64.b64encode(json.dumps(content).encode("utf-8")).decode( + "utf-8" + ) + return Message(f"[CQ:markdown,data=base64://{content_data}]") + @classmethod def custom_forward_msg( cls, msg_list: list[str | Message], uin: str, - name: str = f"这里是{NICKNAME}", + name: str = f"这里是{BotConfig.self_nickname}", ) -> list[dict]: """生成自定义合并消息 @@ -120,12 +201,12 @@ class MessageUtils: forward_data = [] for r_list in msg_list: s = "" - if isinstance(r_list, (UniMessage, list)): + if isinstance(r_list, UniMessage | list): for r in r_list: if isinstance(r, Text): s += str(r) elif isinstance(r, Image): - if v := r.url or r.path: + if v := r.url or r.path or r.raw: s += MessageSegment.image(v) elif isinstance(r_list, Image): if v := r_list.url or r_list.path: @@ -134,3 +215,28 @@ class MessageUtils: s = str(r_list) forward_data.append(s) return cls.custom_forward_msg(forward_data, uni) + + @classmethod + def template2alc(cls, msg_list: list[MessageSegment]) -> list: + """模板转alc + + 参数: + msg_list: 消息列表 + + 返回: + list: alc模板 + """ + forward_data = [] + for msg in msg_list: + if isinstance(msg, str): + forward_data.append(Text(msg)) + elif msg.type == "at": + if msg.data["qq"] == "0": + forward_data.append(AtAll()) + else: + forward_data.append(At(flag="user", target=msg.data["qq"])) + elif msg.type == "image": + forward_data.append(Image(url=msg.data["file"] or msg.data["url"])) + elif msg.type == "text" and msg.data["text"]: + forward_data.append(Text(msg.data["text"])) + return forward_data diff --git a/zhenxun/utils/platform.py b/zhenxun/utils/platform.py index 07213280..fbf877e5 100644 --- a/zhenxun/utils/platform.py +++ b/zhenxun/utils/platform.py @@ -1,27 +1,29 @@ +from collections.abc import Awaitable, Callable import random -from typing import Awaitable, Callable, Literal, Set +from typing import Literal import httpx import nonebot from nonebot.adapters import Bot -from nonebot.adapters.discord import Bot as DiscordBot -from nonebot.adapters.dodo import Bot as DodoBot -from nonebot.adapters.kaiheila import Bot as KaiheilaBot -from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v12 import Bot as v12Bot from nonebot.utils import is_coroutine_callable +from nonebot_plugin_alconna import SupportScope from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage +from nonebot_plugin_uninfo import SceneType, Uninfo, get_interface +from nonebot_plugin_uninfo.model import Member from pydantic import BaseModel +from zhenxun.configs.config import BotConfig from zhenxun.models.friend_user import FriendUser from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger from zhenxun.utils.exception import NotFindSuperuser +from zhenxun.utils.http_utils import AsyncHttpx from zhenxun.utils.message import MessageUtils +driver = nonebot.get_driver() + class UserData(BaseModel): - name: str """昵称""" card: str | None = None @@ -30,6 +32,8 @@ class UserData(BaseModel): """用户id""" group_id: str | None = None """群组id""" + channel_id: str | None = None + """频道id""" role: str | None = None """角色""" avatar_url: str | None = None @@ -39,6 +43,21 @@ class UserData(BaseModel): class PlatformUtils: + @classmethod + def is_qbot(cls, session: Uninfo | Bot) -> bool: + """判断bot是否为qq官bot + + 参数: + session: Uninfo + + 返回: + bool: 是否为官bot + """ + if isinstance(session, Bot): + return bool(BotConfig.get_qbot_uid(session.self_id)) + if BotConfig.get_qbot_uid(session.self_id): + return True + return session.scope == SupportScope.qq_api @classmethod async def ban_user(cls, bot: Bot, user_id: str, group_id: str, duration: int): @@ -50,7 +69,7 @@ class PlatformUtils: group_id: 群组id duration: 禁言时长(分钟) """ - if isinstance(bot, v11Bot): + if cls.get_platform(bot) == "qq": await bot.set_group_ban( group_id=int(group_id), user_id=int(user_id), @@ -61,7 +80,7 @@ class PlatformUtils: async def send_superuser( cls, bot: Bot, - message: UniMessage, + message: UniMessage | str, superuser_id: str | None = None, ) -> Receipt | None: """发送消息给超级用户 @@ -78,11 +97,13 @@ class PlatformUtils: Receipt | None: Receipt """ if not superuser_id: - platform = cls.get_platform(bot) - platform_superusers = bot.config.PLATFORM_SUPERUSERS.get(platform) or [] - if not platform_superusers: - raise NotFindSuperuser() - superuser_id = random.choice(platform_superusers) + if platform := cls.get_platform(bot): + if platform_superusers := BotConfig.get_superuser(platform): + superuser_id = random.choice(platform_superusers) + else: + raise NotFindSuperuser() + if isinstance(message, str): + message = MessageUtils.build_message(message) return await cls.send_message(bot, superuser_id, None, message) @classmethod @@ -96,195 +117,117 @@ class PlatformUtils: 返回: list[UserData]: 用户数据列表 """ - if isinstance(bot, v11Bot): - if member_list := await bot.get_group_member_list(group_id=int(group_id)): - return [ - UserData( - name=user["nickname"], - card=user["card"], - user_id=user["user_id"], - group_id=user["group_id"], - role=user["role"], - join_time=user["join_time"], - ) - for user in member_list - ] - if isinstance(bot, v12Bot): - if member_list := await bot.get_group_member_list(group_id=group_id): - return [ - UserData( - name=user["user_name"], - card=user["user_displayname"], - user_id=user["user_id"], - group_id=group_id, - ) - for user in member_list - ] - if isinstance(bot, DodoBot): - if result_data := await bot.get_member_list( - island_source_id=group_id, page_size=100, max_id=0 - ): - max_id = result_data.max_id - result_list = result_data.list - data_list = [] - while max_id == 100: - result_data = await bot.get_member_list( - island_source_id=group_id, page_size=100, max_id=0 - ) - result_list += result_data.list - max_id = result_data.max_id - for user in result_list: - data_list.append( - UserData( - name=user.nick_name, - card=user.personal_nick_name, - avatar_url=user.avatar_url, - user_id=user.dodo_source_id, - group_id=user.island_source_id, - join_time=int(user.join_time.timestamp()), - ) - ) - return data_list - if isinstance(bot, KaiheilaBot): - if result_data := await bot.guild_userList(guild_id=group_id): - if result_data.users: - data_list = [] - for user in result_data.users: - second = None - if user.joined_at: - second = int(user.joined_at / 1000) - data_list.append( - UserData( - name=user.nickname or "", - avatar_url=user.avatar, - user_id=user.id_, # type: ignore - group_id=group_id, - join_time=second, - ) - ) - return data_list - if isinstance(bot, DiscordBot): - # TODO: discord获取用户 - pass + if interface := get_interface(bot): + members: list[Member] = await interface.get_members( + SceneType.GROUP, group_id + ) + return [ + UserData( + name=member.user.name or "", + card=member.nick, + user_id=member.user.id, + group_id=group_id, + role=member.role.id if member.role else "", + avatar_url=member.user.avatar, + join_time=int(member.joined_at.timestamp()) + if member.joined_at + else None, + ) + for member in members + ] return [] @classmethod async def get_user( - cls, bot: Bot, user_id: str, group_id: str | None = None + cls, + bot: Bot, + user_id: str, + group_id: str | None = None, + channel_id: str | None = None, ) -> UserData | None: """获取用户信息 参数: bot: Bot user_id: 用户id - group_id: 群组/频道id. + group_id: 群组id. + channel_id: 频道id. 返回: UserData | None: 用户数据 """ - if isinstance(bot, v11Bot): - if group_id: - if user := await bot.get_group_member_info( - group_id=int(group_id), user_id=int(user_id) - ): - return UserData( - name=user["nickname"], - card=user["card"], - user_id=user["user_id"], - group_id=user["group_id"], - role=user["role"], - join_time=user["join_time"], - ) + if interface := get_interface(bot): + member = None + user = None + if channel_id: + member = await interface.get_member( + SceneType.CHANNEL_TEXT, channel_id, user_id + ) + if member: + user = member.user + elif group_id: + member = await interface.get_member(SceneType.GROUP, group_id, user_id) + if member: + user = member.user else: - if friend_list := await bot.get_friend_list(): - for f in friend_list: - if f["user_id"] == int(user_id): - return UserData( - name=f["nickname"], - card=f["remark"], - user_id=f["user_id"], - ) - if isinstance(bot, v12Bot): - if group_id: - if user := await bot.get_group_member_info( - group_id=group_id, user_id=user_id - ): - return UserData( - name=user["user_name"], - card=user["user_displayname"], - user_id=user["user_id"], - group_id=group_id, - ) + user = await interface.get_user(user_id) + if not user: + return None + if member: + return UserData( + name=user.name or "", + card=member.nick, + user_id=user.id, + group_id=group_id, + channel_id=channel_id, + role=member.role.id if member.role else None, + join_time=int(member.joined_at.timestamp()) + if member.joined_at + else None, + ) else: - if friend_list := await bot.get_friend_list(): - for f in friend_list: - if f["user_id"] == int(user_id): - return UserData( - name=f["user_name"], - card=f["user_remark"], - user_id=f["user_id"], - ) - if isinstance(bot, DodoBot): - if group_id: - if user := await bot.get_member_info( - island_source_id=group_id, dodo_source_id=user_id - ): - return UserData( - name=user.nick_name, - card=user.personal_nick_name, - avatar_url=user.avatar_url, - user_id=user.dodo_source_id, - group_id=user.island_source_id, - join_time=int(user.join_time.timestamp()), - ) - else: - # TODO: DoDo个人数据 - pass - if isinstance(bot, KaiheilaBot): - if group_id: - if user := await bot.user_view(guild_id=group_id, user_id=user_id): - second = None - if user.joined_at: - second = int(user.joined_at / 1000) - return UserData( - name=user.nickname or "", - avatar_url=user.avatar, - user_id=user_id, - group_id=group_id, - join_time=second, - ) - else: - # TODO: kaiheila用户详情 - pass - if isinstance(bot, DiscordBot): - # TODO: discord获取用户 - pass + return UserData( + name=user.name or "", + user_id=user.id, + group_id=group_id, + channel_id=channel_id, + ) return None @classmethod - async def get_user_avatar(cls, user_id: str, platform: str) -> bytes | None: + async def get_user_avatar( + cls, user_id: str, platform: str, appid: str | None = None + ) -> bytes | None: """快捷获取用户头像 参数: user_id: 用户id platform: 平台 """ + url = None if platform == "qq": - url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160" - async with httpx.AsyncClient() as client: - for _ in range(3): - try: - return (await client.get(url)).content - except Exception as e: - logger.error( - "获取用户头像错误", - "Util", - target=user_id, - platform=platform, - ) + if user_id.isdigit(): + url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160" + else: + url = f"https://q.qlogo.cn/qqapp/{appid}/{user_id}/640" + return await AsyncHttpx.get_content(url) if url else None + + @classmethod + def get_user_avatar_url( + cls, user_id: str, platform: str, appid: str | None = None + ) -> str | None: + """快捷获取用户头像url + + 参数: + user_id: 用户id + platform: 平台 + """ + if platform != "qq": + return None + if user_id.isdigit(): + return f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=160" else: - pass - return None + return f"https://q.qlogo.cn/qqapp/{appid}/{user_id}/640" @classmethod async def get_group_avatar(cls, gid: str, platform: str) -> bytes | None: @@ -300,12 +243,10 @@ class PlatformUtils: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error( "获取群头像错误", "Util", target=gid, platform=platform ) - else: - pass return None @classmethod @@ -327,7 +268,7 @@ class PlatformUtils: 返回: Receipt | None: 是否发送成功 """ - if target := cls.get_target(bot, user_id, group_id): + if target := cls.get_target(user_id=user_id, group_id=group_id): send_message = ( MessageUtils.build_message(message) if isinstance(message, str) @@ -347,26 +288,43 @@ class PlatformUtils: int: 更新个数 """ create_list = [] + update_list = [] group_list, platform = await cls.get_group_list(bot) if group_list: - exists_group_list = await GroupConsole.all().values_list( - "group_id", "channel_id" - ) + db_group = await GroupConsole.all() + db_group_id: list[tuple[str, str]] = [ + (group.group_id, group.channel_id) for group in db_group + ] for group in group_list: group.platform = platform - if (group.group_id, group.channel_id) not in exists_group_list: + if (group.group_id, group.channel_id) not in db_group_id: create_list.append(group) logger.debug( "群聊信息更新成功", "更新群信息", target=f"{group.group_id}:{group.channel_id}", ) + else: + _group = next( + g + for g in db_group + if g.group_id == group.group_id + and g.channel_id == group.channel_id + ) + _group.group_name = group.group_name + _group.max_member_count = group.max_member_count + _group.member_count = group.member_count + update_list.append(_group) if create_list: await GroupConsole.bulk_create(create_list, 10) + if group_list: + await GroupConsole.bulk_update( + update_list, ["group_name", "max_member_count", "member_count"], 10 + ) return len(create_list) @classmethod - def get_platform(cls, bot: Bot) -> str | None: + def get_platform(cls, t: Bot | Uninfo) -> str: """获取平台 参数: @@ -375,84 +333,53 @@ class PlatformUtils: 返回: str | None: 平台 """ - if isinstance(bot, (v11Bot, v12Bot)): - return "qq" - # if isinstance(bot, DodoBot): - # return "dodo" - # if isinstance(bot, KaiheilaBot): - # return "kaiheila" - # if isinstance(bot, DiscordBot): - # return "discord" - return None + if isinstance(t, Bot): + if interface := get_interface(t): + info = interface.basic_info() + platform = info["scope"].lower() + return "qq" if platform.startswith("qq") else platform + else: + platform = t.basic["scope"].lower() + return "qq" if platform.startswith("qq") else platform + return "unknown" @classmethod - async def get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]: + async def get_group_list( + cls, bot: Bot, only_group: bool = False + ) -> tuple[list[GroupConsole], str]: """获取群组列表 参数: bot: Bot + only_group: 是否只获取群组(不获取channel) 返回: tuple[list[GroupConsole], str]: 群组列表, 平台 """ - if isinstance(bot, v11Bot): - group_list = await bot.get_group_list() - return [ + if not (interface := get_interface(bot)): + return [], "" + platform = cls.get_platform(bot) + result_list = [] + scenes = await interface.get_scenes(SceneType.GROUP) + for scene in scenes: + group_id = scene.id + result_list.append( GroupConsole( - group_id=str(g["group_id"]), - group_name=g["group_name"], - max_member_count=g["max_member_count"], - member_count=g["member_count"], + group_id=scene.id, + group_name=scene.name, ) - for g in group_list - ], "qq" - if isinstance(bot, v12Bot): - group_list = await bot.get_group_list() - return [ - GroupConsole( - group_id=g.group_id, # type: ignore - user_name=g.group_name, # type: ignore - ) - for g in group_list - ], "qq" - if isinstance(bot, DodoBot): - island_list = await bot.get_island_list() - source_id_list = [ - (g.island_source_id, g.island_name) - for g in island_list - if g.island_source_id - ] - group_list = [] - for id, name in source_id_list: - channel_list = await bot.get_channel_list(island_source_id=id) - group_list.append(GroupConsole(group_id=id, group_name=name)) - group_list += [ - GroupConsole( - group_id=id, group_name=c.channel_name, channel_id=c.channel_id + ) + if not only_group and platform != "qq": + if channel_list := await interface.get_scenes(parent_scene_id=group_id): + result_list.extend( + GroupConsole( + group_id=scene.id, + group_name=channel.name, + channel_id=channel.id, + ) + for channel in channel_list ) - for c in channel_list - ] - return group_list, "dodo" - if isinstance(bot, KaiheilaBot): - group_list = [] - guilds = await bot.guild_list() - if guilds.guilds: - for guild_id, name in [(g.id_, g.name) for g in guilds.guilds if g.id_]: - view = await bot.guild_view(guild_id=guild_id) - group_list.append(GroupConsole(group_id=guild_id, group_name=name)) - if view.channels: - group_list += [ - GroupConsole( - group_id=guild_id, group_name=c.name, channel_id=c.id_ - ) - for c in view.channels - if c.type != 0 - ] - return group_list, "kaiheila" - if isinstance(bot, DiscordBot): - # TODO: discord群组列表 - pass - return [], "" + return result_list, platform @classmethod async def update_friend(cls, bot: Bot) -> int: @@ -486,36 +413,17 @@ class PlatformUtils: 返回: list[FriendUser]: 好友列表 """ - if isinstance(bot, v11Bot): - friend_list = await bot.get_friend_list() + if interface := get_interface(bot): + user_list = await interface.get_users() return [ - FriendUser(user_id=str(f["user_id"]), user_name=f["nickname"]) - for f in friend_list - ], "qq" - if isinstance(bot, v12Bot): - friend_list = await bot.get_friend_list() - return [ - FriendUser( - user_id=f.user_id, # type: ignore - user_name=f.user_displayname or f.user_remark or f.user_name, # type: ignore - ) - for f in friend_list - ], "qq" - if isinstance(bot, DodoBot): - # TODO: dodo好友列表 - pass - if isinstance(bot, KaiheilaBot): - # TODO: kaiheila好友列表 - pass - if isinstance(bot, DiscordBot): - # TODO: discord好友列表 - pass + FriendUser(user_id=u.id, user_name=u.name) for u in user_list + ], cls.get_platform(bot) return [], "" @classmethod def get_target( cls, - bot: Bot, + *, user_id: str | None = None, group_id: str | None = None, channel_id: str | None = None, @@ -532,25 +440,21 @@ class PlatformUtils: target: 对应平台Target """ target = None - if isinstance(bot, (v11Bot, v12Bot)): - if group_id: - target = Target(group_id) - elif user_id: - target = Target(user_id, private=True) - elif isinstance(bot, (DodoBot, KaiheilaBot)): - if group_id and channel_id: - target = Target(channel_id, parent_id=group_id, channel=True) - elif user_id: - target = Target(user_id, private=True) + if group_id and channel_id: + target = Target(channel_id, parent_id=group_id, channel=True) + elif group_id: + target = Target(group_id) + elif user_id: + target = Target(user_id, private=True) return target async def broadcast_group( message: str | UniMessage, bot: Bot | list[Bot] | None = None, - bot_id: str | Set[str] | None = None, - ignore_group: Set[int] | None = None, - check_func: Callable[[str], Awaitable] | None = None, + bot_id: str | set[str] | None = None, + ignore_group: set[int] | None = None, + check_func: Callable[[Bot, str], Awaitable] | None = None, log_cmd: str | None = None, platform: Literal["qq", "dodo", "kaiheila"] | None = None, ): @@ -604,17 +508,29 @@ async def broadcast_group( or group.channel_id in ignore_group ) ) or key in _used_group: + logger.debug( + "广播方法群组重复, 已跳过...", + log_cmd, + group_id=group.group_id, + ) continue is_run = False if check_func: if is_coroutine_callable(check_func): - is_run = await check_func(group.group_id) + is_run = await check_func(_bot, group.group_id) else: - is_run = check_func(group.group_id) + is_run = check_func(_bot, group.group_id) if not is_run: + logger.debug( + "广播方法检测运行方法为 False, 已跳过...", + log_cmd, + group_id=group.group_id, + ) continue target = PlatformUtils.get_target( - _bot, None, group.group_id, group.channel_id + user_id=None, + group_id=group.group_id, + channel_id=group.channel_id, ) if target: _used_group.append(key) diff --git a/zhenxun/utils/plugin_models/base.py b/zhenxun/utils/plugin_models/base.py index a50f6b4a..74692004 100644 --- a/zhenxun/utils/plugin_models/base.py +++ b/zhenxun/utils/plugin_models/base.py @@ -2,7 +2,6 @@ from pydantic import BaseModel class CommonSql(BaseModel): - sql: str """sql语句""" remark: str diff --git a/zhenxun/utils/rules.py b/zhenxun/utils/rules.py index f3381a48..d439e3fe 100644 --- a/zhenxun/utils/rules.py +++ b/zhenxun/utils/rules.py @@ -1,10 +1,12 @@ from nonebot.adapters import Bot, Event from nonebot.internal.rule import Rule from nonebot.permission import SUPERUSER -from nonebot_plugin_session import EventSession, SessionLevel +from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.config import Config from zhenxun.models.level_user import LevelUser +from zhenxun.utils.platform import PlatformUtils def admin_check(a: int | str, key: str | None = None) -> Rule: @@ -19,33 +21,38 @@ def admin_check(a: int | str, key: str | None = None) -> Rule: Rule: Rule """ - async def _rule(bot: Bot, event: Event, session: EventSession) -> bool: + async def _rule(bot: Bot, event: Event, session: Uninfo) -> bool: if await SUPERUSER(bot, event): return True - if session.id1 and session.id2: + if PlatformUtils.is_qbot(session): + """官bot接口,放弃所有权限检查""" + return False + if session.group: level = a - if type(a) == str and key: + if isinstance(a, str) and key: level = Config.get_config(a, key) if level is not None: return bool( - await LevelUser.check_level(session.id1, session.id2, int(level)) + await LevelUser.check_level( + session.user.id, session.group.id, int(level) + ) ) return False return Rule(_rule) -def ensure_group(session: EventSession) -> bool: +def ensure_group(session: Uninfo) -> bool: """ 是否在群聊中 参数: - session: session + session: Uninfo 返回: bool: bool """ - return session.level in [SessionLevel.LEVEL2, SessionLevel.LEVEL3] + return bool(session.group) def ensure_private(session: EventSession) -> bool: @@ -59,3 +66,26 @@ def ensure_private(session: EventSession) -> bool: bool: bool """ return not session.id3 and not session.id2 + + +def notice_rule(event_type: type | list[type]) -> Rule: + """ + Notice限制 + + 参数: + event_type: Event类型 + + 返回: + Rule: Rule + """ + + async def _rule(event: Event) -> bool: + if isinstance(event_type, list): + for et in event_type: + if isinstance(event, et): + return True + else: + return isinstance(event, event_type) + return False + + return Rule(_rule) diff --git a/zhenxun/utils/typing.py b/zhenxun/utils/typing.py deleted file mode 100644 index b28b04f6..00000000 --- a/zhenxun/utils/typing.py +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/zhenxun/utils/user_agent.py b/zhenxun/utils/user_agent.py index 3047da33..36a29f78 100644 --- a/zhenxun/utils/user_agent.py +++ b/zhenxun/utils/user_agent.py @@ -1,44 +1,69 @@ import random user_agent = [ - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) " + "AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) " + "AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0", - "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) like Gecko", + "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; " + ".NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; InfoPath.3; rv:11.0) " + "like Gecko", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) " + "Gecko/20100101 Firefox/4.0.1", "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11", "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) " + "AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; " + "Trident/4.0; SE 2.X MetaSr 1.0; " + "SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)", - "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5", - "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5", - "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5", - "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", - "MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", - "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10", - "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13", - "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+", - "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0", - "Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)", + "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) " + "AppleWebKit/533.17.9 (KHTML, like Gecko) " + "Version/5.0.2 Mobile/8J2 Safari/6533.18.5", + "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) " + "AppleWebKit/533.17.9 (KHTML, like Gecko) " + "Version/5.0.2 Mobile/8J2 Safari/6533.18.5", + "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) " + "AppleWebKit/533.17.9 (KHTML, like Gecko) " + "Version/5.0.2 Mobile/8J2 Safari/6533.18.5", + "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) " + "AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + "MQQBrowser/26 Mozilla/5.0 " + "(Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) " + "AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", + "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) " + "Presto/2.8.149 Version/11.10", + "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) " + "AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13", + "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) " + "AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+", + "Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) " + "AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0", + "Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; " + "Profile/MIDP-2.1 Configuration/CLDC-1.1) " + "AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; " + "Trident/5.0; IEMobile/9.0; HTC; Titan)", "UCWEB7.0.2.37/28/999", "NOKIA5700/ UCWEB7.0.2.37/28/999", "Openwave/ UCWEB7.0.2.37/28/999", "Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999", # iPhone 6: - "Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25", + "Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) " + "AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25", ] diff --git a/zhenxun/utils/utils.py b/zhenxun/utils/utils.py index 7ea698a9..c8046813 100644 --- a/zhenxun/utils/utils.py +++ b/zhenxun/utils/utils.py @@ -1,8 +1,8 @@ -import os -import time from collections import defaultdict from datetime import datetime +import os from pathlib import Path +import time from typing import Any import httpx @@ -18,7 +18,7 @@ class ResourceDirManager: 临时文件管理器 """ - temp_path = [] + temp_path = [] # noqa: RUF012 @classmethod def __tree_append(cls, path: Path): @@ -69,7 +69,7 @@ class CountLimiter: if day != self.today: self.today = day self.count.clear() - return bool(self.count[key] < self.max) + return self.count[key] < self.max def get_num(self, key): return self.count[key] @@ -130,10 +130,7 @@ def cn2py(word: str) -> str: 参数: word: 文本 """ - temp = "" - for i in pypinyin.pinyin(word, style=pypinyin.NORMAL): - temp += "".join(i) - return temp + return "".join("".join(i) for i in pypinyin.pinyin(word, style=pypinyin.NORMAL)) async def get_user_avatar(uid: int | str) -> bytes | None: @@ -147,7 +144,7 @@ async def get_user_avatar(uid: int | str) -> bytes | None: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error("获取用户头像错误", "Util", target=uid) return None @@ -163,7 +160,7 @@ async def get_group_avatar(gid: int | str) -> bytes | None: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error("获取群头像错误", "Util", target=gid) return None @@ -192,6 +189,7 @@ def change_pixiv_image_links( url = ( url.replace("i.pximg.net", nginx_url) .replace("i.pixiv.cat", nginx_url) + .replace("i.pixiv.re", nginx_url) .replace("_webp", "") ) return url @@ -230,3 +228,19 @@ def is_valid_date(date_text: str, separator: str = "-") -> bool: return True except ValueError: return False + + +def is_number(text: str) -> bool: + """是否为数字 + + 参数: + text: 文本 + + 返回: + bool: 是否为数字 + """ + try: + float(text) + return True + except ValueError: + return False diff --git a/zhenxun/utils/withdraw_manage.py b/zhenxun/utils/withdraw_manage.py index d88b394f..3dfc5e80 100644 --- a/zhenxun/utils/withdraw_manage.py +++ b/zhenxun/utils/withdraw_manage.py @@ -14,8 +14,7 @@ from zhenxun.services.log import logger class WithdrawManager: - - _data = {} + _data = {} # noqa: RUF012 _index = 0 @classmethod @@ -34,7 +33,7 @@ class WithdrawManager: return True if withdraw_time[1] == 1 and (session.id2 or session.id3): return True - if withdraw_time[1] == 0 and not (session.id2 or session.id3): + if withdraw_time[1] == 0 and not session.id2 and not session.id3: return True return False @@ -82,7 +81,7 @@ class WithdrawManager: if time: gid = None _time = 1 - if isinstance(time, (tuple, CommentedSeq)): + if isinstance(time, tuple | CommentedSeq): if time[0] == 0: return if session: